Zum Inhalt

Symbolkursumrechnung

Die Symbolkursumrechnung ist eine wesentliche Aktion, wenn Ihre Anwendung Funktionen im Zusammenhang mit GuV-Berechnungen umfassen soll.

Anwendungsfälle

Die Durchführung von Symbolkursumrechnungen ist bei der Berechnung von Provisionen, Swaps und Dividenden erforderlich. Provisionen, Swaps und Dividenden werden in der Regel in USD angegeben. Je nach Broker und Ihren Präferenzen kann die Einzahlungswährung Ihres Kontos jedoch ein anderes Asset als USD sein. Um Provisionen, Swaps und Dividenden korrekt zu bestimmen, müssten Sie den Wechselkurs zwischen USD und der Einzahlungswährung Ihres Kontos kennen.

Umrechnungsketten

Betrachten Sie den Kurs jedes einzelnen Symbols als Umrechnungskurs zwischen seinem Basiswert und seinem Kurswert. Die Aufgabe, Umrechnungskurse zu ermitteln, wird kompliziert, wenn es keine Symbole gibt, die ein bestimmtes Asset direkt mit einem anderen Asset verknüpfen. Glücklicherweise kann das cTrader-Backend automatisch Umrechnungsketten erstellen, die als kürzestmögliche Umrechnungspfade von einem Asset zu einem anderen fungieren.

Wenn Sie beispielsweise EUR in NZD umrechnen möchten und es kein EURNZD-Symbol auf dem Handelsserver gibt, schlägt cTrader eine Kette vor, die als Zwischenschritte im Umrechnungsprozess dient. Je nach Verfügbarkeit der Symbole könnte eine solche Kette wie folgt aussehen: EURUSD-USDCAD-CADNZD, EURCFH-CFHUSD-USDNZD oder sogar EURCAD-CADUSD-USDCFH-CFHNZD.

Um zwischen zwei Assets umzurechnen, muss Ihre App die folgenden Aktionen ausführen:

  • Zugriff auf die IDs der Assets, zwischen denen Sie umrechnen möchten.
  • Ermittlung einer Umrechnungskette, die ein oder mehrere Light-Symbole enthält.
  • Abonnieren und Verarbeiten von Spot-Ereignissen.
  • Der Umrechnungskette folgen und den endgültigen Umrechnungskurs zurückgeben.

Im Folgenden erläutern wir jede dieser Aktionen im Detail.

Zugriff auf Asset-IDs

Auf Asset-IDs kann direkt als Eigenschaften der Light-Symbol-Entität zugegriffen werden (dargestellt durch die ProtoOALightSymbol-Modellnachricht). Zusätzlich können Sie die ID einer Einzahlungswährung eines Kontos direkt von der zugehörigen ProtoOaTrader-Entität abrufen.

Beachten Sie, dass das Beispiel unten Asset-IDs nur für ein Symbol abruft. Um denselben Vorgang für mehrere Symbole durchzuführen, müssten Sie eine Schleife hinzufügen oder eine andere geeignete Lösung verwenden (zum Beispiel eine map()-Methode oder ein Äquivalent).

Arbeiten mit JSON

Wenn Sie mit JSON arbeiten, können Sie den unten stehenden Code sowie andere Codeausschnitte in diesem Tutorial weiterhin verwenden. Stellen Sie jedoch sicher, dass Sie die Namen der Proto...-Klassen durch die Namen Ihrer benutzerdefinierten Klassen ersetzen und die notwendigen Anpassungen vornehmen, um Ihren bevorzugten TCP- und WebSocket-Client zu berücksichtigen.

symbol ist eine Variable des Typs ProtoOALightSymbol. Wenn Sie JSON verwenden, kann sie von einem beliebigen benutzerdefinierten Typ sein, der die ProtoOALightSymbol-Nachricht darstellt.

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

Im Beispiel unten rufen wir die ID der Einzahlungswährung des account-Objekts ab, das vom Typ ProtoOATrader ist.

1
int depositAssetId = account.DepositAssetId;

symbol ist eine Variable, die eine Nachricht des Typs ProtoOALightSymbol darstellt.

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

Im Beispiel unten rufen wir die ID der Einzahlungswährung des account-Objekts ab, das die ProtoOATrader-Modellnachricht darstellt.

