Vai al contenuto

Conversione del tasso del simbolo

La conversione del tasso del simbolo è un'azione essenziale se vuoi che la tua applicazione includa funzionalità relative ai calcoli del P&L.

Casi d'uso

Eseguire conversioni del tasso del simbolo è necessario quando si calcolano commissioni, swap e dividendi. Commissioni, swap e dividendi sono tipicamente espressi in USD. Tuttavia, a seconda del tuo broker e delle tue preferenze, la valuta di deposito del tuo conto potrebbe essere in qualsiasi altro asset diverso dall'USD. Per definire accuratamente commissioni, swap e dividendi, dovresti conoscere il tasso di cambio tra USD e la valuta di deposito del tuo conto.

Catene di conversione

Considera il prezzo di ogni singolo simbolo come un tasso di conversione tra il suo asset di base e l'asset di quotazione. Il compito di ottenere i tassi di conversione diventa complicato se non ci sono simboli che collegano direttamente un dato asset a un altro asset. Fortunatamente, il backend di cTrader può creare automaticamente catene di conversione che fungono da percorsi di conversione più brevi possibili da un asset all'altro.

Ad esempio, se vuoi convertire EUR in NZD, e non c'è un simbolo EURNZD sul server di trading, cTrader proporrà una catena che servirà come passaggi intermedi nel processo di conversione. A seconda della disponibilità dei simboli, una tale catena potrebbe apparire come EURUSD-USDCAD-CADNZD, EURCFH-CFHUSD-USDNZD, o persino EURCAD-CADUSD-USDCFH-CFHNZD.

Per convertire tra due asset, la tua app deve eseguire le seguenti azioni:

  • Accedere agli ID degli asset che vuoi convertire.
  • Ottenere una catena di conversione che includa uno o più simboli leggeri.
  • Iscriversi e gestire gli eventi spot.
  • Seguire la catena di conversione e restituire il tasso di conversione finale.

Di seguito, spieghiamo in dettaglio ciascuna di queste azioni.

Accedere agli ID degli asset

Gli ID degli asset possono essere accessibili direttamente come proprietà dell'entità del simbolo leggero (rappresentata dal messaggio modello ProtoOALightSymbol). Inoltre, puoi ottenere l'ID di una valuta di deposito del conto direttamente dalla sua relativa entità ProtoOaTrader.

Nota che l'esempio seguente ottiene gli ID degli asset solo per un simbolo. Per eseguire la stessa operazione per più simboli, dovresti aggiungere un ciclo o utilizzare un'altra soluzione adatta (ad esempio, un metodo map() o equivalente).

Lavorare con JSON

Quando lavori con JSON, puoi comunque riutilizzare il codice sottostante e altri frammenti di codice in questo tutorial. Tuttavia, assicurati di sostituire i nomi delle classi Proto... con i nomi delle tue classi personalizzate e apporta le modifiche necessarie per tenere conto del tuo client TCP e WebSocket preferito.

symbol è una variabile di tipo ProtoOALightSymbol. Se stai utilizzando JSON, può essere di qualsiasi tipo personalizzato che rappresenti il messaggio ProtoOALightSymbol.

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

Nell'esempio seguente, otteniamo l'ID della valuta di deposito dell'oggetto account che è di tipo ProtoOATrader.

1
int depositAssetId = account.DepositAssetId;

symbol è una variabile che rappresenta un messaggio di tipo ProtoOALightSymbol.

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

Nell'esempio seguente, otteniamo l'ID della valuta di deposito dell'oggetto account che rappresenta il messaggio modello ProtoOATrader.

1
depositAssetId = account.depositAssetId

Ottenere una catena di conversione

Per ottenere una catena di conversione valida, la tua app deve inviare il messaggio ProtoOASymbolsForConversionReq con i suoi campi firstAssetId e lastAssetId impostati sugli ID degli asset che vuoi convertire. Quando ricevi una risposta di tipo ProtoOASymbolsForConversionRes, memorizza il suo campo symbol ripetuto all'interno di una collezione.

Ecco come puoi eseguire questa azione tramite gli SDK ufficiali.

 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

Aggiungi anche la seguente condizione al callback onMessageReceived.

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

Nota

Raccomandiamo vivamente di ottenere tutti i simboli della catena di conversione per ogni asset quando richiedi e ricevi per la prima volta i dati degli asset dal server. Altrimenti, dovrai inviare il messaggio ProtoOASymbolsForConversionReq ogni volta che hai bisogno di convertire i tassi, che in alcuni casi potrebbe essere diverse volte al secondo.

Nota

Poiché le entità ProtoOALightSymbol non contengono campi che rappresentano i prezzi bid o ask, raccomandiamo vivamente di creare classi Symbol personalizzate o equivalenti in modo da poter facilmente creare nuovi simboli programmaticamente e aggiornare le loro proprietà quando vengono attivati determinati eventi. Dovrai convertire i simboli leggeri in oggetti di questa classe separatamente.

Iscriversi e gestire gli eventi spot

Per seguire la catena di conversione dall'inizio alla fine, devi iscriverti al ProtoOASpotEvent per tutti i simboli inclusi nella catena. Questo viene fatto come segue tramite gli SDK ufficiali.

Prima di eseguire le operazioni elencate di seguito, crea una classe Symbol o equivalente in modo da poter facilmente creare nuovi simboli programmaticamente e aggiornare le loro proprietà quando vengono attivati determinati eventi. Negli esempi seguenti, la nostra classe Symbol include oggetti ProtoOASymbol e ProtoOALightSymbol come proprietà.

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

Dobbiamo anche iscriverci agli eventi spot e gestirli. Nell'esempio seguente, accediamo alla collezione symbols che memorizza i nostri simboli leggeri. Troviamo l'oggetto simbolo il cui ID corrisponde al campo symbolId del ProtoOASpotEvent che abbiamo appena ricevuto. Infine, aggiorniamo le proprietà bid e ask del symbol utilizzando il metodo helper 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));
    }

}

Possiamo utilizzare la seguente funzione per iscriverci al ProtoOASpotEvent per un particolare simbolo. Nota che si iscrive solo a un simbolo, il che significa che dovresti chiamare questa funzione per ogni simbolo leggero nella tua collezione di simboli di conversione.

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)

Per gestire gli eventi spot, possiamo aggiungere le seguenti condizioni al nostro callback onMessageReceived.

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

Infine, ecco il nostro 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

Nella nostra classe Symbol, la funzione getPipsFromRelative è definita come segue:

1
2
3
4
    import math

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

Nota che i valori delle proprietà pipPosition e digits devono essere ottenuti separatamente.

Seguire la catena di conversione e restituire il tasso di conversione finale

A questo punto, tutti i simboli nella tua catena di conversione dovrebbero ricevere tassi spot e dovresti essere in grado di accedere a prezzi bid e ask accurati per ogni simbolo. Tutto ciò che resta è percorrere la catena di conversione e restituire il tasso finale. Questo può essere fatto come segue:

 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