Skip to content

Symbol Rate Conversion

Symbol rate conversion is an essential action if you want your application to include any sort of functionality related to P&L calculations.

Use Cases

Performing symbol rate conversions is necessary when calculating commissions, swaps, and dividends. Commissions, swaps, and dividends are typically denoted in USD. However, depending on your broker and your preferences, your account deposit currency may be in any other asset rather than USD. To accurately define commissions, swaps, and dividends, you would need to know the exchange rate between USD and your account deposit currency.

Conversion Chains

Think of the price of each individual symbol as a conversion rate between its base asset and quote asset. The task of attaining conversion rates becomes complicated if there are no symbols that directly link a given asset to another asset. Fortunately, the cTrader backend can automatically create conversion chains that act as the shortest possible conversion paths from one asset to another.

For example, if you want to convert EUR into NZD, and there is no EURNZD symbol on the trading server, cTrader will propose a chain that will serve as intermediate steps in the conversion process. Depending on symbol availability, such a chain could look like EURUSD-USDCAD-CADNZD, EURCFH-CFHUSD-USDNZD, or even EURCAD-CADUSD-USDCFH-CFHNZD.

To convert between two assets, your app needs to perform the following actions.

  • Access the IDs of the assets that you want to convert between.
  • Attain a conversion chain that includes one or more 'light' symbols.
  • Subscribe to and handle spot events.
  • Follow the conversion chain and return the final conversion rate.

Below, we explain each of these actions in detail.

Access Asset IDs

Asset IDs can be accessed directly as properties of the 'light' symbol entity (represented by the ProtoOALightSymbol model message). In addition, you can attain the ID of an account deposit currency directly from its related ProtoOaTrader entity.

Note that the below example only attain asset IDs for one symbol. To perform the same operation for multiple symbols, you would need to add a loop or use another suitable solution (e.g., a map() method or equivalent).

Working With JSON

When working with JSON, you can still reuse the code below and other code snippets in this tutorial. However, make sure to replace the names of Proto... classes with the names of your custom classes and make the necessary modifications to account for your preferred TCP/WebSocket client.

symbol is a variable of the ProtoOALightSymbol type. If you are using JSON, it can be of any custom type representing the ProtoOALightSymbol message.

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

In the below example, we attain the ID of the deposit currency of the account object which is of the ProtoOATrader type.

1
int depositAssetId = account.DepositAssetId;

symbol is a variable representing a message of the ProtoOALightSymbol type.

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

In the below example, we attain the ID of the deposit currency of the account object which represents the ProtoOATrader model message.

1
depositAssetId = account.depositAssetId

Attain a Conversion Chain

To attain a valid conversion chain, your app needs to send the ProtoOASymbolsForConversionReq message with its firstAssetId and lastAssetId fields set to the IDs of the assets that you want to convert between. When you receive a response of the ProtoOASymbolsForConversionRes type, store its repeated symbol field inside a collection.

Here is how you can perform this action via the official SDKs.

 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

Also add the following condition to the onMessageReceived callback.

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

Note

We highly recommend attaining all conversion chain symbols for each asset when you first request and receive asset data from the server. Otherwise, you will have to send the ProtoOASymbolsForConversionReq message every time you need to convert rates which, in some cases, may be several times per second.

Note

As ProtoOALightSymbol entities do not contain any fields representing bid or ask prices, we highly recommend creating custom Symbol classes or equivalent so you can easily create new symbols programmatically and update their properties when certain events are triggered. You will have to convert light symbols into objects of this class separately.

Subscribe to and Handle Spot Events

To follow the conversion chain from start to finish, you need to subscribe to the ProtoOASpotEvent for all symbols included in the chain. This is done as follows via the official SDKs.

Before you perform the operations listed below, create a Symbol class or equivalent so that you can easily create new symbols programmatically and update their properties when certain events are triggered. In the examples below, our Symbol class includes ProtoOASymbol and ProtoOALightSymbol objects as properties.

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

We also have to subscribe to spot events and handle them. In the example below, we access the symbols collection that stores our light symbols. We find the symbol object whose ID matches the symbolId field of the ProtoOASpotEvent we have just received. Finally, we update the bid and ask properties of the symbol using the GetPriceFromRelative helper method.

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

}

We can use the following function to subscribe to ProtoOASpotEvent for a particular symbol. Note that it only subscribes to one symbol, meaning that you would need to call this function for every light symbol in your collection of conversion symbols.

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)

To handle spot events, we can add the following conditions to our onMessageReceived callback.

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

Finally, here is our 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 our Symbol class, the getPipsFromRelative function is defined as follows.

import math

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

Note that the values of the pipPosition and digits properties have to be attained separately.

Follow the Conversion Chain and Return the Final Conversion Rate

At this point, all symbols in your conversion chain should receive spot rates and you should be able to access accurate bid and ask prices for each symbol. All that is left is to 'travel' along the conversion chain and return the final rate. This can be done as follows.

 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