1
depositAssetId = account.depositAssetId

Ermittlung einer Umrechnungskette

Um eine gültige Umrechnungskette zu ermitteln, muss Ihre App die ProtoOASymbolsForConversionReq-Nachricht senden, wobei die Felder firstAssetId und lastAssetId auf die IDs der Assets gesetzt werden, zwischen denen Sie umrechnen möchten. Wenn Sie eine Antwort des Typs ProtoOASymbolsForConversionRes erhalten, speichern Sie deren wiederholtes Feld symbol in einer Sammlung.

Hier erfahren Sie, wie Sie diese Aktion über die offiziellen SDKs durchführen können.

 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

Fügen Sie außerdem die folgende Bedingung zum onMessageReceived-Callback hinzu.

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

Hinweis

Wir empfehlen dringend, alle Symbole der Umrechnungskette für jedes Asset abzurufen, wenn Sie zum ersten Mal Asset-Daten vom Server anfordern und empfangen. Andernfalls müssten Sie die ProtoOASymbolsForConversionReq-Nachricht jedes Mal senden, wenn Sie Kurse umrechnen müssen, was in manchen Fällen mehrmals pro Sekunde vorkommen kann.

Hinweis

Da ProtoOALightSymbol-Entitäten keine Felder enthalten, die Geld- oder Briefkurse darstellen, empfehlen wir dringend, benutzerdefinierte Symbol-Klassen oder Äquivalente zu erstellen, damit Sie problemlos neue Symbole programmatisch erstellen und deren Eigenschaften aktualisieren können, wenn bestimmte Ereignisse ausgelöst werden. Sie müssen Light-Symbole separat in Objekte dieser Klasse umwandeln.

Spot-Events abonnieren und verarbeiten

Um die Umrechnungskette von Anfang bis Ende zu verfolgen, müssen Sie das ProtoOASpotEvent für alle Symbole abonnieren, die in der Kette enthalten sind. Dies erfolgt über die offiziellen SDKs wie folgt.

Bevor Sie die unten aufgeführten Operationen durchführen, erstellen Sie eine Symbol-Klasse oder ein Äquivalent, damit Sie einfach neue Symbole programmgesteuert erstellen und deren Eigenschaften aktualisieren können, wenn bestimmte Ereignisse ausgelöst werden. In den folgenden Beispielen enthält unsere Symbol-Klasse die Objekte ProtoOASymbol und ProtoOALightSymbol als Eigenschaften.

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

Wir müssen außerdem Spot-Events abonnieren und diese verarbeiten. Im folgenden Beispiel greifen wir auf die symbols-Sammlung zu, in der unsere Light-Symbole gespeichert sind. Wir suchen das Symbolobjekt, dessen ID mit dem Feld symbolId des soeben empfangenen ProtoOASpotEvent übereinstimmt. Abschließend aktualisieren wir die Eigenschaften bid und ask des symbol mithilfe der Hilfsmethode 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));
    }

}

Wir können die folgende Funktion verwenden, um ProtoOASpotEvent für ein bestimmtes Symbol zu abonnieren. Beachten Sie, dass sie nur ein Symbol abonniert, was bedeutet, dass Sie diese Funktion für jedes Light-Symbol in Ihrer Sammlung von Umrechnungssymbolen aufrufen müssten.

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)

Um Spot-Events zu verarbeiten, können wir unserem onMessageReceived-Callback die folgenden Bedingungen hinzufügen.

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

Abschließend folgt unser onSpotEvent-Callback.

 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

In unserer Symbol-Klasse ist die Funktion getPipsFromRelative wie folgt definiert:

1
2
3
4
    import math

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

Beachten Sie, dass die Werte der Eigenschaften pipPosition und digits separat ermittelt werden müssen.

Der Umrechnungskette folgen und den endgültigen Umrechnungskurs zurückgeben

An diesem Punkt sollten alle Symbole in Ihrer Umrechnungskette Spot-Kurse erhalten, und Sie sollten in der Lage sein, auf genaue Geld- und Briefkurse für jedes Symbol zuzugreifen. Es bleibt nur noch, die Umrechnungskette zu durchlaufen und den endgültigen Kurs zurückzugeben. Dies kann wie folgt erfolgen:

 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