Skip to content

Commit 3f82be1

Browse files
committed
add multi signals for tv strategy
1 parent 5e672d1 commit 3f82be1

File tree

5 files changed

+410
-1
lines changed

5 files changed

+410
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
from howtrader.app.tradingview.template import TVTemplate
2+
from howtrader.app.tradingview.engine import TVEngine
3+
from howtrader.trader.object import TickData, TradeData, OrderData, ContractData, Product
4+
from typing import Optional
5+
from howtrader.event import Event, EVENT_TIMER
6+
from decimal import Decimal
7+
from howtrader.trader.utility import round_to
8+
from random import uniform
9+
from howtrader.trader.object import Direction
10+
11+
class BestLimitMultiTVSignalsStrategy(TVTemplate):
12+
"""Place Best Limit order for TV strategy for multi signals
13+
14+
split a large order to small order, and use best limit price to place order automatically until it filled. For more detail, read the codes below.
15+
16+
使用最优价格(买一/卖一)去下限价单,把大单拆成小单,不断去循环下单,直到下单完成。
17+
"""
18+
19+
author: str = "51bitquant"
20+
21+
# the order volume you want to trade, if you trade BTCUSDT, the volume is BTC amount, if you set zero, will use from TV or other signal.
22+
# 订单的数量,如果你是交易BTCUSDT, 这个数量是BTC的数量, 如果设置为零,那么交易使用会使用来自tradingview或则其他第三方的信号
23+
order_volume: float = 0.0
24+
25+
# place max order volume per order 单次最大的下单数量.
26+
min_volume_per_order: float = 0.0
27+
max_volume_per_order: float = 0.0
28+
29+
parameters: list = ["order_volume","min_volume_per_order", "max_volume_per_order"]
30+
31+
def __init__(
32+
self,
33+
tv_engine: TVEngine,
34+
strategy_name: str,
35+
tv_id:str,
36+
vt_symbol: str,
37+
setting: dict,
38+
) -> None:
39+
""""""
40+
super().__init__(tv_engine, strategy_name, tv_id, vt_symbol, setting)
41+
self.last_tick: Optional[TickData] = None
42+
self.orders: list = []
43+
self.order_price: float = 0
44+
45+
self.target_volume: Decimal = Decimal("0") # trade volume target 需要交易的数量.
46+
self.traded_volume: Decimal = Decimal("0") # have already traded volume 已经交易的数量
47+
self.direction: Optional[Direction] = None #
48+
49+
self.contract: Optional[ContractData] = tv_engine.main_engine.get_contract(vt_symbol)
50+
51+
self.signals = [] # store the signals.
52+
53+
def on_init(self) -> None:
54+
"""
55+
Callback when strategy is inited.
56+
"""
57+
self.write_log("strategy inited")
58+
59+
def on_start(self) -> None:
60+
"""
61+
Callback when strategy is started.
62+
"""
63+
self.write_log("strategy started")
64+
65+
def on_stop(self):
66+
"""
67+
Callback when strategy is stopped.
68+
"""
69+
self.write_log("strategy stop")
70+
71+
def on_tick(self, tick: TickData):
72+
""""""
73+
self.last_tick = tick
74+
75+
if self.direction == Direction.LONG:
76+
if len(self.orders) == 0:
77+
self.buy_best_limit()
78+
elif self.order_price != self.last_tick.bid_price_1:
79+
self.cancel_all()
80+
elif self.direction == Direction.SHORT:
81+
if len(self.orders) == 0:
82+
self.sell_best_limit()
83+
elif self.order_price != self.last_tick.ask_price_1:
84+
self.cancel_all()
85+
else:
86+
87+
if len(self.signals) > 0:
88+
signal = self.signals.pop(0)
89+
self.resolve_signal(signal)
90+
91+
def resolve_signal(self, signal: dict) -> None:
92+
action = signal.get('action', None)
93+
if action is None:
94+
self.write_log("the signal doesn't contain action: long/short/exit")
95+
return None
96+
97+
action = action.lower() # to lowercase
98+
if self.contract is None:
99+
self.write_log('contract is None, did you connect to the exchange?')
100+
return None
101+
102+
if self.order_volume > 0:
103+
v = str(self.order_volume)
104+
volume = round_to(Decimal(v), self.contract.min_volume)
105+
else:
106+
v = signal.get('volume', None)
107+
if v is None:
108+
self.write_log("Signal missing volume from signal for placing order volume.")
109+
return None
110+
volume = round_to(Decimal(str(v)), self.contract.min_volume)
111+
112+
volume = abs(volume)
113+
self.traded_volume = Decimal("0")
114+
115+
if action == 'long':
116+
if self.pos < 0:
117+
self.target_volume = volume + abs(self.pos)
118+
else:
119+
self.target_volume = volume
120+
self.direction = Direction.LONG
121+
122+
elif action == 'short':
123+
if self.pos > 0:
124+
self.target_volume = volume + self.pos
125+
else:
126+
self.target_volume = volume
127+
self.direction = Direction.SHORT
128+
129+
elif action == 'exit':
130+
if abs(self.pos) < self.contract.min_volume:
131+
self.write_log(f"ignore exit signal, current pos: {self.pos}")
132+
return None
133+
134+
self.target_volume = abs(self.pos)
135+
if self.pos > 0:
136+
self.direction = Direction.SHORT
137+
else:
138+
self.direction = Direction.LONG
139+
else:
140+
pass
141+
# extend your signal here.
142+
143+
def on_signal(self, signal: dict) -> None:
144+
"""
145+
the signal contains
146+
"""
147+
self.write_log(f"received signal: {signal}")
148+
self.signals.append(signal)
149+
150+
def on_order(self, order: OrderData) -> None:
151+
"""
152+
Callback of new order data update.
153+
"""
154+
if not order.is_active():
155+
try:
156+
# if order is not active, then remove it from self.orders
157+
self.orders.remove(order.vt_orderid)
158+
self.order_price = 0
159+
except Exception:
160+
pass
161+
162+
def on_trade(self, trade: TradeData):
163+
""""""
164+
self.traded_volume += trade.volume
165+
166+
if self.traded_volume >= self.target_volume:
167+
self.write_log(f"algo trading finished: traded_volume:{self.traded_volume},target_volume:{self.target_volume}")
168+
self.traded_volume = Decimal("0")
169+
self.target_volume = Decimal("0")
170+
self.direction = None
171+
172+
def buy_best_limit(self) -> None:
173+
""""""
174+
volume_left = self.target_volume - self.traded_volume
175+
rand_volume = self.generate_rand_volume()
176+
order_volume = min(rand_volume, volume_left)
177+
order_volume = round_to(order_volume, self.contract.min_volume)
178+
if order_volume < self.contract.min_volume or order_volume < 0:
179+
return None
180+
181+
self.order_price = self.last_tick.bid_price_1
182+
orderids = self.buy(Decimal(str(self.order_price)), order_volume)
183+
self.orders.extend(orderids)
184+
185+
def sell_best_limit(self) -> None:
186+
""""""
187+
volume_left = self.target_volume - self.traded_volume
188+
rand_volume = self.generate_rand_volume()
189+
order_volume = min(rand_volume, volume_left)
190+
order_volume = round_to(order_volume, self.contract.min_volume)
191+
if order_volume < self.contract.min_volume or order_volume < 0:
192+
return None
193+
194+
self.order_price = self.last_tick.ask_price_1
195+
if self.contract.product == Product.SPOT:
196+
orderids = self.sell(Decimal(str(self.order_price)), Decimal(order_volume))
197+
self.orders.extend(orderids)
198+
elif self.contract.product == Product.FUTURES:
199+
orderids = self.short(Decimal(str(self.order_price)), Decimal(order_volume))
200+
self.orders.extend(orderids)
201+
202+
def generate_rand_volume(self):
203+
""""""
204+
rand_volume = uniform(self.min_volume_per_order, self.max_volume_per_order)
205+
return Decimal(str(rand_volume))

howtrader/app/tradingview/strategies/BestLimitTVStrategy.py

+6
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,9 @@ def buy_best_limit(self) -> None:
167167
order_volume = min(rand_volume, volume_left)
168168
order_volume = round_to(order_volume, self.contract.min_volume)
169169
if order_volume < self.contract.min_volume or order_volume < 0:
170+
self.traded_volume = Decimal("0")
171+
self.target_volume = Decimal("0")
172+
self.direction = None
170173
return None
171174

172175
self.order_price = self.last_tick.bid_price_1
@@ -180,6 +183,9 @@ def sell_best_limit(self) -> None:
180183
order_volume = min(rand_volume, volume_left)
181184
order_volume = round_to(order_volume, self.contract.min_volume)
182185
if order_volume < self.contract.min_volume or order_volume < 0:
186+
self.traded_volume = Decimal("0")
187+
self.target_volume = Decimal("0")
188+
self.direction = None
183189
return None
184190

185191
self.order_price = self.last_tick.ask_price_1

0 commit comments

Comments
 (0)