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

ตัวอย่างการรวม SDK

ปลั๊กอิน WebView จะมีฟังก์ชันการทำงานที่มากขึ้นเมื่อรวม เมธอด SDK สำหรับการดึงข้อมูล, การเทรด และการดำเนินการอื่นๆ ที่เป็นประโยชน์ต่อผู้ใช้

หมายเหตุ

ตัวอย่างในบทความนี้ไม่ต้องมีไปป์ไลน์หรือการติดตั้งใดๆ เนื่องจากมีการกำหนดค่าทั้งหมดให้โหลดโดยตรงจาก esm.sh โค้ดนี้สามารถบันทึกเป็นไฟล์ .html และอัปโหลดไปยังบริการโฮสติ้งได้ทันทีเพื่อการใช้งานอย่างรวดเร็ว สุดท้าย, URL ของเว็บไซต์ที่ได้สามารถใช้เพื่อ สร้างปลั๊กอิน WebView

เคล็ดลับ

ป้อนโค้ดตัวอย่างและ เอกสารอ้างอิง SDK ให้กับโมเดล AI, จากนั้นแนะนำให้ AI แก้ไขโค้ดเพื่อ สร้าง และส่งมอบไอเดียเว็บไซต์ของคุณสำหรับปลั๊กอิน WebView

โค้ด HTML ด้านล่างเป็นตัวอย่างเว็บไซต์ที่สมบูรณ์ในตัวเองสำหรับปลั๊กอิน WebView เว็บแอปนี้เชื่อมต่อกับโฮสต์การเทรด, แลกเปลี่ยนข้อมูล และดำเนินการเทรดแบบเรียลไทม์

ตัวอย่าง
  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
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8"/>
  <title>SDK integration example — single-file build-less</title>
  <meta name="viewport" content="width=device-width,initial-scale=1"/>

  <!-- Minimal styles -->
  <style>
    html,body{margin:0;height:100%;font-family:Arial,Helvetica,sans-serif}
    #root{height:100%;display:flex;justify-content:center;align-items:flex-start;
          background:#fff;color:#000;padding:10px;overflow:auto}
    button{padding:4px 8px;margin:2px}
    input,select{padding:2px;margin-left:4px}
    pre{margin:0}
  </style>
</head>

