|
|
@@ -0,0 +1,204 @@
|
|
|
+# https://www.joinquant.com/algorithm/index/edit?algorithmId=4519676386ce59c6457da930b52d0e7c&startTime=2015-07-01&endTime=2016-04-06&baseCapital=100000&frequency=day&pyVersion=2
|
|
|
+
|
|
|
+# 克隆自聚宽文章:https://www.joinquant.com/post/1079
|
|
|
+# 标题:多策略组合利器——分仓管控技术【非交易策略】
|
|
|
+# 作者:莫邪的救赎
|
|
|
+
|
|
|
+def initialize(context):
|
|
|
+ # 获取股票
|
|
|
+ df = get_fundamentals(query(
|
|
|
+ valuation.code,valuation.market_cap
|
|
|
+ ).order_by(
|
|
|
+ valuation.market_cap.asc()
|
|
|
+ ).limit(
|
|
|
+ 200
|
|
|
+ )).dropna()
|
|
|
+ g.security = list(df['code'])
|
|
|
+ g.buylist = g.security[-100:]
|
|
|
+ set_universe(g.security)
|
|
|
+
|
|
|
+ log.set_level('order', 'error')#屏蔽order warning提示
|
|
|
+ g.stocknum = 5 #仓位数
|
|
|
+ g.per_sell_stock = [] #欲卖出股票列表
|
|
|
+ g.proportion_initial_buy_cash = 0.6 #建仓资金占总资金的比例
|
|
|
+ g.proportion_cash = 0.4 #预留补仓现金比例占总资金的比例
|
|
|
+ #(0.6代表用仓位总资金的六成用于建仓,其余四成用于加仓)
|
|
|
+ initialize_position_cash(context) #初始化仓位个数及资金
|
|
|
+ # 执行函数
|
|
|
+ run_daily(buy_stocks, time='open')
|
|
|
+ run_daily(overweight_and_stop_profit, time='open')
|
|
|
+ run_daily(stop_loss, time='open')
|
|
|
+ run_daily(change_position_cash, time='after_close')
|
|
|
+
|
|
|
+def before_trading_start(context):
|
|
|
+ #获取前日持仓股票列表
|
|
|
+ g.already_hold_stock = context.portfolio.positions.keys()
|
|
|
+ log.info('buylist长度:%s',len(g.buylist))
|
|
|
+
|
|
|
+def initialize_position_cash(context):
|
|
|
+ '''
|
|
|
+ 初始化每个仓位的信息
|
|
|
+ '''
|
|
|
+ start_cash = context.portfolio.starting_cash
|
|
|
+ every_position = start_cash/g.stocknum
|
|
|
+ g.hold_temp = {'total':every_position, 'initial_buy_cash':every_position*g.proportion_initial_buy_cash, 'cash':every_position*g.proportion_cash}
|
|
|
+ g.hold = {} # 仓位信息
|
|
|
+ g.hold_stock_name = {} # 仓位对于的股票名称
|
|
|
+ for i in range(g.stocknum):
|
|
|
+ g.hold[i] = g.hold_temp.copy()
|
|
|
+ g.hold_stock_name[i] = None #仓位为空,则对应的value值为None
|
|
|
+ # log.info('init hold:',g.hold)
|
|
|
+ # log.info('hold_stock_name:',g.hold_stock_name)
|
|
|
+
|
|
|
+def buy_stocks(context):
|
|
|
+ current_data = get_current_data(g.buylist)
|
|
|
+ for stock in g.buylist:
|
|
|
+ if current_data[stock].paused == 0:#跳过停牌
|
|
|
+ for n in g.hold_stock_name.items():
|
|
|
+ if n[1] == None:
|
|
|
+ g.hold_stock_name[n[0]] = stock #标记仓位对应的股票
|
|
|
+ Cash = g.hold[n[0]]['initial_buy_cash'] #获取建仓资金
|
|
|
+ order_value(stock, Cash) #建仓
|
|
|
+ # log.info('buy: %s',stock)
|
|
|
+ g.buylist.remove(stock)
|
|
|
+ break
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ g.buylist.remove(stock)
|
|
|
+ # log.info('hold_stock_name:',g.hold_stock_name)
|
|
|
+def overweight_and_stop_profit(context):
|
|
|
+ '''
|
|
|
+ 加仓以及止盈
|
|
|
+ '''
|
|
|
+ hold_stock = context.portfolio.positions.keys()
|
|
|
+ if len(hold_stock)>0:
|
|
|
+ current_data = get_current_data(hold_stock)
|
|
|
+ for stock in hold_stock:
|
|
|
+ #跳过停牌,因为T+1。所以sellable_amount>0即跳过当日建仓的股票
|
|
|
+ if current_data[stock].paused == 0 and context.portfolio.positions[stock].sellable_amount>0:
|
|
|
+ avg_cost = context.portfolio.positions[stock].avg_cost #持仓成本
|
|
|
+ price = context.portfolio.positions[stock].price #持仓股票当前价
|
|
|
+ # 根据触发条件,对不在“欲卖出”列表的股票进行加仓,降低持仓成本
|
|
|
+ if ((price/avg_cost) <= 0.9) and (stock not in g.per_sell_stock):
|
|
|
+ log.info("overweight: %s", stock) #打印加仓股票代码
|
|
|
+ i = get_key(stock) #获取加仓股票对应的key值,get_key函数见下
|
|
|
+ Cash = get_buy_cash(i) #获取用于加仓资金,get_buy_cash函数见下
|
|
|
+ order_value(stock, Cash) #买入
|
|
|
+ # log.info('buy: %s',stock)
|
|
|
+ # 止盈
|
|
|
+ elif (price/avg_cost) >= 1.2:
|
|
|
+ order_target(stock, 0)
|
|
|
+ log.info('sell: %s', stock)
|
|
|
+ # 如果股票在“欲卖出”列表中,则将其删除
|
|
|
+ if stock in g.per_sell_stock:
|
|
|
+ g.per_sell_stock.remove(stock)
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+
|
|
|
+def get_key(stock):
|
|
|
+ '''
|
|
|
+ 获取stock在g.hold_stock_name对应的key值
|
|
|
+ '''
|
|
|
+ for n in g.hold_stock_name.items():
|
|
|
+ if n[1] == stock:
|
|
|
+ return n[0]
|
|
|
+
|
|
|
+def get_buy_cash(i):
|
|
|
+ '''
|
|
|
+ 获取g.hold中i键对应仓位的可用购买现金,
|
|
|
+ 这里设定:将剩余四成仓位分两次购买
|
|
|
+ (如需更改购买现金占比,请自行修改)
|
|
|
+ 并在现金用尽之后,将股票加入“欲卖出”股票列表,如下跌找过一定比例,则进行止损
|
|
|
+ '''
|
|
|
+ all_cash = g.hold[i]['cash'] #仓位中可用现金
|
|
|
+ total = g.hold[i]['total'] #仓位初始总资金
|
|
|
+ if all_cash/total > 0.3:
|
|
|
+ cash = all_cash*0.5
|
|
|
+ return cash
|
|
|
+ elif all_cash/total < 0.3:
|
|
|
+ will_sell_stock = g.hold_stock_name[i]
|
|
|
+ if will_sell_stock not in g.per_sell_stock:
|
|
|
+ g.per_sell_stock.append(will_sell_stock)
|
|
|
+ cash = all_cash
|
|
|
+ return cash
|
|
|
+
|
|
|
+def stop_loss(context):
|
|
|
+ '''
|
|
|
+ “欲卖出”股票列表,如下跌找过一定比例,则进行止损
|
|
|
+ '''
|
|
|
+ if len(g.per_sell_stock)>0:
|
|
|
+ current_data = get_current_data(g.per_sell_stock)
|
|
|
+ for stock in g.per_sell_stock:
|
|
|
+ if current_data[stock].paused == 0: #跳过停牌
|
|
|
+ avg_cost = context.portfolio.positions[stock].avg_cost
|
|
|
+ price = context.portfolio.positions[stock].price
|
|
|
+ if (price/avg_cost) <= 0.9:
|
|
|
+ order_target(stock, 0)
|
|
|
+ g.per_sell_stock.remove(stock)
|
|
|
+ log.info('sell: %s', stock)
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+def change_position_cash(context):
|
|
|
+ '''
|
|
|
+ I. 更新每个仓位的可用现金
|
|
|
+
|
|
|
+ II. 将已卖掉的股票现金仓位进行资金再平衡。
|
|
|
+ (这里只是将当前空余的仓位进行了资金再平衡
|
|
|
+ 如,有N支仓位空余,及重新平均分配N支仓位的资金)
|
|
|
+ '''
|
|
|
+ g.selled_list_keys = [] #已卖掉的股票keys
|
|
|
+ g.rebalance_total_money = 0 #再分仓的总金额
|
|
|
+
|
|
|
+ trades=get_orders() #过去当天订单
|
|
|
+ hold_stock = context.portfolio.positions
|
|
|
+ for t in trades.values():
|
|
|
+ # 更新每个仓位的可用现金
|
|
|
+ if t.is_buy and t.filled > 0: #买入有效订单
|
|
|
+ if (t.security not in g.already_hold_stock) and (hold_stock[t.security].sellable_amount > 0):
|
|
|
+ i = get_key(t.security)
|
|
|
+ # g.hold[i]['initial_buy_cash'] = t.cash
|
|
|
+ g.hold[i]['cash'] = g.hold[i]['total'] - t.cash #更新可用现金
|
|
|
+ elif (t.security in g.already_hold_stock) and (hold_stock[t.security].sellable_amount > 0):
|
|
|
+ i = get_key(t.security)
|
|
|
+ # g.hold[i]['initial_buy_cash'] = g.hold[i]['cash'] + t.cash
|
|
|
+ g.hold[i]['cash'] = g.hold[i]['cash'] - t.cash #更新可用现金
|
|
|
+
|
|
|
+ # 将已卖掉的股票现金仓位进行资金再平衡。
|
|
|
+ elif not t.is_buy and t.filled > 0:#卖出有效订单
|
|
|
+ if t.security not in context.portfolio.positions.keys():
|
|
|
+ i = get_key(t.security)
|
|
|
+ g.selled_list_keys.append(i)
|
|
|
+ g.rebalance_total_money += t.cash
|
|
|
+
|
|
|
+ # 资金重分配函数,如需隔离仓位,则不用执行该函数
|
|
|
+ if len(g.selled_list_keys) > 0:
|
|
|
+ rebalance_money(g.selled_list_keys, g.rebalance_total_money)
|
|
|
+
|
|
|
+ # 打印结果(用于调试)
|
|
|
+ # log.info('hold:',g.hold)
|
|
|
+ log.info('hold_stock_name:',g.hold_stock_name)
|
|
|
+ log.info('per_sell_stock:',g.per_sell_stock)
|
|
|
+
|
|
|
+def rebalance_money(selled_list_keys, rebalance_total_money):
|
|
|
+ '''
|
|
|
+ 资金重分配函数
|
|
|
+ 如需隔离仓位,则不用执行该函数
|
|
|
+ '''
|
|
|
+ # 获取再分仓的总金额
|
|
|
+ for i in selled_list_keys:
|
|
|
+ rebalance_total_money += g.hold[i]['cash']
|
|
|
+
|
|
|
+ # 确定每个仓位的总金额
|
|
|
+ every_position = rebalance_total_money/len(selled_list_keys)
|
|
|
+
|
|
|
+ # 资金重分配
|
|
|
+ for i in selled_list_keys:
|
|
|
+ g.hold_stock_name[i] = None
|
|
|
+ g.hold[i]['total'] = every_position
|
|
|
+ g.hold[i]['initial_buy_cash'] = every_position*g.proportion_initial_buy_cash
|
|
|
+ g.hold[i]['cash'] = every_position*g.proportion_cash
|