有哪些量化交易策略?
我分享几个比较实用的策略,现在依旧有效,优化的好,能达到回测80%的收益。
一、小市值策略
小市值的核心就是股票池中选择市值最小的并且基本面没太大问题的股票,这类股票价格波动大,有较大的价值发现空间;涨多了调出股票池,跌到止损线止损卖出,持仓数量不足则买入,来实现小波段高抛低吸。
通过择时和风控策略来实现控制回撤。
回测数据如下:

*回测数据只作测试用,不代表未来实际收益
1、策略初始化配置
定义了可交易日、市值范围、股票池股票数量、个股止损比例、空仓时持有的货币基金
g.trading_signal = True # 是否为可交易日
g.min_mv = 3 # 股票最小市值(单位:亿)
g.max_mv = 1000 # 股票最大市值
g.stock_num = 4 # 目标持仓股票数
g.pass_months = [1, 4] # 指定空仓月份,如1月、4月
g.stoploss_limit = 0.07 # 个股止损比例7%
2、选股逻辑
结合基本面和行情选股
(1)剔除停牌、ST、退市、科创北交、次新股以及涨跌停无法下单的股票
filtered_stocks = []
for stock in stock_list:
if stock.paused:
continue
if stock.is_st:
continue
if '退' in stock.name:
continue
if stock.startswith('30') or stock.startswith('68') or stock.startswith('8') or stock.startswith('4'):
continue
if stock not in context.portfolio.positions and last_prices[stock][-1] >= stock.high_limit:
continue
if stock not in context.portfolio.positions and last_prices[stock][-1] <= stock.low_limit:
continue
start_date = get_security_info(stock).start_date
if context.previous_date - start_date < timedelta(days=375):
continue
filtered_stocks.append(stock)
(2)筛选基本面良好的股票
市值在3-1000亿,母公司净利润与公司净利润都为正,营收不低于1亿
market_cap.between(g.min_mv, g.max_mv),
np_parent_company_owners > 0,
net_profit > 0,
operating_revenue > 1e8
(3)根据大盘均线来动态调整股票池数量
通过大盘10日均线与最新收盘价的差值来代表市场的温度,200以上,表示价格显著高于均线,市场过热,减少持仓数量;-200以下,表示价格显著低于均线,市场超跌,增加持仓数量
...
if diff >= 500:
result = 3
elif 200 <= diff < 500:
result = 3
elif -200 <= diff < 200:
result = 4
elif -500 <= diff < -200:
result = 5
else:
result = 6
(4)1,4月空仓,规避风险
if month in g.pass_months:
return False
else:
return True
3、调仓逻辑
每周固定时间调仓,在调仓之前进行止盈止损,当收益翻倍时止盈,当价格跌破止损线时止损
(1)止盈止损
# 当收益翻倍时止盈
if price >= avg_cost * 2
# 当价格跌破止损线时止损 7%
elif price < avg_cost * (1 - g.stoploss_limit):
...
(2)动态调仓
选择市值最小的股票放在目标股票池,剔除持仓中不在目标股票池的股票并卖出,买入新进目标股票池的股票,达到目标持仓数量
sell_list = [stock for stock in g.hold_list if (stock not in g.target_list)
hold_list = [stock for stock in g.hold_list if (stock in g.target_list)
buy_list = [stock for stock in g.target_list if stock not in g.hold_list]
buy_security(context, buy_list, len(buy_list))
(3)买卖时校验边界情况
不能卖:停牌、不在持仓中、持仓数量为0
不能买:停牌、已持仓
if action == "sell":
if security.paused:
log.info("无法执行卖出操作:股票 {} 停牌中".format(security))
return False
if security not in context.portfolio.positions:
log.info("无法执行卖出操作:股票 {} 不在持仓中".format(security))
return False
if context.portfolio.positions[security].total_amount <= 0:
log.info("无法执行卖出操作:股票 {} 持仓数量为0".format(security))
return False
return True
elif action == "buy":
if security.paused:
log.info("无法执行买入操作:股票 {} 停牌中".format(security))
return False
# 如果已持有,则不执行买入操作
if security in context.portfolio.positions and context.portfolio.positions[security].total_amount > 0:
log.info("无法执行买入操作:股票 {} 已持仓".format(security))
return False
return True
二、首板低开
策略的核心是选择在低位、首次涨停后次日低开的股票,开盘时买入,上午有盈利就卖出,没有就拿到尾盘在卖出;策略优点在于选择的股票在低位,风险较低,并且刚启动首板,有大资金介入,第二天能快速跑路。
回测数据如下:

*回测数据只作测试用,不代表未来实际收益
1、策略初始化配置
上午开盘买入,上午有盈利就卖出,尾盘全部卖出
run_daily(buy, '09:30') # 开盘买入
run_daily(sell, '11:28') # 上午止盈
run_daily(sell, '14:50') # 下午止损
2、买入逻辑
买入过滤条件有三个,一是排除10天内连续涨停的股票,二是股票在60天内要在相对低位,三是要低开3%-4%
(1)基础过滤股票池,过滤科创北交、上市不超过250、停牌、ST股票
def prepare_stock_list(date):
initial_list = get_all_securities('stock', date).index.tolist()
initial_list = filter_kcbj_stock(initial_list)
initial_list = filter_new_stock(initial_list, date)
initial_list = filter_st_stock(initial_list, date)
initial_list = filter_paused_stock(initial_list, date)
return initial_list
(2)排除10天内连续涨停的股票
# 获取非连板涨停的股票
ccd = get_continue_count_df(hl_list, date, 10)
lb_list = list(ccd.index)
stock_list = [s for s in hl_list if s not in lb_list]
(2.1)计算连板数
# 计算连板数
def get_continue_count_df(hl_list, date, watch_days):
df = pd.DataFrame()
for d in range(2, watch_days+1): # 从2天开始,一直到观察周期结束
HLC = get_hl_count_df(hl_list, date, d) # 计算连板数
CHLC = HLC[HLC['count'] == d] # 筛选出连续d天涨停的股票
df = df.append(CHLC) # 记录连续d天涨停的股票
stock_list = list(set(df.index))
ccd = pd.DataFrame()
for s in stock_list:
tmp = df.loc[[s]]
if len(tmp) > 1:
M = tmp['count'].max() # 计算最大连板数
tmp = tmp[tmp['count'] == M]
ccd = ccd.append(tmp)
if len(ccd) != 0:
ccd = ccd.sort_values(by='count', ascending=False) # 按照连板数降序排列
return ccd
(2.2)计算涨停数
# 计算涨停数
def get_hl_count_df(hl_list, date, watch_days):
# 获取watch_days长度(观察周期)的行情数据
# 包含low(最低价)、close(收盘价)、high_limit(涨停价)三个字段
df = get_price(hl_list, end_date=date, frequency='daily', fields=['low','close','high_limit'], count=watch_days, panel=False, fill_paused=False, skip_paused=False)
df.index = df.code # 将股票代码设为索引
hl_count_list = [] # 普通涨停天数
extreme_hl_count_list = [] # 一字涨停天数
for stock in hl_list:
df_sub = df.loc[stock] # 获取单只股票数据
hl_days = df_sub[df_sub.close==df_sub.high_limit].high_limit.count() # 计算普通涨停天数(收盘价=涨停价)
extreme_hl_days = df_sub[df_sub.low==df_sub.high_limit].high_limit.count() # 计算一字涨停天数(最低价=涨停价)
hl_count_list.append(hl_days) # 记录普通涨停天数
extreme_hl_count_list.append(extreme_hl_days) # 记录一字涨停天数
#创建df记录
df = pd.DataFrame(index=hl_list, data={'count':hl_count_list, 'extreme_count':extreme_hl_count_list})
return df
(3)选取60天内在相对低位的股票
rpd = get_relative_position_df(stock_list, date, 60)
rpd = rpd[rpd['rp'] <= 0.5]
stock_list = list(rpd.index)
(3.1)计算相对位置
# 计算股票处于一段时间内相对位置
def get_relative_position_df(stock_list, date, watch_days):
if len(stock_list) != 0:
df = get_price(stock_list, end_date=date, fields=['high', 'low', 'close'], count=watch_days, fill_paused=False, skip_paused=False, panel=False).dropna()
close = df.groupby('code').apply(lambda df: df.iloc[-1,-1]) # 计算最后一天的收盘价
high = df.groupby('code').apply(lambda df: df['high'].max()) # 计算一段时间内的最高价
low = df.groupby('code').apply(lambda df: df['low'].min()) # 计算一段时间内的最低价
result = pd.DataFrame()
result['rp'] = (close-low) / (high-low) # 计算相对位置
return result
else:
return pd.DataFrame(columns=['rp'])
(4)选取低开3%-4%的股票
df = get_price(stock_list, end_date=date, frequency='daily', fields=['close'], count=1, panel=False, fill_paused=False, skip_paused=True).set_index('code') if len(stock_list) != 0 else pd.DataFrame()
df['open_pct'] = [current_data[s].day_open/df.loc[s, 'close'] for s in stock_list]
df = df[(0.96 <= df['open_pct']) & (df['open_pct'] <= 0.97)]
3、卖出逻辑
卖出时段有两个,一是上午止盈卖出,二是下午只要是不涨停就全卖出
(1)上午不涨停,并且大于我的持仓成本就止盈卖出
if ((context.portfolio.positions[s].closeable_amount != 0) and (current_data[s].last_price < current_data[s].high_limit) and (current_data[s].last_price > context.portfolio.positions[s].avg_cost)):
order_target_value(s, 0)
print( '止盈卖出')
(2)不涨停就全卖出
if ((context.portfolio.positions[s].closeable_amount != 0) and (current_data[s].last_price < current_data[s].high_limit)):
order_target_value(s, 0)
print( '全部卖出')
三、红利价值
这个量化策略的核心在于选取过去三年平均股息率最高的股票,在每年的5月进行调仓,买入卖出,一年只调仓一次,年化收益比沪深300强,适合稳健的价值投资者。
回测数据如下:

*回测数据只作测试用,不代表未来实际收益
1、策略初始化配置
定义了运行周期、基础股票池、持股数
# 设置策略
run_monthly(handle_trader, 1, '9:45')
# 设置参数
g.index = '000300.XSHG' #投资指数
g.num = 1 #选股数
g.stocks = [] #股票池
2、选股逻辑
(1)基本面筛选
主要指标有:市净率大于0, 市盈率大于0,市现率大于0,市净率需大于市盈率的15%
sdf = get_fundamentals(query(
valuation.code,
valuation.market_cap, #单位,亿元
).filter(
valuation.code.in_(stocks),
# 市净率大于0(排除净资产为负的公司)
valuation.pb_ratio > 0,
# 市盈率大于0(排除亏损企业)
valuation.pe_ratio > 0,
# 市现率大于0(现金流为正)
valuation.pcf_ratio > 0,
# 市净率需大于市盈率的15%
valuation.pb_ratio > 0.15*valuation.pe_ratio,
)).dropna().set_index('code')
(2)计算最近三年平均股息率
最重要的指标:最近三年平均股息率
dt_3y = context.current_dt.date() - dt.timedelta(days=3*365)
dt_now = context.current_dt.date()
ddf = finance.run_query(query(
finance.STK_XR_XD.code,
finance.STK_XR_XD.company_name,
finance.STK_XR_XD.board_plan_pub_date,
finance.STK_XR_XD.bonus_amount_rmb, #单位,万元
).filter(
finance.STK_XR_XD.code.in_(stocks),
finance.STK_XR_XD.board_plan_pub_date > dt_3y,
finance.STK_XR_XD.board_plan_pub_date < dt_now,
finance.STK_XR_XD.bonus_amount_rmb > 0
)).dropna()
stocks = list(set(ddf.code))
# 累计分红
divy = pd.Series(data=zeros(len(stocks)), index=stocks)
for k in ddf.index:
s = ddf.code[k]
divy[s] += ddf.bonus_amount_rmb[k]
# 建立数据表
sdf = sdf.reindex(stocks)
sdf['div_3y'] = divy
# 计算股息率
sdf['div_ratio'] = 1e-2 * sdf.div_3y / sdf.market_cap
3、调仓逻辑
选取最近三年平均股息率最高的股票,每年5月进行调仓,卖出不在目标股票池的股票,买入新进目标股票池的股票
# 按年更新
if context.current_dt.month in [5]:
g.stocks = choice_stocks(context, g.index, g.num)
# 卖出
cdata = get_current_data()
for s in context.portfolio.positions:
if s not in g.stocks and not cdata[s].paused:
log.info('sell', s, cdata[s].name)
order_target(s, 0)
# 买进
position = 0.99*context.portfolio.total_value / max(1, len(g.stocks))
for s in g.stocks:
if s not in context.portfolio.positions and not cdata[s].paused and\
context.portfolio.available_cash > position:
log.info('buy', s, cdata[s].name)
order_value(s, position)
以上的三个策略对于新入门量化的小白来说比较友好,容易上手。