<body>
  <div id="root"></div>

  <script type="module">
    /***  CDN imports  ***/
    import React, { useState, useEffect, useRef, useCallback } from "https://esm.sh/react@19.1.0";
    import { createRoot }                                    from "https://esm.sh/react-dom@19.1.0/client";

    /* SDK + helpers */
    import { createClientAdapter }
      from "https://esm.sh/@spotware-web-team/sdk-external-api@0.0.5";
    import {
      cancelOrder, closePosition, createNewOrder,
      executionEvent, getAccountGroupInformation, getAccountInformation,
      getDealList, getDynamicLeverage, getLightSymbolList, getSymbol,
      handleConfirmEvent, modifyOrder, modifyOrderProtection, quoteEvent,
      registerEvent, ServerInterfaces, subscribeQuotes, unsubscribeQuotes
    } from "https://esm.sh/@spotware-web-team/sdk@0.0.9";
    import { take, tap, catchError }   from "https://esm.sh/rxjs@7.8.2";
    import { createLogger }            from "https://esm.sh/@veksa/logger@1.0.6";

    /*** Example component  ***/
    function Example(){
      /* --- connection / adapters --- */
      const [connected,setConnected] = useState(false);
      const adapter                  = useRef(null);

      /* --- UI state --- */
      const [logs,setLogs]                         = useState([]);
      const [symbolId,          setSymbolId]       = useState(1);
      const [quotesSymbolId,    setQuotesSymbolId] = useState(1);
      const [leverageId,        setLeverageId]     = useState(1);
      const [newOrderSymbolId,  setNewOrderSymbolId]  = useState(1);
      const [newOrderVolume,    setNewOrderVolume] = useState(100000);
      const [modifyOrderId,     setModifyOrderId]  = useState(1);
      const [modifyOrderVolume, setModifyOrderVolume]=useState(100000);
      const [modifyPrice,       setModifyPrice]    = useState(100);
      const [modifyPositionId,  setModifyPositionId]=useState(1);
      const [modifyStopLoss,    setModifyStopLoss] = useState(0.1);
      const [modifyTakeProfit,  setModifyTakeProfit]=useState(0.1);
      const [cancelOrderId,     setCancelOrderId]  = useState(1);
      const [closePositionId,   setClosePositionId]=useState(1);
      const [closePositionVolume,setClosePositionVolume]=useState(100000);

      const log = m => setLogs(p=>[...p, typeof m==="string"?m:JSON.stringify(m,null,2)]);

      /* ---------- connect on mount ---------- */
      useEffect(()=>{
        const logger   = createLogger(location.href.includes("showLogs"));
        adapter.current= createClientAdapter({logger});
        log("🔌 Connecting…");

        handleConfirmEvent(adapter.current,{}).pipe(take(1)).subscribe();

        registerEvent(adapter.current).pipe(
          take(1),
          tap(()=>{
            handleConfirmEvent(adapter.current,{}).pipe(take(1)).subscribe();
            setConnected(true); log("✅ Connected");
          }),
          catchError(()=>{ log("❌ Error host connection"); return []; })
        ).subscribe();
      },[]);

      /* ---------- helper wrappers ---------- */
      const handleAccountInformation   = ()=> getAccountInformation(adapter.current,{}).pipe(take(1), tap(log)).subscribe();
      const handleAccountGroupInformation=()=>getAccountGroupInformation(adapter.current,{}).pipe(take(1),tap(log)).subscribe();
      const handleLightSymbolList      = ()=> getLightSymbolList(adapter.current,{}).pipe(take(1),tap(log)).subscribe();
      const handleSymbol               = ()=> getSymbol(adapter.current,{symbolId:[symbolId]}).pipe(take(1),tap(log)).subscribe();
      const handleSubscribeQuotes      = ()=> subscribeQuotes(adapter.current,{symbolId:[quotesSymbolId]}).pipe(take(1),tap(log)).subscribe();
      const handleUnsubscribeQuotes    = ()=> unsubscribeQuotes(adapter.current,{symbolId:[quotesSymbolId]}).pipe(take(1),tap(log)).subscribe();
      const handleDynamicLeverage      = ()=> getDynamicLeverage(adapter.current,{leverageId}).pipe(take(1),tap(log)).subscribe();
      const handleDealList             = ()=> getDealList(adapter.current,{fromTimestamp:0,toTimestamp:Date.now()}).pipe(take(1),tap(log)).subscribe();
      const handleCreateNewOrder       = ()=> createNewOrder(adapter.current,{
                                            symbolId:newOrderSymbolId,
                                            orderType:ServerInterfaces.ProtoOrderType.MARKET,
                                            tradeSide:ServerInterfaces.ProtoTradeSide.BUY,
                                            volume:newOrderVolume,
                                          }).pipe(tap(log)).subscribe();
      const handleModifyOrder          = ()=> modifyOrder(adapter.current,{
                                            orderId:modifyOrderId,
                                            limitPrice:modifyPrice,
                                            volume:modifyOrderVolume,
                                          }).pipe(tap(log)).subscribe();
      const handleModifyOrderProtection= ()=> modifyOrderProtection(adapter.current,{
                                            positionId:modifyPositionId,
                                            stopLoss:modifyStopLoss,
                                            takeProfit:modifyTakeProfit,
                                          }).pipe(tap(log)).subscribe();
      const handleCancelOrder          = ()=> cancelOrder(adapter.current,{orderId:cancelOrderId}).pipe(tap(log)).subscribe();
      const handleClosePosition        = ()=> closePosition(adapter.current,{
                                            positionId:closePositionId,
                                            volume:closePositionVolume,
                                          }).pipe(tap(log)).subscribe();

      /* ---------- stream quotes / executions ---------- */
      useEffect(()=>{
        if(!connected) return;
        quoteEvent(adapter.current).pipe(tap(log)).subscribe();
        executionEvent(adapter.current).pipe(tap(log)).subscribe();
      },[connected]);

      const e = React.createElement;          // shorthand

      /* first column of controls */
      const controlBlock = e("div",{style:{
          display:"flex",flexDirection:"column",alignItems:"flex-start",
          gap:10,flexWrap:"wrap",marginBottom:10}},
        [
          e("button",{disabled:!connected,onClick:handleAccountInformation},"getAccountInformation"),
          e("button",{disabled:!connected,onClick:handleAccountGroupInformation},"getAccountGroupInformation"),
          e("button",{disabled:!connected,onClick:handleLightSymbolList},"getLightSymbolList"),

          /* getSymbol */
          e("div",null,[
            e("button",{disabled:!connected,onClick:handleSymbol},"getSymbol"),
            " symbolId:",
            e("input",{style:{width:30},value:symbolId,
                      onChange:ev=>setSymbolId(+ev.target.value)}),
          ]),

          /* subscribeQuotes */
          e("div",null,[
            e("button",{disabled:!connected,onClick:handleSubscribeQuotes},"subscribeQuotes"),
            " symbolId:",
            e("input",{style:{width:30},value:quotesSymbolId,
                      onChange:ev=>setQuotesSymbolId(+ev.target.value)}),
          ]),

          /* unsubscribeQuotes */
          e("div",null,[
            e("button",{disabled:!connected,onClick:handleUnsubscribeQuotes},"unsubscribeQuotes"),
            " symbolId:",
            e("input",{style:{width:30},value:quotesSymbolId,
                      onChange:ev=>setQuotesSymbolId(+ev.target.value)}),
          ]),

          /* getDynamicLeverage */
          e("div",null,[
            e("button",{disabled:!connected,onClick:handleDynamicLeverage},"getDynamicLeverage"),
            " leverageId:",
            e("input",{style:{width:30},value:leverageId,
                      onChange:ev=>setLeverageId(+ev.target.value)}),
          ]),

          e("button",{disabled:!connected,onClick:handleDealList},"getDealList"),

          /* createNewOrder */
          e("div",null,[
            e("button",{disabled:!connected,onClick:handleCreateNewOrder},"createNewOrder"),
            " symbolId:",
            e("input",{style:{width:30},value:newOrderSymbolId,
                      onChange:ev=>setNewOrderSymbolId(+ev.target.value)}),
            " volume:",
            e("input",{style:{width:60},value:newOrderVolume,
                      onChange:ev=>setNewOrderVolume(+ev.target.value)}),
          ]),

          /* modifyOrder */
          e("div",null,[
            e("button",{disabled:!connected,onClick:handleModifyOrder},"modifyOrder"),
            " orderId:",
            e("input",{style:{width:30},value:modifyOrderId,
                      onChange:ev=>setModifyOrderId(+ev.target.value)}),
            " volume:",
            e("input",{style:{width:60},value:modifyOrderVolume,
                      onChange:ev=>setModifyOrderVolume(+ev.target.value)}),
            " modify price:",
            e("input",{style:{width:40},value:modifyPrice,
                      onChange:ev=>setModifyPrice(+ev.target.value)}),
          ]),

          /* modifyOrderProtection */
          e("div",null,[
            e("button",{disabled:!connected,onClick:handleModifyOrderProtection},"modifyOrderProtection"),
            " positionId:",
            e("input",{style:{width:30},value:modifyPositionId,
                      onChange:ev=>setModifyPositionId(+ev.target.value)}),
            " stop loss:",
            e("input",{style:{width:40},value:modifyStopLoss,
                      onChange:ev=>setModifyStopLoss(+ev.target.value)}),
            " takeProfit:",
            e("input",{style:{width:40},value:modifyTakeProfit,
                      onChange:ev=>setModifyTakeProfit(+ev.target.value)}),
          ]),

          /* cancelOrder */
          e("div",null,[
            e("button",{disabled:!connected,onClick:handleCancelOrder},"cancelOrder"),
            " orderId:",
            e("input",{style:{width:30},value:cancelOrderId,
                      onChange:ev=>setCancelOrderId(+ev.target.value)}),
          ]),

          /* closePosition */
          e("div",null,[
            e("button",{disabled:!connected,onClick:handleClosePosition},"closePosition"),
            " positionId:",
            e("input",{style:{width:30},value:closePositionId,
                      onChange:ev=>setClosePositionId(+ev.target.value)}),
            " volume:",
            e("input",{style:{width:60},value:closePositionVolume,
                      onChange:ev=>setClosePositionVolume(+ev.target.value)}),
          ]),
        ]);

      /* log viewer */
      const logPane = e("div",{style:{
          height:"100%",overflowY:"scroll",minHeight:200,background:"#eee",
          padding:6,borderRadius:4}},
        logs.map((l,i)=>e("pre",{key:i,style:{marginBottom:10}},l)));

      return e("div",{style:{display:"flex",flexDirection:"column",
                            width:"100%",padding:10,overflow:"auto"}},[
              controlBlock,
              logPane
      ]);
    }

    /***  Mount to DOM  ***/
    createRoot(document.getElementById("root")).render(React.createElement(Example));
  </script>
