跳转至

符号汇率转换

符号汇率转换是如果您的应用程序包含与盈亏计算相关的功能时必不可少的操作。

用例

执行符号汇率转换在计算佣金、掉期和分红时是必要的。 佣金、掉期和分红通常以美元表示。 然而,根据您的经纪商和您的偏好,您的账户入金币种可能是任何其他资产而非美元。 要准确定义佣金、掉期和分红,您需要知道美元和您的账户入金币种之间的汇率。

转换链

将每个符号的价格视为其基础资产和报价资产之间的转换率。 如果没有任何符号直接将给定资产链接到另一个资产,则获取转换率的任务将变得复杂。 幸运的是,cTrader后端可以自动创建转换链,这些链充当从一个资产到另一个资产的最短可能转换路径。

例如,如果您想将欧元转换为新西兰元,并且交易服务器上没有EURNZD符号,cTrader将提出一个链,作为转换过程中的中间步骤。 根据符号的可用性,这样的链可能看起来像EURUSD-USDCAD-CADNZD、EURCFH-CFHUSD-USDNZD,甚至EURCAD-CADUSD-USDCFH-CFHNZD。

要在两个资产之间进行转换,您的应用程序需要执行以下操作:

  • 访问您要转换的资产的ID。
  • 获取包含一个或多个轻量符号的转换链。
  • 订阅并处理现货事件。
  • 遵循转换链并返回最终转换率。

下面,我们将详细解释这些操作。

访问资产 ID

资产 ID 可以直接作为轻量符号实体的属性访问(由 ProtoOALightSymbol 模型消息 表示)。 此外,您可以直接从其相关的 ProtoOaTrader 实体 获取账户存款货币的 ID。

请注意,以下示例仅获取一个符号的资产 ID。 要对多个符号执行相同的操作,您需要添加循环或使用其他合适的解决方案(例如,map() 方法或等效方法)。

使用 JSON

在使用 JSON 时,您仍然可以重用以下代码和本教程中的其他代码片段。 但是,请确保将 Proto... 类的名称替换为您的自定义类的名称,并进行必要的修改以适应您首选的 TCP 和 WebSocket 客户端。

symbolProtoOALightSymbol 类型的变量。 如果您使用 JSON,它可以是表示 ProtoOALightSymbol 消息的任何自定义类型。

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

在以下示例中,我们获取了 account 对象的存款货币 ID,该对象是 ProtoOATrader 类型。

1
int depositAssetId = account.DepositAssetId;

symbol 是表示 ProtoOALightSymbol 类型消息的变量。

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 消息,在某些情况下,这可能每秒多次。

注意

由于 ProtoOALightSymbol 实体不包含任何表示买价或卖价的字段,我们强烈建议创建自定义 Symbol 类或等效类,以便您可以轻松地以编程方式创建新符号并在触发某些事件时更新其属性。 您必须将轻量符号单独转换为此类的对象。

订阅并处理现货事件

要从头到尾遵循转换链,您需要订阅链中包含的所有符号的 ProtoOASpotEvent。 以下是您可以通过官方 SDK 执行此操作的方式。

在执行以下操作之前,创建一个 Symbol 类或等效类,以便您可以轻松地以编程方式创建新符号并在触发某些事件时更新其属性。 在以下示例中,我们的 Symbol 类包括 ProtoOASymbolProtoOALightSymbol 对象作为属性。

 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 集合。 我们找到其 ID 与刚刚收到的 ProtoOASpotEventsymbolId 字段匹配的符号对象。 最后,我们使用 GetPriceFromRelative 辅助方法更新 symbolbidask 属性。

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

请注意,pipPositiondigits 属性的值必须单独获取。

遵循转换链并返回最终转换率

此时,您的转换链中的所有符号都应接收现货价格,并且您应该能够访问每个符号的准确买价和卖价。 剩下的就是沿着转换链行进并返回最终汇率。 这可以通过以下方式完成:

 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