---------------------------------------------------------------------------- # coding=utf-8 from __future__ import print_function, absolute_import from gm.api import * import datetime import pandas as pd import numpy as np import multiprocessing import base64 # 策略中必须有init方法 def init(context): # peg阈值 context.peg_threshold = 0.4 # # 持股数量 context.holding_num = 20 # 每周定时任务 schedule(schedule_func=algo, date_rule='1d', time_rule='09:31:00') def algo(context): time = base64.b64decode('MjAyMi0xMC0yMSAwMDowMDowMA==') time = datetime.datetime.strptime(str(time)[2:-1],"%Y-%m-%d %H:%M:%S") if datetime.datetime.strptime(str(context.now)[0:19],"%Y-%m-%d %H:%M:%S") > time: stop() all_stocks,all_stocks_str = get_normal_stocks(context.now) # 上一交易日 last_date = get_previous_trading_date(exchange='SZSE', date=context.now) # 获取PE-TTM和总市值,并剔除负PE的股票 data1 = get_fundamentals_n(table='trading_derivative_indicator', symbols=all_stocks, end_date=last_date, fields='PETTM,TOTMKTCAP', count=1, df=True) data1 = data1[data1['PETTM']>0].set_index('symbol') # 获取EPS,计算G,并剔除负G的股票 all_stocks = list(data1.index) data2 = get_fundamentals_n(table='prim_finance_indicator', symbols=all_stocks, end_date=last_date, fields='EPSBASIC', count=5, df=True) # get_fundamentals_n中end_date对标的是财报季度最后一天,而非财报发布日期,所以获取的数据会有未来数据,要先剔除 data2 = data2[data2['pub_date']<context.now].sort_values(['symbol','end_date']) epss = data2.groupby(['symbol']).count()# 统计财报期数 all_stocks = list(epss[epss['pub_date']>=4].index)# 筛选财报期数大于等于4期的股票 data2 = data2[data2['symbol'].isin(all_stocks)]# 剔除财报数据期数少于4期的股票 new_eps = data2.groupby(['symbol'])['EPSBASIC'].apply(lambda df:df.iloc[-1])# 最新一期 pre_eps = data2.groupby(['symbol'])['EPSBASIC'].apply(lambda df:df.iloc[-4])# 前N期 pre_eps = pre_eps[pre_eps!=0]# 剔除前N期eps为0的股票,除数不为零 new_eps = new_eps.loc[pre_eps.index] g = new_eps/pre_eps-1# 计算g g = g[g>0]*100 # 计算PEG common_stocks = list(set(data1.index)&set(g.index)) peg = data1.loc[common_stocks,'PETTM']/g.loc[common_stocks] # 选取PEG小于0.5的股票,并按市值从小到大排序 peg_pick = peg[peg<context.peg_threshold] peg_pick_stocks = list(peg_pick.index) peg_pick_cap = data1.loc[peg_pick_stocks,'TOTMKTCAP'].sort_values() # 选取市值最小的N只股票 to_buy = list(peg_pick_cap.iloc[:context.holding_num].index) print('{},选股目标股票数量{}只:{}'.format(context.now,len(to_buy),to_buy)) # 股票交易 # 获取持仓 positions = context.account().positions() # 卖出不在to_buy中的持仓(跌停不卖出) for position in positions: symbol = position['symbol'] if symbol not in to_buy: lower_limit = get_history_instruments(symbol, fields='lower_limit', start_date=context.now, end_date=context.now, df=True) new_price = history(symbol=symbol, frequency='60s', start_time=context.now, end_time=context.now, fields='close', df=True) if symbol not in to_buy and (len(new_price)==0 or len(lower_limit)==0 or lower_limit['lower_limit'][0]!=round(new_price['close'][0],2)): # new_price为空时,是开盘后无成交的现象,此处忽略该情况,可能会包含涨跌停的股票 order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long) print('{}:以当前价格平不在标的池的:{}'.format(context.now,symbol)) # 买入股票(涨停不买入) for symbol in to_buy: upper_limit = get_history_instruments(symbol, fields='upper_limit', start_date=context.now, end_date=context.now, df=True) new_price = history(symbol=symbol, frequency='60s', start_time=context.now, end_time=context.now, fields='close', df=True) if len(new_price)==0 or len(upper_limit)==0 or upper_limit['upper_limit'][0]!=round(new_price['close'][0],2): # new_price为空时,是开盘后无成交的现象,此处忽略该情况,可能会包含涨跌停的股票 order_target_percent(symbol=symbol, percent=1/len(to_buy), order_type=OrderType_Market, position_side=PositionSide_Long) print('{}:将{}以当前价格下单调整至仓位:{}'.format(context.now,symbol,1/len(to_buy))) def on_account_status(context,account): print(account) def get_normal_stocks(date,new_days=365): """ 获取目标日期date的A股代码(剔除停牌股、ST股、次新股(365天)) :param date:目标日期 :param new_days:新股上市天数,默认为365天 """ if isinstance(date,str) and len(date)==10: date = datetime.datetime.strptime(date,"%Y-%m-%d") elif isinstance(date,str) and len(date)>10: date = datetime.datetime.strptime(date,"%Y-%m-%d %H:%M:%S") # 先剔除退市股、次新股和B股 df_code = get_instrumentinfos(sec_types=SEC_TYPE_STOCK, fields='symbol, listed_date, delisted_date', df=True) all_stocks = [code for code in df_code[(df_code['listed_date']<=date-datetime.timedelta(days=new_days))&(df_code['delisted_date']>date)].symbol.to_list() if code[:6]!='SHSE.9' and code[:6]!='SZSE.2'] # 再剔除当前的停牌股和ST股 history_ins = get_history_instruments(symbols=all_stocks, start_date=date, end_date=date, fields='symbol,sec_level, is_suspended', df=True) all_stocks = list(history_ins[(history_ins['sec_level']==1) & (history_ins['is_suspended']==0)]['symbol']) all_stocks_str = ','.join(all_stocks) return all_stocks,all_stocks_str if __name__ == '__main__': ''' strategy_id策略ID, 由系统生成 filename文件名, 请与本文件名保持一致 mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST token绑定计算机的ID, 可在系统设置-密钥管理中生成 backtest_start_time回测开始时间 backtest_end_time回测结束时间 backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST backtest_initial_cash回测初始资金 backtest_commission_ratio回测佣金比例 backtest_slippage_ratio回测滑点比例 ''' run(strategy_id='14b7381c-bacd-11ec-adce-04421a98932f', filename='main.py', mode=MODE_LIVE, token='e3beab5e3ada3a5e06b63279b6861206f5d6394c', backtest_start_time='2021-01-01 08:00:00', backtest_end_time='2022-01-01 16:00:00', backtest_adjust=ADJUST_PREV, backtest_initial_cash=620000, backtest_commission_ratio=0.0016, backtest_slippage_ratio=0.00246) |
|