# 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

strategy-flow-chart

# Code Sample

  • Example
from futu import *

############################ Global Variables ############################
FUTU_OPEND_ADDRESS = '127.0.0.1'  # Futu OpenD listening address
FUTU_OPEND_PORT = 11111  # Futu 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('获取持仓数据失败:', data)
        return None
    else:
        for qty in data['qty'].values.tolist():
            holding_position += qty
        print('【持仓状态】 {} 的持仓数量为:{}'.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
  • 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