</body>
</html>

ตารางนี้แบ่งแยกการดำเนินการทั่วไปและสำคัญในตัวอย่าง:

การดำเนินการ วัตถุประสงค์ อินสแตนซ์
แฮนด์เชค เปิด WebSocket และทำลำดับ ยืนยัน → ลงทะเบียน → ยืนยัน ให้สมบูรณ์ useEffect ตัวแรก
การซิงโครไนซ์อย่างต่อเนื่อง สตรีมข้อมูลตลาดและเหตุการณ์การดำเนินการเพื่อให้ UI ยังคงเป็นปัจจุบัน useEffect ตัวที่สอง
การเมาท์ แทรก UI (React) เข้าไปในหน้าเพจหรือคอนเทนเนอร์ WebView การเรียก createRoot(…).render(…) ที่ด้านล่างของโค้ด
การดำเนินการ SDK เรียกใช้เมธอด SDK เช่น getAccountInformation, createNewOrder, modifyOrder, closePosition เป็นต้น เพื่อตอบสนองต่อการโต้ตอบของผู้ใช้ ฟังก์ชันจัดการปุ่มแต่ละตัวภายในคอมโพเนนต์ Example

แฮนด์เชค

เมื่อปลั๊กอินโหลด จะสร้างการเชื่อมต่อกับโฮสต์ cTrader โดยการสร้างอินสแตนซ์ clientAdapter อะแดปเตอร์นี้ช่วยให้การสื่อสารสองทางระหว่างปลั๊กอิน WebView และแพลตฟอร์ม

 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
