股价有向均线回归的趋势,利用这个特点,可以在技术指标处于超卖阶段寻找那些上涨速度快的流通性好的股票买入,形成下面的策略。策略来源quantopian。
对于市场上流通性最好的1500只股票在pipeline中先进行一波过滤:
1.年收益率排名前500
2.20日平均成交量大于100万股
3.股价高于1刀
4.rsi小于50
把过滤出的股票按照200日收益降序排列取前十名放入待买股票列表。
接下来用标普500指数的200日均线的98%做风控,指数走弱全部清仓并且不开新仓。
卖出持仓中不在待买股票列表中的标的。
买入持仓中没有且在待买股票列表中的标的,仓位按股票个数平均分配。
这个策略主要是用200日收益去向250日收益去靠拢。
几个需要注意的函数:
class roc_200days(CustomFactor):
inputs = [USEquityPricing.close]
window_length = 200+1
def compute(self, today, assets, out, close):
print(len(close[-1]))
out[:] = ((close[-1] - close[0]) / close[0]) * 100
print(len(out))
print(out)
运行时输出:
2010-01-04 21:45 PRINT 7932
2010-01-04 21:45 PRINT 7932
2010-01-04 21:45 PRINT [ 196.97048283 178.26086957 107.60141788 ..., nan 13.84790011
112.76595745]
这里面定义了一个custom_factor,close是一个201*7932的ndarray。上面这个类roc_200days的作用是不断获取200日收益。
import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import QTradableStocksUS, Q1500US
from quantopian.pipeline.factors import SimpleMovingAverage, RSI, CustomFactor, Returns, Latest
from quantopian.algorithm import attach_pipeline, pipeline_output
class roc_200days(CustomFactor):
inputs = [USEquityPricing.close]
window_length = 200+1
def compute(self, today, assets, out, close):
out[:] = ((close[-1] - close[0]) / close[0]) * 100
def initialize(context):
set_commission(commission.PerTrade(cost=0.00))
set_slippage(slippage.FixedSlippage(spread=0.00))
set_long_only()
schedule_function(is_positive_trend, date_rules.week_start(), time_rules.market_open(hours=0, minutes=59), half_days=False)
schedule_function(rebalance_sell, date_rules.week_start(), time_rules.market_open(hours=1), half_days=False)
schedule_function(rebalance_buy, date_rules.week_start(), time_rules.market_open(hours=2), half_days=False)
schedule_function(my_record_vars, date_rules.every_day(), time_rules.market_close(), half_days=False)
algo.attach_pipeline(make_pipeline(), 'my_pipeline')
def make_pipeline():
TOTAL_STOCKS = 500
returns_1_yr = Returns(window_length = 252)
base_universe = returns_1_yr.top(TOTAL_STOCKS, mask = Q1500US())
avg_volume = SimpleMovingAverage(inputs=[USEquityPricing.volume],window_length=20)
filter_volume = avg_volume > 1000000
last_price = Latest(inputs=[USEquityPricing.close], window_length=1)
filter_price = last_price > 1
rsi = RSI(inputs=[USEquityPricing.close], window_length=3)
filter_overbought = rsi < 50
roc = roc_200days()
stocks_to_trade = base_universe & filter_volume & filter_price & filter_overbought
return Pipeline(
columns = {
'stocks': stocks_to_trade,
'avg_volume': avg_volume,
'roc': roc,
},
screen = (stocks_to_trade)
)
def before_trading_start(context, data):
context.my_output = pipeline_output('my_pipeline')
context.candidates = context.my_output.sort_values('roc', ascending=False).head(10).index.tolist()
def is_positive_trend(context, data):
can_trade = False
spy = symbol('SPY')
price_history = data.history(spy, fields='close', bar_count=200, frequency='1d')
context.spy_close = data.current(spy,'close')
context.sma200 = price_history.mean()
context.sma200_buffered = context.sma200 * 0.98
if context.spy_close > context.sma200_buffered:
can_trade = True
context.is_trend = can_trade
def rebalance_sell(context, data):
if not context.is_trend:
empty_bags(context, data)
else:
for security in context.portfolio.positions:
if security not in context.candidates and data.can_trade(security):
order_target_percent(security, 0)
def rebalance_buy(context, data):
if get_open_orders():
log.info("Unexpected open orders: " + str(len(context.portfolio.positions)))
print_orders()
if context.is_trend is False:
return
needed_cash = get_cash_amount(context, data)
for security in context.candidates:
if security not in context.portfolio.positions and data.can_trade(security):
if(needed_cash < context.portfolio.cash):
order_value(security, needed_cash)
def my_record_vars(context, data):
record(spy=context.spy_close, sma200=context.sma200, sma200_buffered=context.sma200_buffered)
def get_cash_amount(context, data):
weight = 0
num_stocks = 0
for security in context.candidates:
if security not in context.portfolio.positions and data.can_trade(security):
if data.can_trade(security):
num_stocks = num_stocks + 1
if num_stocks > 0:
weight = 1.0 / num_stocks
spend = context.portfolio.cash * weight
return spend
def empty_bags(context, data):
for security in context.portfolio.positions:
if data.can_trade(security):
order_target_percent(security, 0)
def print_orders():
open_orders = get_open_orders()
if len(open_orders) == 0:
return
for security, orders in open_orders.iteritems():
for order in orders:
log.info("Active order for " + str(order.amount) + " shares of " + str(security))