策略示例
在下面我们列举一些常用的算法范例,您可以通过QFF运行,方便您快速学习和掌握QFF框架。
双均线策略
金叉死叉策略其实就是双均线策略。策略思想是:当短期均线上穿长期均线时,形成金叉,此时买入股票。 当短期均线下穿长期均线时,形成死叉,此时卖出股票。研究表明,双均线系统虽然简单,但只要严格执行,也能长期盈利。
1
2# 双均线策略,当五日均线位于十日均线上方则买入,反之卖出。
3from qff import *
4
5
6# 初始化函数,设定要操作的股票、基准等等
7def initialize(context):
8 # 定义一个全局变量, 保存要操作的股票
9 # 000002(股票:万科A)
10 g.security = '000002'
11 # 设定沪深300作为基准
12 set_benchmark('000300')
13 # 运行函数
14 run_daily(trade, 'every_bar')
15
16
17# 交易程序
18def trade(context, data):
19 security = g.security
20 # 设定均线窗口长度
21 n1 = 5
22 n2 = 10
23 # 获取股票的收盘价
24 close_data = attribute_history(security, n2+2, '1d', ['close'])
25 # 取得过去 ma_n1 天的平均价格
26 ma_n1 = close_data['close'][-n1:].mean()
27 # 取得过去 ma_n2 天的平均价格
28 ma_n2 = close_data['close'][-n2:].mean()
29 # 取得当前的现金
30 cash = context.portfolio.available_cash
31
32 # 如果当前有余额,并且n1日均线大于n2日均线
33 if ma_n1 > ma_n2:
34 # 用所有 cash 买入股票
35 order_value(security, cash)
36 # 记录这次买入
37 log.info("Buying %s" % security)
38
39 # 如果n1日均线小于n2日均线,并且目前有头寸
40 elif ma_n1 < ma_n2 and\
41 security in context.portfolio.positions.keys() and\
42 context.portfolio.positions[security].closeable_amount > 0:
43 # 全部卖出
44 order_target(security, 0)
45 # 记录这次卖出
46 log.info("Selling %s" % security)
47
48
49if __name__ == '__main__':
50 run_file(__file__)
51
52
53
MACD策略
以下是一个我们使用TALib编写的单股票MACD算法示例。
macd 是长短均线的差值,signal是macd的均线,使用macd策略有几种不同的方法,我们这里采用macd线突破signal线的判断方法。
talib是python的技术指标库,其中包含了很多150多种量化指标,所以talib是非常值得我们学习和使用的。 talib使用C语言实现,执行速度非常快,其安装方法也比较特殊,请自行搜索安装方法。
1from qff import *
2import talib as tl
3import numpy as np
4
5
6def initialize(context):
7
8 # 设置指数基准
9 set_benchmark(security="000300")
10 # 定义一个全局变量, 保存要操作的股票
11 g.s1 = "000001"
12 # 定义运行函数,每日9点50分运行
13 run_daily(market_open, run_time='09:50')
14
15 log.info("initialize : 初始化运行")
16
17
18def market_open(context):
19 log.info("market_open函数,每天运行一次...")
20 # 读取历史数据,前100天的收盘价
21 close = history(100, '1d', 'close', g.s1).values
22 # 获取当前价格
23 current_price = get_current_data(g.s1).last_price
24 # 将当前价格与历史价格合并
25 close = np.append(close, current_price)
26
27 # 用Talib计算MACD取值,得到三个时间序列数组,
28 macd, signal, hist = tl.MACD(close, 12, 26, 9)
29
30 # 如果macd从上往下跌破macd_signal
31 if macd[-1] < signal[-1] and macd[-2] > signal[-2]:
32 # 进行清仓
33 if g.s1 in context.portfolio.positions.keys():
34 order_target(g.s1, 0)
35
36 # 如果短均线从下往上突破长均线,为入场信号
37 if macd[-1] > signal[-1] and macd[-2] < signal[-2]:
38 # 满仓买入
39 order_value(g.s1, context.portfolio.available_cash)
40
41
42if __name__ == '__main__':
43 run_file(__file__, start="2021-08-27", end="2022-03-25")
44
45
小市值策略
筛选出市值介于20-30亿的股票,选取其中市值最小的三只股票,每天开盘买入,持有五个交易日,然后调仓。 等权重买入,无单只股票仓位上限控制、无止盈止损。小市值策略曾经在15年期间有非常好的收益,未来有可能还能重现。
1from qff import *
2
3
4# 初始化函数,设定要操作的股票、基准等等
5def initialize(context):
6 # 设定沪深300作为基准
7 set_benchmark('000300')
8 # 持仓数量
9 g.stock_num = 3
10 # 交易日计时器
11 g.days = 0
12 # 调仓频率
13 g.refresh_rate = 5
14
15
16def before_trading_start(context):
17 log.info("before_trading_start函数运行...")
18
19
20def check_stocks(context):
21 # 选出小市值股票
22
23 filter = {'date': context.previous_date, 'market_cap': {'$gt': 20, '$lt': 30}}
24 projection = {'market_cap': 1}
25 df = query_valuation(filter, projection)
26 df = df.sort_values('market_cap').reset_index()
27 buy_list = list(df['code'])[:g.stock_num*2]
28
29 # 过滤停牌股票
30 paused_code = get_paused_stock(buy_list, context.previous_date)
31 filter_paused = [x for x in buy_list if x not in paused_code]
32
33 return filter_paused[:g.stock_num]
34
35
36# 交易函数
37def handle_data(context, data):
38 if g.days % g.refresh_rate == 0:
39
40 # 获取持仓列表
41 sell_list = list(context.portfolio.positions.keys())
42 # 如果有持仓,则卖出
43 if len(sell_list) > 0:
44 for stock in sell_list:
45 order_target_value(stock, 0)
46
47 # 分配资金
48 if len(context.portfolio.positions) < g.stock_num:
49 Num = g.stock_num - len(context.portfolio.positions)
50 Cash = context.portfolio.available_cash / Num
51 else:
52 Cash = 0
53
54 # 选股
55 stock_list = check_stocks(context)
56
57 # 买入股票
58 for stock in stock_list:
59 if len(context.portfolio.positions.keys()) < g.stock_num:
60 order_value(stock, Cash)
61
62 # 天计数加一
63 g.days = 1
64 else:
65 g.days += 1
66
67
68if __name__ == '__main__':
69 run_file(__file__, start="2021-08-27", end="2022-03-25")
70
海龟策略
海龟交易系统是非常经典的一种策略,类似的成熟策略体系还有很多种,例如羊驼,鳄鱼等等。 关于海龟策略的原理介绍可以参照 这篇帖子 。
1
2# 海归策略
3# 2012-01-01 到 2016-03-10, ¥1000000, 分钟
4
5# 海龟策略
6
7from qff import *
8import numpy as np
9
10# ================================================================================
11# 总体回测前
12# ================================================================================
13
14
15# 总体回测前要做的事情
16def initialize(context):
17 set_params() # 1设置策参数
18 set_variables() # 2设置中间变量
19 set_backtest() # 3设置回测条件
20
21
22# 1
23# 设置策略参数
24def set_params():
25 g.security = '000001'
26 # 系统1入市的trailing date
27 g.short_in_date = 20
28 # 系统2入市的trailing date
29 g.long_in_date = 55
30 # 系统1 exiting market trailing date
31 g.short_out_date = 10
32 # 系统2 exiting market trailing date
33 g.long_out_date = 20
34 # g.dollars_per_share是标的股票每波动一个最小单位,1手股票的总价格变化量。
35 # 在国内最小变化量是0.01元,所以就是0.01×100=1
36 g.dollars_per_share = 1
37 # 可承受的最大损失率
38 g.loss = 0.1
39 # 若超过最大损失率,则调整率为:
40 g.adjust = 0.8
41 # 计算N值的天数
42 g.number_days = 20
43 # 最大允许单元
44 g.unit_limit = 4
45 # 系统1所配金额占总金额比例
46 g.ratio = 0.8
47
48
49# 2
50# 设置中间变量
51def set_variables():
52 # 初始单元
53 g.unit = 1000
54 # A list storing info of N
55 g.N = []
56 # Record the number of days for this trading system
57 g.days = 0
58 # 系统1的突破价格
59 g.break_price1 = 0
60 # 系统2的突破价格
61 g.break_price2 = 0
62 # 系统1建的仓数
63 g.sys1 = 0
64 # 系统2建的仓数
65 g.sys2 = 0
66 # 系统1执行且系统2不执行
67 g.system1 = True
68
69
70# 3
71# 设置回测条件
72def set_backtest():
73 # 作为判断策略好坏和一系列风险值计算的基准
74 set_benchmark(g.security)
75 log.set_level('info') # 设置报错等级
76
77
78'''
79================================================================================
80每天开盘前
81================================================================================
82'''
83
84
85# 每天开盘前要做的事情
86def before_trading_start(context):
87 set_slip_fee(context)
88
89
90# 4 根据不同的时间段设置滑点与手续费
91def set_slip_fee(context):
92 # 将滑点设置为0
93 set_slippage(0)
94 # 根据不同的时间段设置手续费
95 dt = context.current_dt
96
97 if dt > '2013-01-01':
98 set_order_cost(open_commission=0.0003, close_commission=0.0013, min_commission=5)
99 elif dt > '2011-01-01':
100 set_order_cost(open_commission=0.001, close_commission=0.002, min_commission=5)
101 elif dt > '2009-01-01':
102 set_order_cost(open_commission=0.002, close_commission=0.003, min_commission=5)
103 else:
104 set_order_cost(open_commission=0.003, close_commission=0.004, min_commission=5)
105
106# ================================================================================
107# 每天交易时
108# ================================================================================
109
110
111# 按分钟回测
112def handle_data(context, data):
113 dt = context.current_dt # 当前日期
114 if dt[:10] == '2020-04-20':
115 log.info(dt)
116 data = get_current_data(g.security)
117 current_price = data.last_price # 当前价格N
118 if dt[11:15] == '09:30':
119 g.days += 1
120 calculate_N() # 计算N的值
121 if g.days > g.number_days:
122 # 当前持有的股票和现金的总价值
123 value = context.portfolio.total_assets
124 # 可花费的现金
125 cash = context.portfolio.available_cash
126 if g.sys1 == 0 and g.sys2 == 0:
127 # 若损失率大于g.loss,则调整(减小)可持有现金和总价值
128 if value < (1 - g.loss) * context.portfolio.starting_cash:
129 cash *= g.adjust
130 value *= g.adjust
131
132 # 计算美元波动的价格
133 dollar_volatility = g.dollars_per_share * g.N[-1]
134 # 依本策略,计算买卖的单位
135 g.unit = value * 0.01 / dollar_volatility
136
137 # 系统1的操作
138 g.system1 = True
139 if g.sys1 == 0:
140 market_in(current_price, g.ratio * cash, g.short_in_date)
141 else:
142 stop_loss(current_price)
143 market_add(current_price, g.ratio * cash, g.short_in_date)
144 market_out(current_price, g.short_out_date)
145
146 # 系统2的操作
147 g.system1 = False
148 if g.sys2 == 0:
149 market_in(current_price, (1 - g.ratio) * cash, g.long_in_date)
150 else:
151 stop_loss(current_price)
152 market_add(current_price, (1 - g.ratio) * cash, g.long_in_date)
153 market_out(current_price, g.long_out_date)
154
155 # 5
156
157
158# 计算当前N的值
159# 输入:none
160# 输出:N的值的更新列表-list类型
161def calculate_N():
162 # 如果交易天数小于等于20天
163 if g.days <= g.number_days:
164 price = attribute_history(g.security, g.days+1, '1d', ['high', 'low', 'close'])
165 price['pre_close'] = price['close'].shift(1)
166 lst = []
167 for i in range(0, g.days):
168 h_l = price['high'][i] - price['low'][i]
169 h_c = price['high'][i] - price['pre_close'][i]
170 c_l = price['pre_close'][i] - price['low'][i]
171 # 计算 True Range
172 True_Range = max(h_l, h_c, c_l)
173 lst.append(True_Range)
174 # 计算前g.days(小于等于20)天的True_Range平均值,即当前N的值:
175 current_N = np.mean(np.array(lst))
176 g.N.append(current_N)
177
178 # 如果交易天数超过20天
179 else:
180 price = attribute_history(g.security, 2, '1d', ['high', 'low', 'close'])
181 price['pre_close'] = price['close'].shift(1)
182 h_l = price['high'][-1] - price['low'][-1]
183 h_c = price['high'][-1] - price['pre_close'][-1]
184 c_l = price['pre_close'][-1] - price['low'][-1]
185 # Calculate the True Range
186 True_Range = max(h_l, h_c, c_l)
187 # 计算前g.number_days(大于20)天的True_Range平均值,即当前N的值:
188 current_N = (True_Range + (g.number_days - 1) * g.N[-1]) / g.number_days
189 g.N.append(current_N)
190
191
192# 6
193# 入市:决定系统1、系统2是否应该入市,更新系统1和系统2的突破价格
194# 海龟将所有资金分为2部分:一部分资金按系统1执行,一部分资金按系统2执行
195# 输入:当前价格-float, 现金-float, 天数-int
196# 输出:none
197def market_in(current_price, cash, in_date):
198 # Get the price for the past "in_date" days
199 price = attribute_history(g.security, in_date, '1d', ['close'])
200 # Build position if current price is higher than highest in past
201 if current_price > max(price['close']):
202 # 计算可以买该股票的股数
203 num_of_shares = cash / current_price
204 if num_of_shares >= g.unit:
205 print("买入")
206 print(current_price)
207 print(max(price['close']))
208 if g.system1:
209 if g.sys1 < int(g.unit_limit * g.unit):
210 order(g.security, int(g.unit))
211 g.sys1 += int(g.unit)
212 g.break_price1 = current_price
213 else:
214 if g.sys2 < int(g.unit_limit * g.unit):
215 order(g.security, int(g.unit))
216 g.sys2 += int(g.unit)
217 g.break_price2 = current_price
218
219
220# 7
221# 加仓函数
222# 输入:当前价格-float, 现金-float, 天数-int
223# 输出:none
224def market_add(current_price, cash, in_date):
225 if g.system1:
226 break_price = g.break_price1
227 else:
228 break_price = g.break_price2
229 # 每上涨0.5N,加仓一个单元
230 if current_price >= break_price + 0.5 * g.N[-1]:
231 num_of_shares = cash / current_price
232 # 加仓
233 if num_of_shares >= g.unit:
234 print("加仓")
235 print(g.sys1)
236 print(g.sys2)
237 print(current_price)
238 print(break_price + 0.5 * g.N[-1])
239
240 if g.system1:
241 if g.sys1 < int(g.unit_limit * g.unit):
242 order(g.security, int(g.unit))
243 g.sys1 += int(g.unit)
244 g.break_price1 = current_price
245 else:
246 if g.sys2 < int(g.unit_limit * g.unit):
247 order(g.security, int(g.unit))
248 g.sys2 += int(g.unit)
249 g.break_price2 = current_price
250
251
252# 8
253# 离场函数
254# 输入:当前价格-float, 天数-int
255# 输出:none
256def market_out(current_price, out_date):
257 # Function for leaving the market
258 price = attribute_history(g.security, out_date, '1d', ['close'])
259 # 若当前价格低于前out_date天的收盘价的最小值, 则卖掉所有持仓
260 if current_price < min(price['close']):
261 print("离场")
262 print(current_price)
263 print(min(price['close']))
264 if g.system1:
265 if g.sys1 > 0:
266 order(g.security, -g.sys1)
267 g.sys1 = 0
268 else:
269 if g.sys2 > 0:
270 order(g.security, -g.sys2)
271 g.sys2 = 0
272
273
274# 9
275# 止损函数
276# 输入:当前价格-float
277# 输出:none
278def stop_loss(current_price):
279 # 损失大于2N,卖出股票
280 if g.system1:
281 break_price = g.break_price1
282 else:
283 break_price = g.break_price2
284 # If the price has decreased by 2N, then clear all position
285 if current_price < (break_price - 2 * g.N[-1]):
286 print("止损")
287 print(current_price)
288 print(break_price - 2 * g.N[-1])
289 if g.system1:
290 order(g.security, -g.sys1)
291 g.sys1 = 0
292 else:
293 order(g.security, -g.sys2)
294 g.sys2 = 0
295
296
297# ================================================================================
298# 每天收盘后
299# ================================================================================
300
301# 每日收盘后要做的事情(本策略中不需要)
302def after_trading_end(context):
303 return
304
305