---------------------------------------------------------------------------- # 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對標的是財報季度最后一天,而非財報發(fā)布日期,所以獲取的數據會有未來數據,要先剔除 data2 = data2[data2['pub_date']<context.now].sort_values(['symbol','end_date']) epss = data2.groupby(['symbol']).count()# 統(tǒng)計財報期數 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, 由系統(tǒng)生成 filename文件名, 請與本文件名保持一致 mode運行模式, 實時模式:MODE_LIVE回測模式:MODE_BACKTEST token綁定計算機的ID, 可在系統(tǒng)設置-密鑰管理中生成 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) |
|