コンテンツにスキップ

シンボルレート変換

損益計算に関連する機能をアプリケーションに含めたい場合、シンボルレート変換は必須のアクションです。

ユースケース

手数料、スワップ、および配当を計算する際に、シンボルレート変換を実行する必要があります。 手数料、スワップ、および配当は通常USDで表示されます。 ただし、ブローカーと設定によっては、アカウントの入金通貨がUSD以外の資産である場合があります。 手数料、スワップ、および配当を正確に定義するには、USDとアカウントの入金通貨間の為替レートを知る必要があります。

変換チェーン

各シンボルの価格を、その基本資産とクォート資産間の変換レートとして考えてください。 ある資産から別の資産に直接リンクするシンボルがない場合、変換レートを取得するタスクは複雑になります。 幸い、cTraderのバックエンドは、ある資産から別の資産への最短の変換パスとして機能する変換チェーンを自動的に作成できます。

例えば、EURをNZDに変換したいが、取引サーバーにEURNZDシンボルがない場合、cTraderは変換プロセスの中間ステップとして機能するチェーンを提案します。 シンボルの可用性に応じて、そのようなチェーンはEURUSD-USDCAD-CADNZD、EURCFH-CFHUSD-USDNZD、またはEURCAD-CADUSD-USDCFH-CFHNZDのようになる可能性があります。

2つの資産間で変換するには、アプリは次のアクションを実行する必要があります。

  • 変換したい資産のIDにアクセスします。
  • 1つ以上のライトシンボルを含む変換チェーンを取得します。
  • スポットイベントを購読し、処理します。
  • 変換チェーンを追跡し、最終的な変換レートを返します。

以下では、これらのアクションについて詳しく説明します。

アセットIDへのアクセス

アセットIDは、ライトシンボルエンティティ(ProtoOALightSymbolモデルメッセージで表される)のプロパティとして直接アクセスできます。 さらに、関連するProtoOaTraderエンティティからアカウントのデポジット通貨のIDを直接取得できます。

以下の例では、1つのシンボルのアセットIDのみを取得していることに注意してください。 複数のシンボルに対して同じ操作を実行するには、ループを追加するか、別の適切なソリューション(例えば、map()メソッドまたは同等のもの)を使用する必要があります。

JSONでの作業

JSONで作業する場合でも、以下のコードやこのチュートリアルの他のコードスニペットを再利用できます。 ただし、Proto...クラスの名前をカスタムクラスの名前に置き換え、好みのTCPおよびWebSocketクライアントに対応するために必要な変更を行ってください。

symbolProtoOALightSymbol型の変数です。 JSONを使用している場合、ProtoOALightSymbolメッセージを表す任意のカスタム型にすることができます。

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

以下の例では、ProtoOATrader型のaccountオブジェクトのデポジット通貨のIDを取得します。

1
int depositAssetId = account.DepositAssetId;

symbolProtoOALightSymbol型のメッセージを表す変数です。

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

以下の例では、ProtoOATraderモデルメッセージを表すaccountオブジェクトのデポジット通貨のIDを取得します。

1
depositAssetId = account.depositAssetId

変換チェーンの取得

有効な変換チェーンを取得するには、アプリがProtoOASymbolsForConversionReqメッセージを送信し、firstAssetIdlastAssetIdフィールドを変換したいアセットのIDに設定する必要があります。 ProtoOASymbolsForConversionRes型の応答を受け取ったら、その繰り返しsymbolフィールドをコレクション内に保存します。

公式SDKを使用してこのアクションを実行する方法は以下の通りです。

 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

onMessageReceivedコールバックに以下の条件も追加してください。

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

注意

サーバーからアセットデータを初めてリクエストして受け取ったときに、各アセットのすべての変換チェーンシンボルを取得することを強くお勧めします。 そうしないと、レートを変換する必要があるたびにProtoOASymbolsForConversionReqメッセージを送信しなければならず、場合によっては1秒間に数回送信する必要があるかもしれません。

注意

ProtoOALightSymbolエンティティには売値や買値を表すフィールドが含まれていないため、カスタムSymbolクラスまたは同等のものを作成し、プログラムで新しいシンボルを簡単に作成し、特定のイベントがトリガーされたときにそのプロパティを更新することを強くお勧めします。 ライトシンボルをこのクラスのオブジェクトに変換する必要があります。

スポットイベントの購読と処理

変換チェーンを最初から最後まで追跡するには、チェーンに含まれるすべてのシンボルのProtoOASpotEventを購読する必要があります。 公式SDKを使用して以下のように行います。

以下の操作を実行する前に、Symbolクラスまたは同等のものを作成し、プログラムで新しいシンボルを簡単に作成し、特定のイベントがトリガーされたときにそのプロパティを更新できるようにします。 以下の例では、SymbolクラスにProtoOASymbolおよびProtoOALightSymbolオブジェクトがプロパティとして含まれています。

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

また、スポットイベントを購読し、それらを処理する必要があります。 以下の例では、ライトシンボルを保存するsymbolsコレクションにアクセスします。 受け取ったProtoOASpotEventsymbolIdフィールドと一致するIDを持つシンボルオブジェクトを見つけます。 最後に、GetPriceFromRelativeヘルパーメソッドを使用してsymbolbidおよびaskプロパティを更新します。

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

}

特定のシンボルのProtoOASpotEventを購読するために、以下の関数を使用できます。 これは1つのシンボルのみを購読するため、変換シンボルのコレクション内のすべてのライトシンボルに対してこの関数を呼び出す必要があります。

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)

スポットイベントを処理するために、onMessageReceivedコールバックに以下の条件を追加できます。

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

最後に、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

Symbolクラスでは、getPipsFromRelative関数は以下のように定義されています:

1
2
3
4
    import math

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

pipPositionおよびdigitsプロパティの値は別途取得する必要があることに注意してください。

変換チェーンを追跡し、最終的な変換レートを返す

この時点で、変換チェーン内のすべてのシンボルがスポットレートを受け取り、各シンボルの正確な売値と買値にアクセスできるはずです。 あとは変換チェーンをたどり、最終的なレートを返すだけです。 これは以下のように行うことができます:

 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