// The first useEffect, which runs only on initial mounting
useEffect(() => {
  // 1. Open the WebSocket connection
  const logger        = createLogger(location.href.includes("showLogs"));
  adapter.current     = createClientAdapter({ logger });
  log("🔌 Connecting…");

  // 2. First handshake message (“confirm”)
  handleConfirmEvent(adapter.current, {})
    .pipe(take(1))
    .subscribe();

  // 3. Wait until the host registers the client
  registerEvent(adapter.current)
    .pipe(
      take(1),
      tap(() => {
        // 4. Second confirmation required by the protocol
        handleConfirmEvent(adapter.current, {})
          .pipe(take(1))
          .subscribe();

        // 5. Fully in-sync. Unlock the UI
        setConnected(true);
        log("✅ Connected");
      }),
      catchError(err => {
        log("❌ Error host connection");
        log(err);
        return [];
      })
    )
    .subscribe();
}, []);        // Empty deps → runs once

การซิงโครไนซ์อย่างต่อเนื่อง

เมื่อการแฮนด์เชคเสร็จสิ้นและปลั๊กอินเชื่อมต่ออย่างสมบูรณ์ ก็จะเริ่มซิงโครไนซ์กับโฮสต์โดยการซับสไครบ์สองสตรีมเหตุการณ์ต่อเนื่อง: ราคาสดและการอัปเดตการดำเนินการเทรด สิ่งนี้ทำให้มั่นใจว่าปลั๊กอินจะได้รับราคาล่าสุดและกิจกรรมคำสั่งจากสภาพแวดล้อมการเทรดแบบเรียลไทม์

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// The second useEffect, which depends on the `connected` flag
useEffect(() => {
  if (!connected) return;   // do not subscribe until handshake finishes

  // 1. Subscribe to live quotes (price updates)
  quoteEvent(adapter.current)
    .pipe(tap(log))
    .subscribe();

  // 2. Subscribe to trade executions (order fills, cancels, rejections)
  executionEvent(adapter.current)
    .pipe(tap(log))
    .subscribe();
}, [connected]);             // rerun this block only once `connected` becomes true

