# Strategy Setup
Tips
- The content of this trading strategy is not an investment advice. It is for learning purposes only.
# Strategy Introduction
Contruct a Double Moving Averaging Strategy.
That is, using the 1 minute candlestick of an underlying stock, to calculate two moving averages of different periods, MA1 and MA3. The values of MA1 and MA3 are tracked to determine the timing of buying and selling.
When MA1 >= MA3, the underlying stock is judged to be strong and the market is considered to be a bull market, which shows a long signal.
When MA1 < MA3, the underlying stock is judged to be weak and the market is considered to be a bear market, which shows a short signal.
# Flow Chart
# Code Sample
- Example
from futu import *
############################ Global Variables ############################
FUTU_OPEND_ADDRESS = '127.0.0.1' # OpenD listening address
FUTU_OPEND_PORT = 11111 # OpenD listening port
TRADING_ENVIRONMENT = TrdEnv.SIMULATE # Trading environment: REAL / SIMULATE
TRADING_MARKET = TrdMarket.HK # Transaction market authority, used to filter accounts
TRADING_PWD = '123456' # Trading password, used to unlock trading for real trading environment
TRADING_PERIOD = KLType.K_1M # Underlying trading time period
TRADING_SECURITY = 'HK.00700' # Underlying trading security code
FAST_MOVING_AVERAGE = 1 # Parameter for fast moving average
SLOW_MOVING_AVERAGE = 3 # Parameter for slow moving average
quote_context = OpenQuoteContext(host=FUTU_OPEND_ADDRESS, port=FUTU_OPEND_PORT) # Quotation context
trade_context = OpenSecTradeContext(filter_trdmarket=TRADING_MARKET, host=FUTU_OPEND_ADDRESS, port=FUTU_OPEND_PORT, security_firm=SecurityFirm.FUTUSECURITIES) # Trading context. It must be consistent with the underlying varieties.
# Unlock trade
def unlock_trade():
if TRADING_ENVIRONMENT == TrdEnv.REAL:
ret, data = trade_context.unlock_trade(TRADING_PWD)
if ret != RET_OK:
print('Unlock trade failed: ', data)
return False
print('Unlock Trade success!')
return True
# Check if it is regular trading time for underlying security
def is_normal_trading_time(code):
ret, data = quote_context.get_market_state([code])
if ret != RET_OK:
print('Get market state failed: ', data)
return False
market_state = data['market_state'][0]
'''
MarketState.MORNING HK and A-share morning
MarketState.AFTERNOON HK and A-share afternoon, US opening hours
MarketState.FUTURE_DAY_OPEN HK, SG, JP futures day market open
MarketState.FUTURE_OPEN US futures open
MarketState.FUTURE_BREAK_OVER Trading hours of U.S. futures after break
MarketState.NIGHT_OPEN HK, SG, JP futures night market open
'''
if market_state == MarketState.MORNING or \
market_state == MarketState.AFTERNOON or \
market_state == MarketState.FUTURE_DAY_OPEN or \
market_state == MarketState.FUTURE_OPEN or \
market_state == MarketState.FUTURE_BREAK_OVER or \
market_state == MarketState.NIGHT_OPEN:
return True
print('It is not regular trading hours.')
return False
# Get positions
def get_holding_position(code):
holding_position = 0
ret, data = trade_context.position_list_query(code=code, trd_env=TRADING_ENVIRONMENT)
if ret != RET_OK:
print('Get holding position failed:', data)
return None
else:
for qty in data['qty'].values.tolist():
holding_position += qty
print('[Holidng Position Status] The holidng position quantity of {} is:{}'.format(TRADING_SECURITY, holding_position))
return holding_position
# Query for candlesticks, calculate moving average value and judge bull or bear
def calculate_bull_bear(code, fast_param, slow_param):
if fast_param <= 0 or slow_param <= 0:
return 0
if fast_param > slow_param:
return calculate_bull_bear(code, slow_param, fast_param)
ret, data = quote_context.get_cur_kline(code=code, num=slow_param + 1, ktype=TRADING_PERIOD)
if ret != RET_OK:
print('Get candlestick value failed: ', data)
return 0
candlestick_list = data['close'].values.tolist()[::-1]
fast_value = None
slow_value = None
if len(candlestick_list) > fast_param:
fast_value = sum(candlestick_list[1: fast_param + 1]) / fast_param
if len(candlestick_list) > slow_param:
slow_value = sum(candlestick_list[1: slow_param + 1]) / slow_param
if fast_value is None or slow_value is None:
return 0
return 1 if fast_value >= slow_value else -1
# Get ask1 and bid1 from order book
def get_ask_and_bid(code):
ret, data = quote_context.get_order_book(code, num=1)
if ret != RET_OK:
print('Get order book failed: ', data)
return None, None
return data['Ask'][0][0], data['Bid'][0][0]
# Open long positions
def open_position(code):
# Get order book data
ask, bid = get_ask_and_bid(code)
# Get quantity
open_quantity = calculate_quantity()
# Check whether buying power is enough
if is_valid_quantity(TRADING_SECURITY, open_quantity, ask):
# Place order
ret, data = trade_context.place_order(price=ask, qty=open_quantity, code=code, trd_side=TrdSide.BUY,
order_type=OrderType.NORMAL, trd_env=TRADING_ENVIRONMENT,
remark='moving_average_strategy')
if ret != RET_OK:
print('Open position failed: ', data)
else:
print('Maximum quantity that can be bought less than transaction quantity.')
# Close position
def close_position(code, quantity):
# Get order book data
ask, bid = get_ask_and_bid(code)
# Check quantity
if quantity == 0:
print('Invalid order quantity.')
return False
# Close position
ret, data = trade_context.place_order(price=bid, qty=quantity, code=code, trd_side=TrdSide.SELL,
order_type=OrderType.NORMAL, trd_env=TRADING_ENVIRONMENT, remark='moving_average_strategy')
if ret != RET_OK:
print('Close position failed: ', data)
return False
return True
# Calculate order quantity
def calculate_quantity():
price_quantity = 0
# Use minimum lot size
ret, data = quote_context.get_market_snapshot([TRADING_SECURITY])
if ret != RET_OK:
print('Get market snapshot failed: ', data)
return price_quantity
price_quantity = data['lot_size'][0]
return price_quantity
# Check the buying power is enough for the quantity
def is_valid_quantity(code, quantity, price):
ret, data = trade_context.acctradinginfo_query(order_type=OrderType.NORMAL, code=code, price=price,
trd_env=TRADING_ENVIRONMENT)
if ret != RET_OK:
print('Get max long/short quantity failed: ', data)
return False
max_can_buy = data['max_cash_buy'][0]
max_can_sell = data['max_sell_short'][0]
if quantity > 0:
return quantity < max_can_buy
elif quantity < 0:
return abs(quantity) < max_can_sell
else:
return False
# Show order status
def show_order_status(data):
order_status = data['order_status'][0]
order_info = dict()
order_info['Code'] = data['code'][0]
order_info['Price'] = data['price'][0]
order_info['TradeSide'] = data['trd_side'][0]
order_info['Quantity'] = data['qty'][0]
print('[OrderStatus]', order_status, order_info)
############################ Fill in the functions below to finish your trading strategy ############################
# Strategy initialization. Run once when the strategy starts
def on_init():
# unlock trade (no need to unlock for paper trading)
if not unlock_trade():
return False
print('************ Strategy Starts ***********')
return True
# Run once for each tick. You can write the main logic of the strategy here
def on_tick():
pass
# Run once for each new candlestick. You can write the main logic of the strategy here
def on_bar_open():
# Print seperate line
print('*****************************************')
# Only trade during regular trading hours
if not is_normal_trading_time(TRADING_SECURITY):
return
# Query for candlesticks, and calculate moving average value
bull_or_bear = calculate_bull_bear(TRADING_SECURITY, FAST_MOVING_AVERAGE, SLOW_MOVING_AVERAGE)
# Get positions
holding_position = get_holding_position(TRADING_SECURITY)
# Trading signals
if holding_position == 0:
if bull_or_bear == 1:
print('[Signal] Long signal. Open long positions.')
open_position(TRADING_SECURITY)
else:
print('[Signal] Short signal. Do not open short positions.')
elif holding_position > 0:
if bull_or_bear == -1:
print('[Signal] Short signal. Close positions.')
close_position(TRADING_SECURITY, holding_position)
else:
print('[Signal] Long signal. Do not add positions.')
# Run once when an order is filled
def on_fill(data):
pass
# Run once when the status of an order changes
def on_order_status(data):
if data['code'][0] == TRADING_SECURITY:
show_order_status(data)
############################### Framework code, which can be ignored ###############################
class OnTickClass(TickerHandlerBase):
def on_recv_rsp(self, rsp_pb):
on_tick()
class OnBarClass(CurKlineHandlerBase):
last_time = None
def on_recv_rsp(self, rsp_pb):
ret_code, data = super(OnBarClass, self).on_recv_rsp(rsp_pb)
if ret_code == RET_OK:
cur_time = data['time_key'][0]
if cur_time != self.last_time and data['k_type'][0] == TRADING_PERIOD:
if self.last_time is not None:
on_bar_open()
self.last_time = cur_time
class OnOrderClass(TradeOrderHandlerBase):
def on_recv_rsp(self, rsp_pb):
ret, data = super(OnOrderClass, self).on_recv_rsp(rsp_pb)
if ret == RET_OK:
on_order_status( data)
class OnFillClass(TradeDealHandlerBase):
def on_recv_rsp(self, rsp_pb):
ret, data = super(OnFillClass, self).on_recv_rsp(rsp_pb)
if ret == RET_OK:
on_fill(data)
# Main function
if __name__ == '__main__':
# Strategy initialization
if not on_init():
print('Strategy initialization failed, exit script!')
quote_context.close()
trade_context.close()
else:
# Set up callback functions
quote_context.set_handler(OnTickClass())
quote_context.set_handler(OnBarClass())
trade_context.set_handler(OnOrderClass())
trade_context.set_handler(OnFillClass())
# Subscribe tick-by-tick, candlestick and order book of the underlying trading security
quote_context.subscribe(code_list=[TRADING_SECURITY], subtype_list=[SubType.TICKER, SubType.ORDER_BOOK, TRADING_PERIOD])
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
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
- Output
************ Strategy Starts ***********
*****************************************
[Position] The position of HK.00700 is 0
[Signal] Long signal. Open long positions.
[OrderStatus] SUBMITTING {'Code': 'HK.00700', 'Price': 597.5, 'TradeSide': 'BUY', 'Quantity': 100.0}
[OrderStatus] SUBMITTED {'Code': 'HK.00700', 'Price': 597.5, 'TradeSide': 'BUY', 'Quantity': 100.0}
[OrderStatus] FILLED_ALL {'Code': 'HK.00700', 'Price': 597.5, 'TradeSide': 'BUY', 'Quantity': 100.0}
*****************************************
[Position] The position of HK.00700 is 100.0
[Signal] Short signal. Close positions.
[OrderStatus] SUBMITTING {'Code': 'HK.00700', 'Price': 596.5, 'TradeSide': 'SELL', 'Quantity': 100.0}
[OrderStatus] SUBMITTED {'Code': 'HK.00700', 'Price': 596.5, 'TradeSide': 'SELL', 'Quantity': 100.0}
[OrderStatus] FILLED_ALL {'Code': 'HK.00700', 'Price': 596.5, 'TradeSide': 'SELL', 'Quantity': 100.0}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13