انتقل إلى المحتوى

تحويل سعر الرمز

تحويل سعر الرمز هو إجراء أساسي إذا كنت تريد أن يتضمن تطبيقك وظائف متعلقة بحسابات الربح والخسارة.

حالات الاستخدام

يعد إجراء تحويلات سعر الرمز ضرورياً عند حساب العمولات والسواب وتوزيعات الأرباح. عادةً ما يتم تحديد العمولات والسواب وتوزيعات الأرباح بالدولار الأمريكي. ومع ذلك، اعتماداً على الوسيط الخاص بك وتفضيلاتك، قد تكون عملة إيداع حسابك بأي أصل آخر غير الدولار الأمريكي. لتحديد العمولات والسواب وتوزيعات الأرباح بدقة، ستحتاج إلى معرفة سعر الصرف بين الدولار الأمريكي وعملة إيداع حسابك.

سلاسل التحويل

فكر في سعر كل رمز فردي كمعدل تحويل بين الأصل الأساسي وأصل التسعير. تصبح مهمة الحصول على أسعار التحويل معقدة إذا لم تكن هناك رموز تربط مباشرة بين أصل معين وأصل آخر. لحسن الحظ، يمكن لخادم cTrader إنشاء سلاسل تحويل تلقائياً تعمل كأقصر مسارات تحويل ممكنة من أصل إلى آخر.

على سبيل المثال، إذا كنت تريد تحويل اليورو إلى الدولار النيوزيلندي، ولم يكن هناك رمز EURNZD على خادم التداول، سيقترح cTrader سلسلة ستعمل كخطوات وسيطة في عملية التحويل. اعتماداً على توفر الرموز، يمكن أن تبدو هذه السلسلة مثل EURUSD-USDCAD-CADNZD، أو EURCFH-CFHUSD-USDNZD، أو حتى EURCAD-CADUSD-USDCFH-CFHNZD.

للتحويل بين أصلين، يحتاج تطبيقك إلى تنفيذ الإجراءات التالية:

  • الوصول إلى معرفات الأصول التي تريد التحويل بينها.
  • الحصول على سلسلة تحويل تتضمن رمزًا خفيفًا واحدًا أو أكثر.
  • الاشتراك في أحداث السعر الفوري والتعامل معها.
  • اتباع سلسلة التحويل وإرجاع معدل التحويل النهائي.

فيما يلي، نشرح كل من هذه الإجراءات بالتفصيل.

الوصول إلى معرفات الأصول

يمكن الوصول إلى معرفات الأصول مباشرة كخصائص لكيان الرمز الخفيف (ممثلاً برسالة نموذج ProtoOALightSymbol). بالإضافة إلى ذلك، يمكنك الحصول على معرف عملة إيداع الحساب مباشرة من كيان ProtoOaTrader المرتبط به.

لاحظ أن المثال أدناه يحصل فقط على معرفات الأصول لرمز واحد. لتنفيذ نفس العملية لعدة رموز، ستحتاج إلى إضافة حلقة تكرار أو استخدام حل مناسب آخر (على سبيل المثال، طريقة map() أو ما يعادلها).

العمل مع JSON

عند العمل مع JSON، لا يزال بإمكانك إعادة استخدام الكود أدناه ومقتطفات الكود الأخرى في هذا البرنامج التعليمي. ومع ذلك، تأكد من استبدال أسماء فئات Proto... بأسماء فئاتك المخصصة وإجراء التعديلات اللازمة لمراعاة عميل TCP و WebSocket المفضل لديك.

symbol هو متغير من نوع ProtoOALightSymbol. إذا كنت تستخدم JSON، يمكن أن يكون من أي نوع مخصص يمثل رسالة ProtoOALightSymbol.

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

في المثال أدناه، نحصل على معرف عملة الإيداع لكائن account الذي هو من نوع ProtoOATrader.

1
int depositAssetId = account.DepositAssetId;

symbol هو متغير يمثل رسالة من نوع ProtoOALightSymbol.

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

في المثال أدناه، نحصل على معرف عملة الإيداع لكائن account الذي يمثل رسالة نموذج ProtoOATrader.

1
depositAssetId = account.depositAssetId

الحصول على سلسلة تحويل

للحصول على سلسلة تحويل صالحة، يحتاج تطبيقك إلى إرسال رسالة ProtoOASymbolsForConversionReq مع تعيين حقول firstAssetId و lastAssetId الخاصة بها إلى معرفات الأصول التي تريد التحويل بينها. عندما تتلقى استجابة من نوع 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 التي تخزن رموزنا الخفيفة. نجد كائن الرمز الذي يتطابق معرفه مع حقل symbolId الخاص بـ ProtoOASpotEvent الذي تلقيناه للتو. أخيرًا، نقوم بتحديث خصائص bid و ask للرمز باستخدام طريقة المساعدة 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