การเมาท์

หลังจากกำหนดคอมโพเนนต์ Example แล้ว ปลั๊กอินจะแสดงอินเทอร์เฟซผู้ใช้ลงใน DOM โดยใช้ React createRoot API ขั้นตอนนี้จะติดตั้งปลั๊กอินทั้งในด้านการมองเห็นและการทำงานภายในคอนเทนเนอร์โฮสต์ ทำให้ผู้ใช้สามารถโต้ตอบได้

1
2
3
// At the bottom. This runs once at load
createRoot(document.getElementById('root'))
  .render(React.createElement(Example));

ขั้นตอนการเมาท์นี้ทำให้ขั้นตอนการตั้งค่าสมบูรณ์โดยการวางปลั๊กอินเข้าไปในอินเทอร์เฟซที่มองเห็นได้ และอนุญาตให้ลอจิกที่กำหนดไว้ภายใน Example() เริ่มรัน วมถึงการจัดการการเชื่อมต่อ การผูก SDK และการซับสไครบ์เหตุการณ์

การดำเนินการ SDK

SDK มีแรปเปอร์ที่สะดวกสำหรับการขอข้อมูลที่เกี่ยวข้องกับการเทรด และการเรียก SDK ในโค้ดมีรูปแบบเดียวกัน:

1
2
3
sdkMethod(adapter.current, params)
  .pipe(take(1), tap(log), catchError(handleError))
  .subscribe();

แต่ละฟังก์ชันเหล่านี้ถูกแนบมากับปุ่มที่เปิดใช้งานได้เฉพาะเมื่อปลั๊กอินเชื่อมต่อแล้วเท่านั้น การคลิกปุ่มแต่ละครั้งจะเรียกใช้เมธอด SDK เฉพาะ เช่น การดึงข้อมูลบัญชีหรือการสร้างคำสั่ง

รับข้อมูลบัญชี

1
getAccountInformation(adapter, {}).pipe(take(1), tap(log)).subscribe();

สร้างคำสั่งตลาดใหม่

1
2
3
4
5
6
createNewOrder(adapter, {
  symbolId: 1,
  orderType: ServerInterfaces.ProtoOrderType.MARKET,
  tradeSide: ServerInterfaces.ProtoTradeSide.BUY,
  volume: 100000,
}).pipe(tap(log)).subscribe();

รับรายการสัญลักษณ์ที่เทรดได้

1
getLightSymbolList(adapter, {}).pipe(take(1), tap(log)).subscribe();

ซับไครบ์และยกเลิกการซับสไครบ์เพื่อรับราคา

1
2
subscribeQuotes(adapter, { symbolId: [1] }).pipe(take(1), tap(log)).subscribe();
unsubscribeQuotes(adapter, { symbolId: [1] }).pipe(take(1), tap(log)).subscribe();

นอกจากนี้ คุณสามารถฟังการอัปเดตราคาและรับผลการดำเนินการเทรดโดยใช้:

1
2
quoteEvent(adapter.current).pipe(tap(log)).subscribe();
executionEvent(adapter.current).pipe(tap(log)).subscribe();

ผลลัพธ์การบันทึกและดีบัก (ไม่บังคับ)

การตอบกลับและเหตุการณ์ทั้งหมดของ SDK จะถูกบันทึกลงในแผงด้านข้างแบบเรียลไทม์โดยใช้:

1
const log = m => setLogs(p => [...p, typeof m === "string" ? m : JSON.stringify(m, null, 2)]);

พื้นที่ผลลัพธ์จะถูกแสดงผลโดยใช้:

1
logs.map((l, i) => e("pre", { key: i }, l))

บันทึกเหล่านี้ช่วยในการตรวจสอบ JSON เพย์โหลด และช่วยยืนยันว่าการดำเนินการสำเร็จหรือล้มเหลวพร้อมรายละเอียด