Перейти к содержанию

Конвертация котировки символа

Конвертация котировки символа является важным действием, если вы хотите, чтобы ваше приложение включало функциональность, связанную с расчетами прибыли и убытков.

Варианты использования

Выполнение конвертации котировки символа необходимо при расчете комиссий, свопов и дивидендов. Комиссии, свопы и дивиденды, как правило, указываются в USD. Однако, в зависимости от вашего брокера и ваших предпочтений, валюта депозита вашего счета может представлять собой любой другой актив, отличный от USD. Чтобы точно определить комиссии, свопы и дивиденды, вам потребуется знать обменный курс между USD и валютой депозита вашего счета.

Цепочки конвертации

Рассматривайте цену каждого отдельного символа как курс конвертации между его базовым активом и котируемым активом. Задача получения курсов конвертации усложняется, если нет символов, напрямую связывающих один актив с другим. К счастью, бэкенд cTrader может автоматически создавать цепочки конвертации, которые выступают в качестве кратчайших возможных путей конвертации от одного актива к другому.

Например, если вы хотите конвертировать EUR в NZD, а на торговом сервере нет символа EURNZD, cTrader предложит цепочку, которая будет служить промежуточными шагами в процессе конвертации. В зависимости от доступности символов, такая цепочка может выглядеть как EURUSD-USDCAD-CADNZD, EURCFH-CFHUSD-USDNZD или даже EURCAD-CADUSD-USDCFH-CFHNZD.

Чтобы выполнить конвертацию между двумя активами, ваше приложение должно выполнить следующие действия:

  • Получить доступ к ID активов, между которыми вы хотите выполнить конвертацию.
  • Получить цепочку конвертации, включающую один или несколько легких символов.
  • Подписаться на спотовые события и обработать их.
  • Пройти по цепочке конвертации и вернуть итоговый курс конвертации.

Ниже мы подробно объясним каждое из этих действий.

Доступ к ID активов

Доступ к ID активов можно получить напрямую как к свойствам сущности легкого символа (представленной сообщением модели ProtoOALightSymbol). Кроме того, вы можете получить ID валюты депозита счета напрямую из связанной с ним сущности ProtoOaTrader.

Обратите внимание, что приведенный ниже пример позволяет получить ID активов только для одного символа. Чтобы выполнить ту же операцию для нескольких символов, вам потребуется добавить цикл или использовать другое подходящее решение (например, метод map() или его эквивалент).

Работа с JSON

При работе с JSON вы все равно можете повторно использовать приведенный ниже код и другие фрагменты кода из этого руководства. Однако убедитесь, что вы заменили названия классов Proto... названиями ваших пользовательских классов и внесли необходимые изменения с учетом предпочитаемого вами TCP- и WebSocket-клиента.

symbol — это переменная типа ProtoOALightSymbol. Если вы используете JSON, она может быть любого пользовательского типа, представляющего сообщение ProtoOALightSymbol.

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

В примере ниже мы получаем ID валюты депозита объекта account, который относится к типу ProtoOATrader.

1
int depositAssetId = account.DepositAssetId;

symbol — это переменная, представляющая сообщение типа ProtoOALightSymbol.

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

В примере ниже мы получаем ID валюты депозита объекта account, который представляет сообщение модели ProtoOATrader.

1
depositAssetId = account.depositAssetId

Получение цепочки конвертации

Чтобы получить действительную цепочку конвертации, ваше приложение должно отправить сообщение ProtoOASymbolsForConversionReq с полями firstAssetId и lastAssetId, установленными на 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 включает объекты 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, которая хранит наши легкие символы. Мы находим объект символа, ID которого совпадает с полем symbolId только что полученного нами ProtoOASpotEvent. Наконец, мы обновляем свойства bid и ask объекта symbol с помощью вспомогательного метода 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));
    }

}

Мы можем использовать следующую функцию для подписки на 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)

Обратите внимание, что значения свойств 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