价差交易策略

策略说明

价差交易,又叫套利交易,是一种通过相关的期货合约或者期现品种等商品、金融产品及它们的衍生品之间的价差波动来获取利润的交易方法。

这里价差交易主要指利用期货合约价格之间不合理关系进行套利交易(Spread Trading)。

在页面上填入 A、B 两个合约,期望的买开卖平和卖开买平的价差、手数。

单击开始按钮后,当前价格处显示买入价差和卖出价差。

价差交易策略分为两个 Task,第一个 TaskQuote 用来显示两个合约的价差,第二个 TaskSpread 用来实现下单交易的策略。交易买卖策略如下:

  • 合约A买1价 - 合约B卖1价 <= 买开价差,买开合约A,卖开合约B
  • 合约A卖1价 - 合约B买1价 >= 卖平价差,卖平合约A,买平合约B
  • 合约A卖1价 - 合约B买1价 >= 卖开价差,卖开合约A,买开合约B
  • 合约A买1价 - 合约B卖1价 <= 买平价差,买平合约A,卖平合约B

点击停止按钮时,可以暂停任务,并且撤掉已经发出且为成交的挂单。

源码

  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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
    <title>天勤-交易</title>
    <!-- base ui && js -->
    <link href="assets/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="assets/highlight/styles/atom-one-light.css">
    <link rel="stylesheet" href="tq.css">
</head>

