Ir para o conteúdo

Conversão de taxa de símbolo

A conversão de taxa de símbolo é uma ação essencial se quiser que a sua aplicação inclua funcionalidades relacionadas com cálculos de P&L.

Casos de utilização

A execução de conversões de taxa de símbolo é necessária ao calcular comissões, swaps e dividendos. As comissões, swaps e dividendos são normalmente denominados em USD. No entanto, dependendo do seu corretor e das suas preferências, a moeda de depósito da sua conta pode ser em qualquer outro ativo que não USD. Para definir com precisão comissões, swaps e dividendos, precisaria de saber a taxa de câmbio entre USD e a moeda de depósito da sua conta.

Cadeias de conversão

Pense no preço de cada símbolo individual como uma taxa de conversão entre o seu ativo base e o ativo de cotação. A tarefa de obter taxas de conversão torna-se complicada se não houver símbolos que liguem diretamente um determinado ativo a outro ativo. Felizmente, o backend do cTrader pode criar automaticamente cadeias de conversão que atuam como os caminhos de conversão mais curtos possíveis de um ativo para outro.

Por exemplo, se quiser converter EUR em NZD, e não houver símbolo EURNZD no servidor de negociação, o cTrader irá propor uma cadeia que servirá como passos intermediários no processo de conversão. Dependendo da disponibilidade de símbolos, tal cadeia poderia ser EURUSD-USDCAD-CADNZD, EURCFH-CFHUSD-USDNZD, ou até EURCAD-CADUSD-USDCFH-CFHNZD.

Para converter entre dois ativos, a sua aplicação precisa de executar as seguintes ações:

  • Aceder aos IDs dos ativos entre os quais pretende converter.
  • Obtenha uma cadeia de conversão que inclua um ou mais símbolos leves.
  • Subscreva e processe eventos spot.
  • Siga a cadeia de conversão e retorne a taxa de conversão final.

Abaixo, explicamos cada uma destas ações em detalhe.

Aceder a IDs de ativos

Os IDs de ativos podem ser acedidos diretamente como propriedades da entidade de símbolo leve (representada pela ProtoOALightSymbol mensagem modelo). Além disso, pode obter o ID de uma moeda de depósito de conta diretamente da sua entidade ProtoOaTrader relacionada.

Note que o exemplo abaixo apenas obtém IDs de ativos para um símbolo. Para realizar a mesma operação para vários símbolos, seria necessário adicionar um ciclo ou usar outra solução adequada (por exemplo, um método map() ou equivalente).

Trabalhar com JSON

Ao trabalhar com JSON, ainda pode reutilizar o código abaixo e outros fragmentos de código neste tutorial. No entanto, certifique-se de substituir os nomes das classes Proto... pelos nomes das suas classes personalizadas e faça as modificações necessárias para acomodar o seu cliente TCP e WebSocket preferido.

symbol é uma variável do tipo ProtoOALightSymbol. Se estiver a usar JSON, pode ser de qualquer tipo personalizado que represente a mensagem ProtoOALightSymbol.

1
2
int baseAssetId = symbol.BaseAsset.AssetId; 
int quoteAssetId = symbol.QuoteAsset.AssetId;

No exemplo abaixo, obtemos o ID da moeda de depósito do objeto account que é do tipo ProtoOATrader.

1
int depositAssetId = account.DepositAssetId;

symbol é uma variável que representa uma mensagem do tipo ProtoOALightSymbol.

1
2
baseAssetId = symbol.BaseAsset.AssetId
quoteAssetId = symbol.QuoteAsset.AssetId

No exemplo abaixo, obtemos o ID da moeda de depósito do objeto account que representa a mensagem modelo ProtoOATrader.

1
depositAssetId = account.depositAssetId

Obter uma cadeia de conversão

Para obter uma cadeia de conversão válida, a sua aplicação precisa de enviar a mensagem ProtoOASymbolsForConversionReq com os seus campos firstAssetId e lastAssetId definidos para os IDs dos ativos entre os quais pretende converter. Quando receber uma resposta do tipo ProtoOASymbolsForConversionRes, armazene o seu campo symbol repetido dentro de uma coleção.

Aqui está como pode realizar esta ação através dos SDKs oficiais.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
List<ProtoOALightSymbol> lightSymbolsForConversion = new List<ProtoOALightSymbol>();

var symbolsResult = await GetConversionSymbols(accountId, isLive, baseAssetId, quoteAssetId);

