|
| 1 | +from vnpy.trader.constant import Direction |
| 2 | +from vnpy.trader.object import TradeData, OrderData |
| 3 | +from vnpy.trader.engine import BaseEngine |
| 4 | + |
| 5 | +from ..template import AlgoTemplate |
| 6 | + |
| 7 | + |
| 8 | +class ArbitrageAlgo(AlgoTemplate): |
| 9 | + """""" |
| 10 | + |
| 11 | + display_name = "Arbitrage 套利" |
| 12 | + |
| 13 | + default_setting = { |
| 14 | + "active_vt_symbol": "", |
| 15 | + "passive_vt_symbol": "", |
| 16 | + "spread_up": 0.0, |
| 17 | + "spread_down": 0.0, |
| 18 | + "max_pos": 0, |
| 19 | + "interval": 0, |
| 20 | + } |
| 21 | + |
| 22 | + variables = [ |
| 23 | + "timer_count", |
| 24 | + "active_vt_orderid", |
| 25 | + "passive_vt_orderid", |
| 26 | + "active_pos", |
| 27 | + "passive_pos" |
| 28 | + ] |
| 29 | + |
| 30 | + def __init__( |
| 31 | + self, |
| 32 | + algo_engine: BaseEngine, |
| 33 | + algo_name: str, |
| 34 | + setting: dict |
| 35 | + ): |
| 36 | + """""" |
| 37 | + super().__init__(algo_engine, algo_name, setting) |
| 38 | + |
| 39 | + # 参数 |
| 40 | + self.active_vt_symbol = setting["active_vt_symbol"] |
| 41 | + self.passive_vt_symbol = setting["passive_vt_symbol"] |
| 42 | + self.spread_up = setting["spread_up"] |
| 43 | + self.spread_down = setting["spread_down"] |
| 44 | + self.max_pos = setting["max_pos"] |
| 45 | + self.interval = setting["interval"] |
| 46 | + |
| 47 | + # 变量 |
| 48 | + self.active_vt_orderid = "" |
| 49 | + self.passive_vt_orderid = "" |
| 50 | + self.active_pos = 0 |
| 51 | + self.passive_pos = 0 |
| 52 | + self.timer_count = 0 |
| 53 | + |
| 54 | + self.subscribe(self.active_vt_symbol) |
| 55 | + self.subscribe(self.passive_vt_symbol) |
| 56 | + |
| 57 | + self.put_parameters_event() |
| 58 | + self.put_variables_event() |
| 59 | + |
| 60 | + def on_stop(self): |
| 61 | + """""" |
| 62 | + self.write_log("停止算法") |
| 63 | + |
| 64 | + def on_order(self, order: OrderData): |
| 65 | + """""" |
| 66 | + if order.vt_symbol == self.active_vt_symbol: |
| 67 | + if not order.is_active(): |
| 68 | + self.active_vt_orderid = "" |
| 69 | + elif order.vt_symbol == self.passive_vt_symbol: |
| 70 | + if not order.is_active(): |
| 71 | + self.passive_vt_orderid = "" |
| 72 | + self.put_variables_event() |
| 73 | + |
| 74 | + def on_trade(self, trade: TradeData): |
| 75 | + """""" |
| 76 | + # 更新持仓 |
| 77 | + if trade.direction == Direction.LONG: |
| 78 | + if trade.vt_symbol == self.active_vt_symbol: |
| 79 | + self.active_pos += trade.volume |
| 80 | + else: |
| 81 | + self.passive_pos += trade.volume |
| 82 | + else: |
| 83 | + if trade.vt_symbol == self.active_vt_symbol: |
| 84 | + self.active_pos -= trade.volume |
| 85 | + else: |
| 86 | + self.passive_pos -= trade.volume |
| 87 | + |
| 88 | + # 主动腿成交,执行对冲 |
| 89 | + if trade.vt_symbol == self.active_vt_symbol: |
| 90 | + self.write_log("收到主动腿成交回报,执行对冲") |
| 91 | + self.hedge() |
| 92 | + |
| 93 | + self.put_variables_event() |
| 94 | + |
| 95 | + def on_timer(self): |
| 96 | + """""" |
| 97 | + # 定时执行算法 |
| 98 | + self.timer_count += 1 |
| 99 | + if self.timer_count < self.interval: |
| 100 | + self.put_variables_event() |
| 101 | + return |
| 102 | + self.timer_count = 0 |
| 103 | + |
| 104 | + # 继续运行前撤销所有活动中订单 |
| 105 | + if self.active_vt_orderid or self.passive_vt_orderid: |
| 106 | + self.write_log("有未成交委托,执行撤单") |
| 107 | + self.cancel_all() |
| 108 | + return |
| 109 | + |
| 110 | + # 确保主动腿和被动腿完全对冲 |
| 111 | + if (self.active_pos + self.passive_pos) != 0: |
| 112 | + self.write_log("主动腿和被动腿数量不一致,执行对冲") |
| 113 | + self.hedge() |
| 114 | + return |
| 115 | + |
| 116 | + # 确保两条套利腿的行情数据都通畅 |
| 117 | + active_tick = self.get_tick(self.active_vt_symbol) |
| 118 | + passive_tick = self.get_tick(self.passive_vt_symbol) |
| 119 | + if not active_tick or not passive_tick: |
| 120 | + self.write_log("获取某条套利腿的行情失败,无法交易") |
| 121 | + return |
| 122 | + |
| 123 | + # 计算价差 |
| 124 | + spread_bid_price = active_tick.bid_price_1 - passive_tick.ask_price_1 |
| 125 | + spread_ask_price = active_tick.ask_price_1 - passive_tick.bid_price_1 |
| 126 | + |
| 127 | + spread_bid_volume = min(active_tick.bid_volume_1, |
| 128 | + passive_tick.ask_volume_1) |
| 129 | + spread_ask_volume = min(active_tick.ask_volume_1, |
| 130 | + passive_tick.bid_volume_1) |
| 131 | + |
| 132 | + msg = f"价差盘口,买:{spread_bid_price} ({spread_bid_volume}),卖:{spread_ask_price} ({spread_ask_volume})" |
| 133 | + self.write_log(msg) |
| 134 | + |
| 135 | + # 做空条件 |
| 136 | + if spread_bid_price > self.spread_up: |
| 137 | + self.write_log("套利价差超过上限,满足做空条件") |
| 138 | + |
| 139 | + if self.active_pos > -self.max_pos: |
| 140 | + self.write_log("当前持仓小于最大持仓限制,执行卖出操作") |
| 141 | + |
| 142 | + volume = min(spread_bid_volume, |
| 143 | + self.active_pos + self.max_pos) |
| 144 | + |
| 145 | + self.active_vt_orderid = self.sell( |
| 146 | + self.active_vt_symbol, |
| 147 | + active_tick.bid_price_1, |
| 148 | + volume |
| 149 | + ) |
| 150 | + |
| 151 | + # 做多条件 |
| 152 | + elif spread_ask_price < -self.spread_down: |
| 153 | + self.write_log("套利价差超过下限,满足做多条件") |
| 154 | + |
| 155 | + if self.active_pos < self.max_pos: |
| 156 | + self.write_log("当前持仓小于最大持仓限制,执行买入操作") |
| 157 | + |
| 158 | + volume = min(spread_ask_volume, |
| 159 | + self.max_pos - self.active_pos) |
| 160 | + |
| 161 | + self.active_vt_orderid = self.buy( |
| 162 | + self.active_vt_symbol, |
| 163 | + active_tick.ask_price_1, |
| 164 | + volume |
| 165 | + ) |
| 166 | + |
| 167 | + self.put_variables_event() |
| 168 | + |
| 169 | + def hedge(self): |
| 170 | + """对冲""" |
| 171 | + tick = self.get_tick(self.passive_vt_symbol) |
| 172 | + volume = -self.active_pos - self.passive_pos |
| 173 | + |
| 174 | + if volume > 0: |
| 175 | + self.passive_vt_orderid = self.buy( |
| 176 | + self.passive_vt_symbol, |
| 177 | + tick.ask_price_1, |
| 178 | + volume |
| 179 | + ) |
| 180 | + elif volume < 0: |
| 181 | + self.passive_vt_orderid = self.sell( |
| 182 | + self.passive_vt_symbol, |
| 183 | + tick.bid_price_1, |
| 184 | + abs(volume) |
| 185 | + ) |
0 commit comments