<body>
    <div class="container-fluid main-container">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h3 class="panel-title">
                    价差策略
                    <span class="pull-right STATE">
                        <span class="label label-danger">停止</span>
                    </span>
                </h3>
            </div>
            <ul class="list-group inputs" style="font-size:12px;">
                <li class="list-group-item tq-container">

                    <div class="row">
                        <div class="col col-xs-3">
                            <span class="pull-right">合约代码A</span>
                        </div>
                        <div class="col col-xs-9">
                            <input type="text" class="form-control input-sm" placeholder="合约代码A" value="SHFE.rb1805" id="symbolA">
                        </div>
                    </div>
                    <div class="row">
                        <div class="col col-xs-3">
                            <span class="pull-right">合约代码B</span>
                        </div>
                        <div class="col col-xs-9">
                            <input type="text" class="form-control input-sm" placeholder="合约代码B" value="SHFE.rb1810" id="symbolB">
                        </div>
                    </div>
                    <div class="row">
                        <div class="col col-xs-3">
                            <span class="pull-right">卖平</span>
                        </div>
                        <div class="col col-xs-3">
                            <input type="number" class="form-control input-sm" placeholder="卖平" value="160" id="price_sell_close">
                        </div>
                        <div class="col col-xs-3">
                            <span class="pull-right">卖开</span>
                        </div>
                        <div class="col col-xs-3">
                            <input type="number" class="form-control input-sm" placeholder="卖开" value="133" id="price_sell_open">
                        </div>
                    </div>
                    <div class="row">
                        <div class="col col-xs-3">
                            <span class="pull-right">买开</span>
                        </div>
                        <div class="col col-xs-3">
                            <input type="number" class="form-control input-sm" placeholder="买开" value="154" id="price_buy_open">
                        </div>
                        <div class="col col-xs-3">
                            <span class="pull-right">买平</span>
                        </div>
                        <div class="col col-xs-3">
                            <input type="number" class="form-control input-sm" placeholder="买平" value="111" id="price_buy_close">
                        </div>
                    </div>
                    <div class="row">
                        <div class="col col-xs-3">
                            <span class="pull-right">手数</span>
                        </div>
                        <div class="col col-xs-9">
                            <input type="number" class="form-control input-sm" placeholder="手数" value="3" id="volume">
                        </div>
                    </div>
                    <div class="row">
                        <div class="col col-xs-3">
                            <span class="pull-right">当前价格</span>
                        </div>
                        <div class="col col-xs-9">
                            <span>买入价差: </span>
                            <span id="spread_ask"></span>
                            <span></span>
                            <span>卖出价差: </span>
                            <span id="spread_bid"></span>
                            <span></span>
                        </div>
                    </div>
                </li>
                <li class="list-group-item">
                    <div class="row">
                        <div class="col col-xs-12">
                            <div class="btn-toolbar" role="toolbar">
                                <div class="btn-group btn-group-xs">
                                    <button type="button" class="btn btn-default START" id="START">
                                        <span class="glyphicon glyphicon-play"></span>&nbsp;开始
                                    </button>
                                    <button type="button" class="btn btn-default STOP" id="STOP">
                                        <span class="glyphicon glyphicon-stop"></span>&nbsp;停止
                                    </button>
                                </div>
                            </div>
                        </div>
                    </div>
                </li>
            </ul>
        </div>

        <div class="panel panel-default">
            <div class="panel-heading" role="tab" id="headingOne">
                <h4 class="panel-title">
                    <a role="button" data-toggle="collapse" href="#collapse" aria-expanded="true" aria-controls="collapse">
                        说明:本策略为一组合套利策略. 指定两个合约构成组合, 当组合价差低于指定值时, 买入; 当组合价差高于指定值时, 卖出.
                        <span id='collapse_arrow' class="glyphicon glyphicon-menu-down pull-right"></span>
                    </a>
                </h4>
            </div>
            <div id="collapse" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingOne">
                <ul class="list-group" style="font-size:12px;">
                    <li class="list-group-item">
                        <div class="row">
                            <div class="col col-xs-12">
                                <b>详细说明:</b>
                            </div>
                        </div>
                        <div class="row">
                            <div class="col col-xs-3">
                                <p class="pull-right">
                                    下单:
                                </p>
                            </div>
                            <div class="col col-xs-9">
                                <p>设置两个价格关联的合约,这里记为A,B。</p>
                                <p>设置买开、卖平的期望价差点,以及卖开、买平的期望价差点。</p>
                                <p>监控价格到达指定价差位置,立即采用对手加价追单策略下单。</p>
                            </div>
                        </div>
                        <div class="row">
                            <div class="col col-xs-3">
                                <span class="pull-right">
                                    挂单
                                </span>
                            </div>
                            <div class="col col-xs-9">
                                <p>挂单后若不成交、或部分成交,则立即撤单,以剩余手数继续下单。</p>
                            </div>
                        </div>
                    </li>
                    <li class="list-group-item">
                        <div class="row">
                            <div class="col col-xs-12">
                                <b>源代码:</b>
                            </div>
                        </div>
                        <div class="row">
                            <div class="col col-xs-12" id="code_container">
                                <pre><code class="javascript"></code></pre>
                            </div>
                        </div>
                    </li>
                </ul>
            </div>
        </div>
    </div>

    <script type="text/javascript" src="assets/jquery.min.js"></script>
    <script type="text/javascript" src="assets/bootstrap/js/bootstrap.min.js"></script>
    <script type="text/javascript" src="assets/highlight/highlight.pack.js"></script>
    <script type="text/javascript" src="assets/noty.js"></script>
    <!-- del:start -->
    <script type="text/javascript" src="libs/modules/utils.js"></script>
    <script type="text/javascript" src="libs/modules/color.js"></script>
    <script type="text/javascript" src="libs/modules/event.js"></script>
    <script type="text/javascript" src="libs/modules/datamanager.js"></script>
    <script type="text/javascript" src="libs/modules/indDefineCtx.js"></script>
    <script type="text/javascript" src="libs/modules/indRunCtx.js"></script>
    <script type="text/javascript" src="libs/modules/tamanager.js"></script>
    <script type="text/javascript" src="libs/modules/taskmanager.js"></script>
    <script type="text/javascript" src="libs/modules/websocket.js"></script>
    <!-- del:end -->

    <script type="text/javascript" src="libs/func/basefuncs.js"></script>
    <script type="text/javascript" src="libs/tqsdk.js"></script>
    <script type="text/javascript" id='TRADE-CODE'>
        const TQ = new TQSDK();

        function* TaskQuote() {
            let quoteA = TQ.GET_QUOTE(TQ.UI.symbolA);
            let quoteB = TQ.GET_QUOTE(TQ.UI.symbolB);
            while (true) {
                let result = yield {
                    QUOTE_UPDATED: function () { return TQ.IS_CHANGING(quoteA) || TQ.IS_CHANGING(quoteB); },
                    SYMBOL_CHANGED_A: TQ.ON_CHANGE('symbolA'),
                    SYMBOL_CHANGED_B: TQ.ON_CHANGE('symbolB'),
                };
                if (result.SYMBOL_CHANGED_A || result.SYMBOL_CHANGED_B) {
                    quoteA = TQ.GET_QUOTE(TQ.UI.symbolA);
                    quoteB = TQ.GET_QUOTE(TQ.UI.symbolB);
                }
                if (quoteA && quoteB) {
                    TQ.UI.spread_ask = quoteA.ask_price1 - quoteB.bid_price1;
                    TQ.UI.spread_bid = quoteA.bid_price1 - quoteB.ask_price1;
                }
            }
        }

        function* TaskSpread(C) {
            let quoteA = TQ.GET_QUOTE(TQ.UI.symbolA);
            let quoteB = TQ.GET_QUOTE(TQ.UI.symbolB);

            TQ.SET_STATE('START');

            let short_volume = 0, long_volume = 0;

            let volume = parseInt(TQ.UI.volume);
            let order_a = { symbol: TQ.UI.symbolA, volume};
            let order_b = { symbol: TQ.UI.symbolB, volume};
            let buy_open = { direction: 'BUY', offset: 'OPEN', price_field: 'ask_price1' };
            let sell_close = { direction: 'SELL', offset: 'CLOSE', price_field: 'bid_price1' };
            let sell_open = { direction: 'SELL', offset: 'OPEN', price_field: 'bid_price1' };
            let buy_close = { direction: 'BUY', offset: 'CLOSE', price_field: 'ask_price1' };

            let task_a, task_b;
            while (true) {
                let result = yield {
                    USER_CLICK_STOP: TQ.ON_CLICK('STOP'),
                    BUY_OPEN: function() {return quoteA.ask_price1 - quoteB.bid_price1 < TQ.UI.price_buy_open + 0.00001 && long_volume == 0},
                    SELL_CLOSE: function() {return quoteA.bid_price1 - quoteB.ask_price1 > TQ.UI.price_sell_close - 0.00001 && long_volume > 0},
                    SELL_OPEN: function() {return quoteA.bid_price1 - quoteB.ask_price1 > TQ.UI.price_sell_open - 0.00001 && short_volume == 0},
                    BUY_CLOSE: function() {return quoteA.ask_price1 - quoteB.bid_price1 < TQ.UI.price_buy_close + 0.00001 && short_volume > 0},
                };
                if (result.USER_CLICK_STOP)
                    break;
                if(result.BUY_OPEN){
                    task_a = TQ.START_TASK(TaskOrder, Object.assign({}, order_a, buy_open));
                    task_b = TQ.START_TASK(TaskOrder, Object.assign({}, order_b, sell_open));
                    long_volume = volume;
                }
                if(result.SELL_CLOSE){
                    task_a = TQ.START_TASK(TaskOrder, Object.assign({}, order_a, sell_close));
                    task_b = TQ.START_TASK(TaskOrder, Object.assign({}, order_b, buy_close));
                    long_volume = 0;
                }
                if(result.SELL_OPEN){
                    task_a = TQ.START_TASK(TaskOrder, Object.assign({}, order_a, sell_open));
                    task_b = TQ.START_TASK(TaskOrder, Object.assign({}, order_b, buy_open));
                    short_volume = volume;
                }
                if(result.BUY_CLOSE){
                    task_a = TQ.START_TASK(TaskOrder, Object.assign({}, order_a, buy_close));
                    task_b = TQ.START_TASK(TaskOrder, Object.assign({}, order_b, sell_close));
                    short_volume = 0;
                }
                let wait_subtask_finish = yield {
                  USER_CLICK_STOP: TQ.ON_CLICK('STOP'),
                  ANY_TASK_ERROR: function() {return task_a.return=="error" || task_b.return=="error";},
                  BOTH_TASK_FINISH: function() {return task_a.stopped && task_b.stopped;},
                }
                // 错单 - 任务结束
                if (wait_subtask_finish.USER_CLICK_STOP || wait_subtask_finish.ANY_TASK_ERROR)
                    break;
            }
            // 任务结束
            TQ.STOP_TASK(task_a);
            TQ.STOP_TASK(task_b);
            TQ.CANCEL_ORDER();
            TQ.SET_STATE('STOP');
        }

        function* TaskOrder(order_params) {
            var quote = TQ.GET_QUOTE(order_params.symbol);
            order_params.limit_price = quote[order_params.price_field];
            var order = TQ.INSERT_ORDER(order_params);

            while (true) {
                let result = yield {
                    ORDER_TRADE: function () { return order.volume_orign > order.volume_left; },
                    PRICE_CHANGED: function () {
                        var new_price  = order_params.direction === 'SELL' ? quote.bid_price1 : quote.ask_price1;
                        return new_price !== order_params.limit_price;
                    },
                };
                // 错单
                if (order.status == 'FINISHED' && order.exchange_order_id == '')
                    return 'error';
                if (order.volume_left == 0)
                    break;
                if (result.PRICE_CHANGED && order.status == "ALIVE")
                    TQ.CANCEL_ORDER(order);
                let wait_cancel = yield {
                    ORDER_CANCELED: function () { return order.status == 'FINISHED'; },
                };
                if (order.volume_left > 0) {
                    order_params.limit_price = order_params.direction === 'SELL' ? quote.bid_price1 : quote.ask_price1;
                    order_params.volume = order.volume_left;
                    order = TQ.INSERT_ORDER(order_params);
                }
            }
        }

        $(function () {
            TQ.START_TASK(TaskQuote);
            // 开始交易按钮
            $('button.START').on('click', function () {
                TQ.START_TASK(TaskSpread);
            });
        });
    </script>
</body>

</html>