lightSymbolsForConversion.AddRange(symbolsResult);

public Task<ProtoOALightSymbol[]> GetConversionSymbols(long accountId, bool isLive, long baseAssetId, long quoteAssetId)
{
    VerifyConnection();

    var client = GetClient(isLive);

    var taskCompletionSource = new TaskCompletionSource<ProtoOALightSymbol[]>();

    IDisposable disposable = null;

    disposable = client.OfType<ProtoOASymbolsForConversionRes>().Where(response => response.CtidTraderAccountId == accountId)
        .Subscribe(response =>
        {
            taskCompletionSource.SetResult(response.Symbol.ToArray());

            disposable?.Dispose();
        });

    var requestMessage = new ProtoOASymbolsForConversionReq
    {
        CtidTraderAccountId = accountId,
        FirstAssetId = baseAssetId,
        LastAssetId = quoteAssetId
    };

    EnqueueMessage(requestMessage, ProtoOAPayloadType.ProtoOASymbolsForConversionReq, client);

    return taskCompletionSource.Task;
}
1
2
3
4
5
def sendProtoOASymbolsForConversionReq(accountId, firstAssetId, lastAssetId):
    request = ProtoOASymbolsForConversionReq()
    request.ctidTraderAccountId = accountId
    request.firstAssedId = firstAssetId
    request.lastAssetId = lastAssetId

Adicione também a seguinte condição ao callback onMessageReceived.

1
2
3
elif message.payloadType == ProtoOASymbolsForConversionRes().payloadType:
    ProtoOASymbolsForConversionRes = Protobuf.extract(message)
    conversionSymbols = ProtoOASymbolsForConversionRes.symbol

Nota

Recomendamos vivamente obter todos os símbolos da cadeia de conversão para cada ativo quando solicitar e receber dados de ativos do servidor pela primeira vez. Caso contrário, terá de enviar a mensagem ProtoOASymbolsForConversionReq sempre que precisar de converter taxas, o que, em alguns casos, pode ser várias vezes por segundo.

Nota

Como as entidades ProtoOALightSymbol não contêm campos que representem preços de venda ou compra, recomendamos vivamente criar classes Symbol personalizadas ou equivalentes para que possa criar facilmente novos símbolos programaticamente e atualizar as suas propriedades quando determinados eventos forem acionados. Terá de converter símbolos leves em objetos desta classe separadamente.

Subscrever e processar eventos spot

Para seguir a cadeia de conversão do início ao fim, precisa de subscrever o ProtoOASpotEvent para todos os símbolos incluídos na cadeia. Isto é feito da seguinte forma através dos SDKs oficiais.

Antes de realizar as operações listadas abaixo, crie uma classe Symbol ou equivalente para que possa criar facilmente novos símbolos programaticamente e atualizar as suas propriedades quando determinados eventos forem acionados. Nos exemplos abaixo, a nossa classe Symbol inclui objetos ProtoOASymbol e ProtoOALightSymbol como propriedades.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public Task<ProtoOASubscribeSpotsRes> SubscribeToSpots(long accountId, bool isLive, params long[] symbolIds)
{
    var client = GetClient(isLive);

    var taskCompletionSource = new TaskCompletionSource<ProtoOASubscribeSpotsRes>();

    IDisposable disposable = null;

    disposable = client.OfType<ProtoOASubscribeSpotsRes>().Where(response => response.CtidTraderAccountId == accountId).Subscribe(response =>
    {
        taskCompletionSource.SetResult(response);

        disposable?.Dispose();
    });

    var requestMessage = new ProtoOASubscribeSpotsReq
    {
        CtidTraderAccountId = accountId,
    };

    requestMessage.SymbolId.AddRange(symbolIds);

    EnqueueMessage(requestMessage, ProtoOAPayloadType.ProtoOaSubscribeSpotsReq, client);

    return taskCompletionSource.Task;
}

Também temos de subscrever eventos spot e processá-los. No exemplo abaixo, acedemos à coleção symbols que armazena os nossos símbolos leves. Encontramos o objeto de símbolo cujo ID corresponde ao campo symbolId do ProtoOASpotEvent que acabámos de receber. Finalmente, atualizamos as propriedades bid e ask do symbol usando o método auxiliar GetPriceFromRelative.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
client.OfType<ProtoOASpotEvent>().Subscribe(OnSpotEvent);

