ข้ามไปที่เนื้อหา

การแปลงอัตราสัญลักษณ์

การแปลงอัตราสัญลักษณ์เป็นสิ่งสำคัญหากคุณต้องการให้แอปพลิเคชันของคุณมีฟังก์ชันการทำงานที่เกี่ยวข้องกับการคำนวณ P&L

กรณีการใช้งาน

การแปลงอัตราสัญลักษณ์เป็นสิ่งจำเป็นเมื่อคำนวณค่าคอมมิชชัน สวอป และเงินปันผล ค่าคอมมิชชัน สวอป และเงินปันผลมักจะแสดงใน USD อย่างไรก็ตาม ขึ้นอยู่กับโบรกเกอร์และความต้องการของคุณ สกุลเงินฝากบัญชีของคุณอาจเป็นสกุลอื่นที่ไม่ใช่ USD เพื่อกำหนดค่าคอมมิชชัน สวอป และเงินปันผลอย่างแม่นยำ คุณจำเป็นต้องทราบอัตราแลกเปลี่ยนระหว่าง USD และสกุลเงินฝากบัญชีของคุณ

สายการแปลง

คิดว่าราคาของแต่ละสัญลักษณ์เป็นอัตราแปลงระหว่างสินทรัพย์ฐานและสินทรัพย์อ้างอิง งานของการรับอัตราแปลงจะซับซ้อนหากไม่มีสัญลักษณ์ที่เชื่อมโยงสินทรัพย์หนึ่งกับสินทรัพย์อื่นโดยตรง โชคดีที่ backend ของ 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 ที่ซ้ำไว้ภายในคอลเลกชัน

นี่คือวิธีที่คุณสามารถดำเนินการนี้ผ่าน 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

นอกจากนี้ ให้เพิ่มเงื่อนไขต่อไปนี้ให้กับ callback onMessageReceived

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

หมายเหตุ

เราขอแนะนำให้รับสัญลักษณ์สายการแปลงทั้งหมดสำหรับแต่ละสินทรัพย์เมื่อคุณร้องขอและรับข้อมูลสินทรัพย์จากเซิร์ฟเวอร์ครั้งแรก มิฉะนั้น คุณจะต้องส่งข้อความ ProtoOASymbolsForConversionReq ทุกครั้งที่คุณต้องการแปลงอัตราซึ่งในบางกรณีอาจทำหลายครั้งต่อวินาที

หมายเหตุ

เนื่องจากเอนทิตี ProtoOALightSymbol ไม่มีฟิลด์ใดที่แสดงราคา Bid หรือ Ask เราขอแนะนำให้สร้างคลาส Symbol ที่กำหนดเองหรือเทียบเท่าเพื่อให้คุณสามารถสร้างสัญลักษณ์ใหม่ได้อย่างง่ายดายและอัปเดตคุณสมบัติเมื่อเกิดเหตุการณ์บางอย่าง คุณจะต้องแปลงสัญลักษณ์เบาเป็นวัตถุของคลาสนี้แยกต่างหาก

สมัครและจัดการเหตุการณ์สปอต

เพื่อติดตามสายการแปลงตั้งแต่ต้นจนจบ คุณจำเป็นต้องสมัครรับ ProtoOASpotEvent สำหรับสัญลักษณ์ทั้งหมดที่รวมอยู่ในสายการแปลง ดำเนินการดังนี้ผ่าน SDKs อย่างเป็นทางการ

ก่อนที่คุณจะดำเนินการตามรายการด้านล่าง ให้สร้างคลาส 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)

เพื่อจัดการเหตุการณ์สปอต เราสามารถเพิ่มเงื่อนไขต่อไปนี้ให้กับ callback onMessageReceived ของเรา

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

สุดท้าย นี่คือ callback 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 จะต้องได้รับแยกต่างหาก

ติดตามสายการแปลงและส่งคืนอัตราแปลงสุดท้าย

ณ จุดนี้ สัญลักษณ์ทั้งหมดในสายการแปลงของคุณควรได้รับอัตราสปอตและคุณควรสามารถเข้าถึงราคา Bid และ Ask ที่แม่นยำสำหรับแต่ละสัญลักษณ์ สิ่งที่เหลืออยู่คือการเดินทางตามสายการแปลงและส่งคืนอัตราสุดท้าย สามารถดำเนินการดังนี้:

 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