Symbol rate conversionは、P&L計算に関連するあらゆる機能を含めるために、アプリケーションに組み込む必要がある重要なアクションです。
使用例
手数料、スワップ、および配当を計算する際には、通常、USDで表されます。ただし、ブローカーや個人の設定によっては、口座の入金通貨がUSD以外の資産である場合があります。手数料、スワップ、および配当を正確に定義するには、USDと口座の入金通貨の間の為替レートを知る必要があります。
変換チェーン
各個別のシンボルの価格を、そのベースアセットとクォートアセットとの間の変換レートと考えてください。特定の資産を別の資産に直接リンクするシンボルがない場合、変換レートを取得するタスクは複雑になります。幸いなことに、cTraderバックエンドは、1つの資産から別の資産への最短の変換パスとして機能する変換チェーンを自動的に作成できます。
たとえば、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クライアントに対応するように必要な修正を行ってください。
symbol
はProtoOALightSymbol
タイプの変数です。JSONを使用している場合、それはProtoOALightSymbol
メッセージを表す任意のカスタムタイプです。
int baseAssetId = symbol.BaseAsset.AssetId;
int quoteAssetId = symbol.QuoteAsset.AssetId;
以下の例では、account
オブジェクトの預入通貨のIDを取得します。このaccount
オブジェクトはProtoOATrader
タイプです。
int depositAssetId = account.DepositAssetId;
symbol
は ProtoOALightSymbol
タイプのメッセージを表す変数です。
baseAssetId = symbol.BaseAsset.AssetId
quoteAssetId = symbol.QuoteAsset.AssetId
以下の例では、account
オブジェクトから預金通貨の ID を取得します。この account
オブジェクトは ProtoOATrader
モデルメッセージを表します。
depositAssetId = account.depositAssetId
変換チェーンの取得
有効な変換チェーンを取得するには、アプリケーションは、firstAssetId
と lastAssetId
フィールドを対象の資産の ID に設定して、ProtoOASymbolsForConversionReq
メッセージ を送信する必要があります。 ProtoOASymbolsForConversionRes
タイプの応答を受信したら、その中の symbol
フィールドをコレクションに格納します。
以下は、公式 SDK を使用してこのアクションを実行する方法です。
List lightSymbolsForConversion = new List();
var symbolsResult = await GetConversionSymbols(accountId, isLive, baseAssetId, quoteAssetId);
lightSymbolsForConversion.AddRange(symbolsResult);
public Task GetConversionSymbols(long accountId, bool isLive, long baseAssetId, long quoteAssetId)
{
VerifyConnection();
var client = GetClient(isLive);
var taskCompletionSource = new TaskCompletionSource();
IDisposable disposable = null;
disposable = client.OfType().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;
}
def sendProtoOASymbolsForConversionReq(accountId, firstAssetId, lastAssetId):
request = ProtoOASymbolsForConversionReq()
request.ctidTraderAccountId = accountId
request.firstAssedId = firstAssetId
request.lastAssetId = lastAssetId
onMessageReceived
コールバックに以下の条件を追加してください。
elif message.payloadType == ProtoOASymbolsForConversionRes().payloadType:
ProtoOASymbolsForConversionRes = Protobuf.extract(message)
conversionSymbols = ProtoOASymbolsForConversionRes.symbol
Note
サーバーからアセットデータを最初に要求して受信した際に、各アセットのすべての変換チェーンシンボルを取得することを強くお勧めします。そうしないと、レート変換が必要なたびにProtoOASymbolsForConversionReq
メッセージを送信する必要があります。これは場合によっては1秒に数回行われることがあります。
Note
ProtoOALightSymbol
のエンティティには入札価格や要求価格を表すフィールドが含まれていないため、カスタムのSymbol
クラスまたは同等のクラスを作成し、トリガーされた特定のイベントでプロパティを簡単に更新できるようにすることを強くお勧めします。ライトシンボルをこのクラスのオブジェクトに変換する必要があります。
Spot イベントの購読と処理
開始から終了までの変換チェーンをフォローするには、チェーンに含まれるすべてのシンボルに対してProtoOASpotEvent
を購読する必要があります。公式の SDK を使用して、これは次のように行います。
以下の操作を実行する前に、特定のイベントが発生したときに簡単に新しいシンボルをプログラムで作成し、そのプロパティを更新できるように、Symbol
クラスまたはそれに相当するものを作成してください。以下の例では、Symbol
クラスにProtoOASymbol
およびProtoOALightSymbol
オブジェクトをプロパティとして含めています。
public Task SubscribeToSpots(long accountId, bool isLive, params long[] symbolIds)
{
var client = GetClient(isLive);
var taskCompletionSource = new TaskCompletionSource();
IDisposable disposable = null;
disposable = client.OfType().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
コレクションにアクセスします。受信したProtoOASpotEvent
のsymbolId
フィールドと一致するシンボルオブジェクトを見つけます。最後に、GetPriceFromRelative
ヘルパーメソッドを使用して、symbol
のbid
およびask
プロパティを更新します。
client.OfType().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つのシンボルにのみ購読するため、変換シンボルのコレクション内のすべてのライトシンボルに対してこの関数を呼び出す必要があります。
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
コールバックに追加できます。
elif message.paloadType == ProtoOASpotEvent().payloadType:
ProtoOASpotEvent = Protobuf.extract(message)
onSpotEvent(ProtoOASpotEvent)
最後に、以下がonSpotEvent
コールバックです。
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
関数が次のように定義されています。
import math
def getPipsFromRelative(self, relative): return round((relative / 100000.0) / symbol.pipPosition, symbol.digits – symbol.pipPosition) “`
pipPosition
およびdigits
プロパティの値は、個別に取得する必要があることに注意してください。
変換チェーンに従って最終的な変換レートを返す
この時点で、変換チェーン内のすべてのシンボルがスポットレートを受信し、各シンボルの正確な売値と買値にアクセスできるはずです。残されたことは、変換チェーンを ‘移動’ して最終的なレートを返すだけです。これは以下のように行うことができます。
private double GetConversionRate(List 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;
}
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