QMT Python API
策略回测_投研端
策略回测
1
# coding:gbk2
3
import pandas as pd4
import numpy as np5
6
class G():7
pass8
9
g = G()10
11
def init(C):12
# ------------------------参数设定-----------------------------13
g.his_st = {}14
g.s = get_stock_list_in_sector("上证50") # 获取沪深A股股票列表15
# g.s = get_stock_list_in_sector("沪深300") # 获取沪深300股票列表16
# print(g.s)17
# g.s = ['000001.SZ']18
g.day = 019
g.holdings = {i: 0 for i in g.s}20
g.weight = [0.1] * 1021
g.buypoint = {}22
g.money = 1000000 # C.capital23
g.accid = 'test'24
g.profit = 025
# 因子权重26
g.buy_num = 10 # 买排名前5的股票,在过滤中会用到27
g.per_money = g.money / g.buy_num * 0.9528
29
30
31
def after_init(C):32
# ------------------------量价数据获取-----------------------------33
data = C.get_market_data_ex([], g.s, period='1d', dividend_type='front_ratio',34
fill_data=True)35
close_df = get_df_ex(data,"close")36
# print(close_df)37
open_df = get_df_ex(data,"open")38
low_df = get_df_ex(data,"low")39
high_df = get_df_ex(data,"high")40
volume_df = get_df_ex(data,"volume")41
amount_df = get_df_ex(data,"amount")42
preclose_df = get_df_ex(data,"preClose")43
44
# ------------------------基础数据获取-----------------------------45
# 将 g.s 中的全部股票的 TotalVolume 都获取出来,组合成一个 DataFrame46
# 例如 g.s 中有 10 个股票,那么下面的代码就会返回一个 1 行 10 列的 DataFrame47
# 该 DataFrame 的 index 是股票代码,columns 是 TotalVolume48
# 该 DataFrame 的数据是每个股票的 TotalVolume,请给出代码:49
# C.get_instrumentdetail('600000.SH')['TotalVolume']50
51
# 使用字典推导来获取每个股票的TotalVolume,注意在内置 Python 环境的拼写52
total_volumes = {stock: C.get_instrumentdetail(stock)['TotalVolumn'] for stock in g.s}53
# print(total_volumes)54
55
# 将字典转换为DataFrame,但先转化为一个嵌套字典56
df_total_volume = pd.DataFrame({k: v for k, v in total_volumes.items()}, index=['TotalVolumn'])57
# print(df_total_volume)58
# exit()59
60
# ------------------------财务数据获取-----------------------------61
62
# ------------------------因子1计算及处理--------------------------------63
# 1. 市值因子: 用市值因子 = 股票收盘价 * 股票总股本,要利用好,要求对应列名相乘64
factor = close_df * df_total_volume.loc['TotalVolumn']65
# print(factor)66
# exit()67
68
# ------------------------因子2计算及处理--------------------------------69
# 判断 close_df 中的 code 上市时间大于120天70
stock_opendate_filter = filter_opendate_qmt(C, close_df, 120)71
# print(stock_opendate_filter)72
73
# ------------------------上市日期过滤处理--------------------------------74
# 两个布尔值的 DataFrame 对应相乘过滤掉每个交易日上市不足120天的75
factor *= stock_opendate_filter.astype(int).replace(0, np.nan)76
# print(factor)77
# ------------------------排序处理-----------------------------------78
# 对 factor 每行在一行内进行排序79
factor_sorted = rank_filter(factor, 10, ascending=True, method='min', na_option='keep')80
# print(factor_sorted)81
# exit()82
83
# ------------------------因子组合得到布尔值信号--------------------------------84
85
# 确保没有未来数据的影响,将因子数据向后移动一天86
g.factor_df = factor_sorted.shift(1) #87
g.close_df = close_df.shift(1) # 为了计算收益率,将收盘价向后移动一天88
g.open_df = open_df89
g.stock_opendate_filter = stock_opendate_filter90
91
def handlebar(C):92
# 获取当前 K 线位置93
d = C.barpos94
# 获取当前 K 线时间95
backtest_time = timetag_to_datetime(C.get_bar_timetag(C.barpos), "%Y%m%d")96
# print(g.factor_df)97
# print(backtest_time)98
factor_series = g.factor_df.loc[backtest_time]99
# factor_series.sort_values(ascending=True,inplace=True)100
# sl = factor_series.index.tolist()101
buy_list = daily_filter(factor_series, backtest_time)102
print(backtest_time, buy_list)103
# exit()104
105
# 获取持仓106
hold = get_holdings(g.accid, 'stock')107
need_sell = [s for s in hold if s not in buy_list]108
print('\t\t\t\t\t\t\t', backtest_time, 'sell list', need_sell)109
110
# 卖出111
for s in need_sell:112
price = g.open_df.loc[backtest_time, s]113
vol = hold[s]['持仓数量']114
passorder(24, 1101, g.accid, s, 11, price, vol, C)115
116
# 获取持仓117
hold = get_holdings(g.accid, 'stock')118
asset = get_trade_detail_data(g.accid, 'stock', 'account')119
# cash = asset[0].m_dAvailable120
buy_num = g.buy_num - len(hold)121
buy_list = [s for s in buy_list if s not in hold]122
123
# 买入124
if buy_num > 0 and buy_list:125
buy_list = buy_list[:buy_num]126
# money = cash/buy_num127
print(backtest_time, 'buy list', buy_list)128
for s in buy_list:129
price = g.open_df.loc[backtest_time, s]130
if price > 0:131
passorder(23, 1102, g.accid, s, 11, float(price), g.per_money, C)132
133
134
def daily_filter(factor_series, backtest_time):135
# 将 factor_series 中值 True 的index,转化成列表136
print(len(factor_series))137
sl = factor_series[factor_series].index.tolist()138
print(len(sl))139
# exit()140
# st过滤141
sl = [s for s in sl if not is_st(s, backtest_time)]142
sl = sorted(sl, key=lambda k: factor_series.loc[k])143
return sl[:g.buy_num]144
145
146
def is_st(s, date):147
# 判断某日在历史上是不是st *st148
st_dict = g.his_st.get(s, {})149
if not st_dict:150
return False151
else:152
st = st_dict.get('ST', []) + st_dict.get('*ST', [])153
for start, end in st:154
if start <= date <= end:155
return True156
157
158
def rank_filter(df: pd.DataFrame, N: int, axis=1, ascending=False, method="max", na_option="keep") -> pd.DataFrame:159
"""160
Args:161
df: 标准数据的df162
N: 判断是否是前N名163
axis: 默认是横向排序164
ascending : 默认是降序排序165
na_option : 默认保留nan值,但不参与排名166
Return:167
pd.DataFrame:一个全是bool值的df168
"""169
_df = df.copy()170
171
_df = _df.rank(axis=axis, ascending=ascending, method=method, na_option=na_option)172
173
return _df <= N174
175
def get_df_ex(data:dict,field:str) -> pd.DataFrame:176
'''177
ToDo:用于在使用get_market_data_ex的情况下,取到标准df178
179
Args:180
data: get_market_data_ex返回的dict181
field: ['time', 'open', 'high', 'low', 'close', 'volume','amount', 'settelementPrice', 'openInterest', 'preClose', 'suspendFlag']182
183
Return:184
一个以时间为index,标的为columns的df185
186
'''187
_index = data[list(data.keys())[0]].index.tolist()188
_columns = list(data.keys())189
df = pd.DataFrame(index=_index,columns=_columns)190
for i in _columns:191
df[i] = data[i][field]192
return df193
194
195
def filter_opendate_qmt(C, df: pd.DataFrame, n: int) -> pd.DataFrame:196
'''197
198
ToDo: 判断传入的df.columns中,上市天数是否大于N日,返回的值是一个全是bool值的df199
200
Args:201
C:contextinfo类202
df:index为时间,columns为stock_code的df,目的是为了和策略中的其他df对齐203
n:用于判断上市天数的参数,如要判断是否上市120天,则填写204
Return:pd.DataFrame205
206
'''207
# print(df.index)208
local_df = pd.DataFrame(index=df.index, columns=df.columns)209
# print(local_df)210
# print(type(list(local_df.index)[0]))211
stock_list = df.columns212
# 这里的索引数据类型不一样213
stock_opendate = {i: str(C.get_instrumentdetail(i)["OpenDate"]) for i in stock_list}214
# stock_opendate = {i: C.get_instrumentdetail(i)["OpenDate"] for i in stock_list}215
# print(type(stock_opendate["000001.SZ"]), stock_opendate["000001.SZ"])216
# print("+================================+\n")217
218
for stock, date in stock_opendate.items():219
local_df.at[date, stock] = 1220
221
df_fill = local_df.fillna(method="ffill")222
223
result = df_fill.expanding().sum() >= n224
# print(result)225
return result226
227
def get_holdings(accid, datatype):228
'''229
Arg:230
accondid:账户id231
datatype:232
'FUTURE':期货233
'STOCK':股票234
......235
return:236
{股票名:{'手数':int,"持仓成本":float,'浮动盈亏':float,"可用余额":int}}237
'''238
PositionInfo_dict = {}239
resultlist = get_trade_detail_data(accid, datatype, 'POSITION')240
for obj in resultlist:241
PositionInfo_dict[obj.m_strInstrumentID + "." + obj.m_strExchangeID] = {242
"持仓数量": obj.m_nVolume,243
"持仓成本": obj.m_dOpenPrice,244
"浮动盈亏": obj.m_dFloatProfit,245
"可用数量": obj.m_nCanUseVolume246
}247
return PositionInfo_dict248
249
250