Saltar a contenido

Conversión de tasa de símbolo

La conversión de tasa de símbolo es una acción esencial si desea que su aplicación incluya funcionalidad relacionada con cálculos de P&L.

Casos de uso

Es necesario realizar conversiones de tasa de símbolo al calcular comisiones, swaps y dividendos. Las comisiones, swaps y dividendos generalmente se expresan en USD. Sin embargo, dependiendo de su bróker y sus preferencias, la moneda de depósito de su cuenta puede estar en cualquier otro activo que no sea USD. Para definir con precisión las comisiones, swaps y dividendos, necesitaría conocer el tipo de cambio entre USD y la moneda de depósito de su cuenta.

Cadenas de conversión

Piense en el precio de cada símbolo individual como una tasa de conversión entre su activo base y activo de cotización. La tarea de obtener tasas de conversión se vuelve complicada si no hay símbolos que vinculen directamente un activo dado con otro activo. Afortunadamente, el backend de cTrader puede crear automáticamente cadenas de conversión que actúan como las rutas de conversión más cortas posibles de un activo a otro.

Por ejemplo, si desea convertir EUR a NZD, y no hay un símbolo EURNZD en el servidor de operaciones, cTrader propondrá una cadena que servirá como pasos intermedios en el proceso de conversión. Dependiendo de la disponibilidad de símbolos, dicha cadena podría verse como EURUSD-USDCAD-CADNZD, EURCFH-CFHUSD-USDNZD, o incluso EURCAD-CADUSD-USDCFH-CFHNZD.

Para convertir entre dos activos, su aplicación necesita realizar las siguientes acciones:

  • Acceder a los ID de los activos que desea convertir.
  • Obtener una cadena de conversión que incluya uno o más símbolos ligeros.
  • Suscribirse y manejar eventos spot.
  • Seguir la cadena de conversión y devolver la tasa de conversión final.

A continuación, explicamos cada una de estas acciones en detalle.

Acceder a los ID de activos

Se puede acceder a los ID de activos directamente como propiedades de la entidad de símbolo ligero (representada por el mensaje modelo ProtoOALightSymbol). Además, puede obtener el ID de una moneda de depósito de cuenta directamente de su entidad ProtoOaTrader relacionada.

Tenga en cuenta que el ejemplo siguiente solo obtiene ID de activos para un símbolo. Para realizar la misma operación para múltiples símbolos, necesitaría agregar un bucle o usar otra solución adecuada (por ejemplo, un método map() o equivalente).

Trabajar con JSON

Al trabajar con JSON, puede seguir reutilizando el código a continuación y otros fragmentos de código de este tutorial. Sin embargo, asegúrese de reemplazar los nombres de las clases Proto... con los nombres de sus clases personalizadas y realice las modificaciones necesarias para adaptarse a su cliente TCP y WebSocket preferido.

symbol es una variable del tipo ProtoOALightSymbol. Si está usando JSON, puede ser de cualquier tipo personalizado que represente el mensaje ProtoOALightSymbol.

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

En el ejemplo a continuación, obtenemos el ID de la moneda de depósito del objeto account que es del tipo ProtoOATrader.

1
int depositAssetId = account.DepositAssetId;

symbol es una variable que representa un mensaje del tipo ProtoOALightSymbol.

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

En el ejemplo a continuación, obtenemos el ID de la moneda de depósito del objeto account que representa el mensaje del modelo ProtoOATrader.

1
depositAssetId = account.depositAssetId

Obtener una cadena de conversión

Para obtener una cadena de conversión válida, su aplicación necesita enviar el mensaje ProtoOASymbolsForConversionReq con sus campos firstAssetId y lastAssetId configurados con los ID de los activos entre los que desea convertir. Cuando reciba una respuesta del tipo ProtoOASymbolsForConversionRes, almacene su campo symbol repetido dentro de una colección.

Así es como puede realizar esta acción a través de los SDK oficiales.

 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

También agregue la siguiente condición al callback onMessageReceived.

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

Nota

Recomendamos encarecidamente obtener todos los símbolos de la cadena de conversión para cada activo cuando solicite y reciba datos de activos del servidor por primera vez. De lo contrario, tendrá que enviar el mensaje ProtoOASymbolsForConversionReq cada vez que necesite convertir tasas, lo que en algunos casos puede ser varias veces por segundo.

Nota

Como las entidades ProtoOALightSymbol no contienen campos que representen precios de oferta o demanda, recomendamos encarecidamente crear clases Symbol personalizadas o equivalentes para que pueda crear fácilmente nuevos símbolos programáticamente y actualizar sus propiedades cuando se activen ciertos eventos. Tendrá que convertir los símbolos ligeros en objetos de esta clase por separado.

Suscribirse y manejar eventos spot

Para seguir la cadena de conversión de principio a fin, necesita suscribirse al ProtoOASpotEvent para todos los símbolos incluidos en la cadena. Esto se hace de la siguiente manera a través de los SDK oficiales.

Antes de realizar las operaciones enumeradas a continuación, cree una clase Symbol o equivalente para que pueda crear fácilmente nuevos símbolos programáticamente y actualizar sus propiedades cuando se activen ciertos eventos. En los ejemplos a continuación, nuestra clase Symbol incluye objetos ProtoOASymbol y ProtoOALightSymbol como propiedades.

 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;
}

También tenemos que suscribirnos a eventos spot y manejarlos. En el ejemplo a continuación, accedemos a la colección symbols que almacena nuestros símbolos ligeros. Encontramos el objeto símbolo cuyo ID coincide con el campo symbolId del ProtoOASpotEvent que acabamos de recibir. Finalmente, actualizamos las propiedades bid y ask del symbol usando el 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 la siguiente función para suscribirnos al ProtoOASpotEvent para un símbolo particular. Tenga en cuenta que solo se suscribe a un símbolo, lo que significa que necesitaría llamar a esta función para cada símbolo ligero en su colección de símbolos de conversión.

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 manejar eventos spot, podemos agregar las siguientes condiciones a nuestro callback onMessageReceived.

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

Finalmente, aquí está nuestro 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

En nuestra clase Symbol, la función getPipsFromRelative se define de la siguiente manera:

1
2
3
4
    import math

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

Tenga en cuenta que los valores de las propiedades pipPosition y digits deben obtenerse por separado.

Seguir la cadena de conversión y devolver la tasa de conversión final

En este punto, todos los símbolos en su cadena de conversión deberían recibir tasas spot y debería poder acceder a precios de oferta y demanda precisos para cada símbolo. Todo lo que queda es recorrer la cadena de conversión y devolver la tasa final. Esto se puede hacer de la siguiente manera:

 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