private void OnSpotEvent(ProtoOASpotEvent spotEvent) 
{
    var symbol = symbols.FirstOrDefault(iSymbol => iSymbol.Id == spotEvent.SymbolId);

    double bid;
    double ask;

    if (spotEvent.HasBid) bid = symbol.Data.GetPriceFromRelative((long)spotEvent.Bid);
    if (spotEvent.HasAsk) ask = symbol.Data.GetPriceFromRelative((long)spotEvent.Ask);

    if (bid != symbol.Bid) 
    {
        symbol.Bid = bid;
        RaisePropertyChanged(nameof(Bid));
    }

    if (ask != symbol.Ask) 
    {
        symbol.Ask = ask
        RaisePropertyChanged(nameof(Ask));
    }

}

Podemos usar a seguinte função para subscrever o ProtoOASpotEvent para um símbolo específico. Note que apenas subscreve um símbolo, o que significa que precisaria de chamar esta função para cada símbolo leve na sua coleção de símbolos de conversão.

1
2
3
4
5
6
7
def sendProtoOASubscribeSpotsReq(symbolId, subscribeToSpotTimestamp = False, clientMsgId = None):
    request = ProtoOASubscribeSpotsReq()
    request.ctidTraderAccountId = currentAccountId
    request.symbolId.append(int(symbolId))
    request.subscribeToSpotTimestamp = subscribeToSpotTimestamp if type(subscribeToSpotTimestamp) is bool else bool(subscribeToSpotTimestamp)
    deferred = client.send(request, clientMsgId = clientMsgId)
    deferred.addErrback(onError)

Para processar eventos spot, podemos adicionar as seguintes condições ao nosso callback onMessageReceived.

1
2
3
elif message.paloadType == ProtoOASpotEvent().payloadType:
    ProtoOASpotEvent = Protobuf.extract(message)
    onSpotEvent(ProtoOASpotEvent)

Finalmente, aqui está o nosso callback onSpotEvent.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
def onSpotEvent(ProtoOASpotEvent):
    iterableConversionSymbols = iter(conversionSymbols)
    symbol = next(s for s in iterableConversionSymbols if s.symbolId == ProtoOASpotEvent.symbolId)

    if symbol is None:
        return

    if ProtoOASpotEvent.hasBid == true:
        bid = symbol.data.getPipsFromRelative()
    if ProtoOASpotEvent.hasAsk == true:
        ask = symbol.data.getPipsFromRelative()

    if bid != symbol.bid:
        symbol.bid = bid
    if ask != symbol.ask
        symbol.ask = ask

Na nossa classe Symbol, a função getPipsFromRelative é definida da seguinte forma:

1
2
3
4
    import math

    def getPipsFromRelative(self, relative):
    return round((relative / 100000.0) / symbol.pipPosition, symbol.digits - symbol.pipPosition)

Note que os valores das propriedades pipPosition e digits têm de ser obtidos separadamente.

Seguir a cadeia de conversão e retornar a taxa de conversão final

Neste ponto, todos os símbolos na sua cadeia de conversão devem receber taxas spot e deve poder aceder a preços de venda e compra precisos para cada símbolo. Tudo o que resta é percorrer a cadeia de conversão e retornar a taxa final. Isto pode ser feito da seguinte forma:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private double GetConversionRate(List<Symbol> conversionSymbols) 
{
    double conversionRate = 1;

    var currentAsset = conversionSymbols.First().BaseAsset;

    foreach (var symbol in conversionSymbols)
    {
        var closePrice = symbol.Bid; //Replaced by symbol.Ask when calculating P&Ls for short positions

        if (symbol.BaseAsset == currentAsset)
        {
            conversionRate = conversionRate * closePrice;
            currentAsset = symbol.QuoteAsset;
        }
        else
        {
            conversionRate = conversionRate * 1 / closePrice;
            currentAsset = symbol.BaseAsset;
        }
    }

    return conversionRate;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def getConversionRate(conversionSymbols):
    conversionRate = 1
    currentAsset = conversionSymbols[0].baseAsset

    for symbol in conversionSymbols:
        closePrice = symbol.bid #Replaced by symbol.ask when calculating P&L for short positions

        if symbol.baseAsset == currentAsset:
            conversionRate = conversionRate * closePrice
            currentAsset = symbol.quoteAsset

        else:
            conversionRate = conversionRate * 1 / closePrice
            currentAsset = symbol.baseAsset