# 导入包
import matplotlib.pyplot as plt
from sklearn.preprocessing import scale
from tensorflow.keras.layers import Input, Dense, LSTM
# from keras.layers import merge
from tensorflow.keras.models import Model
# 基础参数配置
class conf:
instrument = '000300.HIX' #股票代码
#设置用于训练和回测的开始/结束日期
start_date = '2005-01-01'
split_date = '2015-01-01'
end_date = '2017-05-01'
fields = ['close', 'open', 'high', 'low', 'amount', 'volume'] # features
seq_len = 30 #每个input的长度
batch = 100 #整数,指定进行梯度下降时每个batch包含的样本数,训练时一个batch的样本会被计算一次梯度下降,使目标函数优化一步
# 数据导入以及初步处理
data = D.history_data(conf.instrument, conf.start_date, conf.end_date, conf.fields)
data['return'] = data['close'].shift(-5) / data['open'].shift(-1) - 1 #计算未来5日收益率(未来第五日的收盘价/明日的开盘价)
data=data[data.amount>0]
data.dropna(inplace=True)
datatime = data['date'][data.date>=conf.split_date] #记录predictions的时间,回测要用
data['return'] = data['return'].apply(lambda x:np.where(x>=0.2,0.2,np.where(x>-0.2,x,-0.2))) #去极值
data['return'] = data['return']*10 # 适当增大return范围,利于LSTM模型训练
data.reset_index(drop=True, inplace=True)
scaledata = data[conf.fields]
traindata = data[data.date<conf.split_date]
# 数据处理:设定每个input(30time series×6features)以及数据标准化
train_input = []
train_output = []
test_input = []
test_output = []
for i in range(conf.seq_len-1, len(traindata)):
a = scale(scaledata[i+1-conf.seq_len:i+1])
train_input.append(a)
c = data['return'][i]
train_output.append(c)
for j in range(len(traindata), len(data)):
b = scale(scaledata[j+1-conf.seq_len:j+1])
test_input.append(b)
c = data['return'][j]
test_output.append(c)
# LSTM接受数组类型的输入
train_x = np.array(train_input)
train_y = np.array(train_output)
test_x = np.array(test_input)
test_y = np.array(test_output)
# 自定义激活函数
import tensorflow.keras as tf
def atan(x):
return tf.atan(x)
# 构建神经网络层 1层LSTM层+3层Dense层
# 用于1个输入情况
lstm_input = Input(shape=(30,6), name='lstm_input')
# lstm_output = LSTM(128, activation=atan, dropout_W=0.2, dropout_U=0.1)(lstm_input)
lstm_output = LSTM(128, input_shape=(30,6))(lstm_input)
Dense_output_1 = Dense(64, activation='linear')(lstm_output)
Dense_output_2 = Dense(16, activation='linear')(Dense_output_1)
# predictions = Dense(1, activation=atan)(Dense_output_2)
predictions = Dense(1)(Dense_output_2)
model = Model(inputs=lstm_input, outputs=predictions)
model.compile(optimizer='adam', loss='mse', metrics=['mse'])
model.fit(train_x, train_y, batch_size=conf.batch, nb_epoch=10, verbose=2)
# 预测
predictions = model.predict(test_x)
# 预测值和真实值的关系
data1 = test_y
data2 = predictions
fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(data2,data1, 'o', label="data")
ax.legend(loc='best')
# 如果预测值>0,取为1;如果预测值<=0,取为-1.为回测做准备
for i in range(len(predictions)):
if predictions[i]>0:
predictions[i]=1
elif predictions[i]<=0:
predictions[i]=-1
# 将预测值与时间整合作为回测数据
cc = np.reshape(predictions,len(predictions), 1)
databacktest = pd.DataFrame()
databacktest['date'] = datatime
databacktest['direction']=np.round(cc)
# 在沪深300上回测
def initialize(context):
# 系统已经设置了默认的交易手续费和滑点,要修改手续费可使用如下函数
context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
# 传入预测数据和真实数据
context.predictions=databacktest
context.hold=conf.split_date
# 回测引擎:每日数据处理函数,每天执行一次
def handle_data(context, data):
current_dt = data.current_dt.strftime('%Y-%m-%d')
sid = context.symbol(conf.instrument)
cur_position = context.portfolio.positions[sid].amount # 持仓
if cur_position==0:
if databacktest['direction'].values[databacktest.date==current_dt]==1:
context.order_target_percent(sid, 0.9)
context.date=current_dt
else:
if databacktest['direction'].values[databacktest.date==current_dt]==-1:
if context.trading_calendar.session_distance(pd.Timestamp(context.date), pd.Timestamp(current_dt))>=5:
context.order_target(sid, 0)
# 调用回测引擎
m8 = M.trade.v4(
instruments=DataSource().write_pickle(conf.instrument),
options_data=DataSource().write_df(databacktest),
start_date=conf.split_date,
end_date=conf.end_date,
handle_data=handle_data,
initialize=initialize,
volume_limit=0.025,
order_price_field_buy='open',
order_price_field_sell='close',
capital_base=1000000,
auto_cancel_non_tradable_orders=True,
data_frequency='daily',
price_type='真实价格',
product_type='股票',
plot_charts=True,
backtest_only=False,
benchmark='000300.SHA'
)
[2020-05-06 17:40:30.090437] INFO: moduleinvoker: backtest.v8 开始运行..
[2020-05-06 17:40:30.213610] INFO: backtest: biglearning backtest:V8.3.4
[2020-05-06 17:40:30.214854] INFO: backtest: product_type:stock by specified
[2020-05-06 17:40:30.216277] INFO: backtest: 其它市场:{'HIX'}
[2020-05-06 17:40:45.135469] INFO: moduleinvoker: cached.v2 开始运行..
[2020-05-06 17:40:45.945061] INFO: moduleinvoker: 命中缓存
[2020-05-06 17:40:45.946215] INFO: moduleinvoker: cached.v2 运行完成[0.810748s].
[2020-05-06 17:40:46.682308] INFO: algo: TradingAlgorithm V1.6.7
[2020-05-06 17:40:56.265339] INFO: algo: trading transform...
[2020-05-06 17:40:58.034276] INFO: Performance: Simulated 565 trading days out of 565.
[2020-05-06 17:40:58.035489] INFO: Performance: first open: 2015-01-05 09:30:00+00:00
[2020-05-06 17:40:58.036565] INFO: Performance: last close: 2017-04-28 15:00:00+00:00
[2020-05-06 17:41:14.585260] INFO: moduleinvoker: backtest.v8 运行完成[44.494829s].
[2020-05-06 17:41:14.586485] INFO: moduleinvoker: trade.v4 运行完成[44.538722s].
# LSTM与stockranker配合回测
# 基础参数配置
class conf:
start_date = '2010-01-01'
end_date='2017-05-01'
# split_date 之前的数据用于训练,之后的数据用作效果评估
split_date = '2015-01-01'
# D.instruments: https://bigquant.com/docs/data_instruments.html
instruments = D.instruments(start_date, end_date)
# 机器学习目标标注函数
# 如下标注函数等价于 min(max((持有期间的收益 * 100), -20), 20) + 20 (后面的M.fast_auto_labeler会做取整操作)
# 说明:max/min这里将标注分数限定在区间[-20, 20],+20将分数变为非负数 (StockRanker要求标注分数非负整数)
# label_expr = ['return * 100', 'where(label > 20, 20, where(label < -20, -20, label)) + 20']
label_expr = ['return * 100', 'where(label > {0}, {0}, where(label < -{0}, -{0}, label)) + {0}'.format(20)]
# 持有天数,用于计算label_expr中的return值(收益)
hold_days = 5
# 特征 https://bigquant.com/docs/data_features.html,你可以通过表达式构造任何特征
features = [
'close_5/close_0', # 5日收益
'close_10/close_0', # 10日收益
'close_20/close_0', # 20日收益
'avg_amount_0/avg_amount_5', # 当日/5日平均交易额
'avg_amount_5/avg_amount_20', # 5日/20日平均交易额
'rank_avg_amount_0/rank_avg_amount_5', # 当日/5日平均交易额排名
'rank_avg_amount_5/rank_avg_amount_10', # 5日/10日平均交易额排名
'rank_return_0', # 当日收益
'rank_return_5', # 5日收益
'rank_return_10', # 10日收益
'rank_return_0/rank_return_5', # 当日/5日收益排名
'rank_return_5/rank_return_10', # 5日/10日收益排名
'pe_ttm_0', # 市盈率TTM
]
# 给数据做标注:给每一行数据(样本)打分,一般分数越高表示越好
m1 = M.fast_auto_labeler.v5(
instruments=conf.instruments, start_date=conf.start_date, end_date=conf.end_date,
label_expr=conf.label_expr, hold_days=conf.hold_days,
benchmark='000300.SHA', sell_at='open', buy_at='open')
# 计算特征数据
m2 = M.general_feature_extractor.v5(
instruments=conf.instruments, start_date=conf.start_date, end_date=conf.end_date,
features=conf.features)
# 数据预处理:缺失数据处理,数据规范化,T.get_stock_ranker_default_transforms为StockRanker模型做数据预处理
m3 = M.transform.v2(
data=m2.data, transforms=T.get_stock_ranker_default_transforms(),
drop_null=True, astype='int32', except_columns=['date', 'instrument'],
clip_lower=0, clip_upper=200000000)
# 合并标注和特征数据
m4 = M.join.v2(data1=m1.data, data2=m3.data, on=['date', 'instrument'], sort=True)
# 训练数据集
m5_training = M.filter.v2(data=m4.data, expr='date < "%s"' % conf.split_date)
# 评估数据集
m5_evaluation = M.filter.v2(data=m4.data, expr='"%s" <= date' % conf.split_date)
# StockRanker机器学习训练
m6 = M.stock_ranker_train.v6(training_ds=m5_training.data, features=conf.features)
# 对评估集做预测
m7 = M.stock_ranker_predict.v5(model=m6.model, data=m5_evaluation.data)
## 量化回测 https://bigquant.com/docs/strategy_backtest.html
# 回测引擎:初始化函数,只执行一次
def initialize(context):
# 系统已经设置了默认的交易手续费和滑点,要修改手续费可使用如下函数
context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
# 预测数据,通过options传入进来,使用 read_df 函数,加载到内存 (DataFrame)
context.ranker_prediction = context.options['ranker_prediction'].read_df()
# 设置买入的股票数量,这里买入预测股票列表排名靠前的5只
stock_count = 5
# 每只的股票的权重,如下的权重分配会使得靠前的股票分配多一点的资金,[0.339160, 0.213986, 0.169580, ..]
context.stock_weights = T.norm([1 / math.log(i + 2) for i in range(0, stock_count)])
# 设置每只股票占用的最大资金比例
context.max_cash_per_instrument = 0.2
context.date={}
# 回测引擎:每日数据处理函数,每天执行一次
def handle_data(context, data):
# 按日期过滤得到今日的预测数据
ranker_prediction = context.ranker_prediction[context.ranker_prediction.date == data.current_dt.strftime('%Y-%m-%d')]
current_dt = data.current_dt.strftime('%Y-%m-%d')
# 1. 资金分配
# 平均持仓时间是hold_days,每日都将买入股票,每日预期使用 1/hold_days 的资金
# 实际操作中,会存在一定的买入误差,所以在前hold_days天,等量使用资金;之后,尽量使用剩余资金(这里设置最多用等量的1.5倍)
is_staging = context.trading_day_index < context.options['hold_days'] # 是否在建仓期间(前 hold_days 天)
cash_avg = context.portfolio.portfolio_value / context.options['hold_days']
cash_for_buy = min(context.portfolio.cash, (1 if is_staging else 1.5) * cash_avg)
cash_for_sell = cash_avg - (context.portfolio.cash - cash_for_buy)
positions = {e.symbol: p.amount * p.last_sale_price for e, p in context.perf_tracker.position_tracker.positions.items()}
equities = {e.symbol: e for e, p in context.perf_tracker.position_tracker.positions.items()}
buy_dates = {}
for e in equities:
if e in context.date:
buy_dates[e] = context.date[e]
# 2. 生成卖出订单:hold_days天之后才开始卖出;对持仓的股票,按StockRanker预测的排序末位淘汰
if databacktest['direction'].values[databacktest.date==current_dt]==-1: # LSTM择时卖
instruments = list(reversed(list(ranker_prediction.instrument[ranker_prediction.instrument.apply(
lambda x: x in equities and not context.has_unfinished_sell_order(equities[x]))])))
for instrument in instruments:
if context.trading_calendar.session_distance(pd.Timestamp(context.date[instrument]), pd.Timestamp(current_dt))>=5:
context.order_target(context.symbol(instrument), 0)
if not is_staging and cash_for_sell > 0:
instruments = list(reversed(list(ranker_prediction.instrument[ranker_prediction.instrument.apply(
lambda x: x in equities and not context.has_unfinished_sell_order(equities[x]))])))
# print('rank order for sell %s' % instruments)
for instrument in instruments:
context.order_target(context.symbol(instrument), 0)
cash_for_sell -= positions[instrument]
if cash_for_sell <= 0:
break
# 3. 生成买入订单:按StockRanker预测的排序,买入前面的stock_count只股票
if databacktest['direction'].values[databacktest.date==current_dt]==1: # LSTM择时买
buy_dt = data.current_dt.strftime('%Y-%m-%d')
context.date=buy_dt
buy_cash_weights = context.stock_weights
buy_instruments = list(ranker_prediction.instrument[:len(buy_cash_weights)])
max_cash_per_instrument = context.portfolio.portfolio_value * context.max_cash_per_instrument
for i, instrument in enumerate(buy_instruments):
cash = cash_for_buy * buy_cash_weights[i]
if cash > max_cash_per_instrument - positions.get(instrument, 0):
# 确保股票持仓量不会超过每次股票最大的占用资金量
cash = max_cash_per_instrument - positions.get(instrument, 0)
if cash > 0:
context.order_value(context.symbol(instrument), cash)
buy_dates[instrument] = current_dt
context.date = buy_dates
# 调用回测引擎
m8 = M.trade.v4(
instruments=m7.instruments,
start_date=m7.start_date,
end_date=m7.end_date,
initialize=initialize,
handle_data=handle_data,
order_price_field_buy='open', # 表示 开盘 时买入
order_price_field_sell='close', # 表示 收盘 前卖出
capital_base=100000, # 初始资金
benchmark='000300.SHA', # 比较基准,不影响回测结果
# 通过 options 参数传递预测数据和参数给回测引擎
options={'ranker_prediction': m7.predictions, 'hold_days': conf.hold_days},
m_cached=False
)
[2020-05-06 14:53:17.331474] WARNING: moduleinvoker: 此模块版本 M.fast_auto_labeler.v5 已不再维护。你仍然可以使用,但建议升级到最新版本:请更新到 fast_auto_labeler 最新版本
[2020-05-06 14:53:17.334001] INFO: moduleinvoker: fast_auto_labeler.v5 开始运行..
[2020-05-06 14:53:17.697615] INFO: moduleinvoker: 命中缓存
[2020-05-06 14:53:17.703543] INFO: moduleinvoker: fast_auto_labeler.v5 运行完成[0.369548s].
[2020-05-06 14:53:17.706650] INFO: moduleinvoker: general_feature_extractor.v5 开始运行..
[2020-05-06 14:53:17.984921] INFO: moduleinvoker: 命中缓存
[2020-05-06 14:53:17.986605] INFO: moduleinvoker: general_feature_extractor.v5 运行完成[0.279951s].
[2020-05-06 14:53:17.992928] INFO: moduleinvoker: transform.v2 开始运行..
[2020-05-06 14:53:18.017101] INFO: moduleinvoker: 命中缓存
[2020-05-06 14:53:18.018867] INFO: moduleinvoker: transform.v2 运行完成[0.025936s].
[2020-05-06 14:53:18.021739] INFO: moduleinvoker: join.v2 开始运行..
[2020-05-06 14:53:18.579719] INFO: moduleinvoker: 命中缓存
[2020-05-06 14:53:18.581499] INFO: moduleinvoker: join.v2 运行完成[0.559749s].
[2020-05-06 14:53:18.584291] INFO: moduleinvoker: filter.v2 开始运行..
[2020-05-06 14:53:20.136456] INFO: moduleinvoker: 命中缓存
[2020-05-06 14:53:20.138195] INFO: moduleinvoker: filter.v2 运行完成[1.553899s].
[2020-05-06 14:53:20.140886] INFO: moduleinvoker: filter.v2 开始运行..
[2020-05-06 14:53:20.942650] INFO: moduleinvoker: 命中缓存
[2020-05-06 14:53:20.944354] INFO: moduleinvoker: filter.v2 运行完成[0.803457s].
[2020-05-06 14:53:20.947047] INFO: moduleinvoker: stock_ranker_train.v6 开始运行..
[2020-05-06 14:53:21.332617] INFO: moduleinvoker: 命中缓存
[2020-05-06 14:53:21.423587] INFO: moduleinvoker: stock_ranker_train.v6 运行完成[0.476519s].
[2020-05-06 14:53:21.426471] INFO: moduleinvoker: stock_ranker_predict.v5 开始运行..
[2020-05-06 14:53:22.809262] INFO: StockRanker预测: /y_2015 ..
[2020-05-06 14:53:25.226847] INFO: StockRanker预测: /y_2016 ..
[2020-05-06 14:53:27.559763] INFO: StockRanker预测: /y_2017 ..
[2020-05-06 14:53:31.171538] INFO: moduleinvoker: stock_ranker_predict.v5 运行完成[9.74504s].
[2020-05-06 14:53:31.206197] INFO: moduleinvoker: backtest.v8 开始运行..
[2020-05-06 14:53:31.207908] INFO: backtest: biglearning backtest:V8.3.4
[2020-05-06 14:53:31.209284] INFO: backtest: product_type:stock by specified
[2020-05-06 14:53:31.374319] INFO: moduleinvoker: cached.v2 开始运行..
[2020-05-06 14:53:46.258385] INFO: backtest: 读取股票行情完成:2436048
[2020-05-06 14:53:55.325098] INFO: moduleinvoker: cached.v2 运行完成[23.950773s].
[2020-05-06 14:53:56.912661] INFO: algo: TradingAlgorithm V1.6.7
[2020-05-06 14:53:58.160074] INFO: algo: trading transform...
[2020-05-06 14:54:00.823812] INFO: algo: handle_splits get splits [dt:2015-05-11 00:00:00+00:00] [asset:Equity(2120 [000582.SZA]), ratio:0.9970400333404541]
[2020-05-06 14:54:00.825188] INFO: Position: position stock handle split[sid:2120, orig_amount:200, new_amount:200.0, orig_cost:23.78999901398749, new_cost:23.72, ratio:0.9970400333404541, last_sale_price:23.579998016357422]
[2020-05-06 14:54:00.826514] INFO: Position: after split: PositionStock(asset:Equity(2120 [000582.SZA]), amount:200.0, cost_basis:23.72, last_sale_price:23.650001525878906)
[2020-05-06 14:54:00.828004] INFO: Position: returning cash: 14.0
[2020-05-06 14:54:01.058216] INFO: algo: handle_splits get splits [dt:2015-05-21 00:00:00+00:00] [asset:Equity(1721 [600999.SHA]), ratio:0.9922848343849182]
[2020-05-06 14:54:01.320698] INFO: algo: handle_splits get splits [dt:2015-06-04 00:00:00+00:00] [asset:Equity(617 [600233.SHA]), ratio:0.9962894320487976]
[2020-05-06 14:54:01.322134] INFO: Position: position stock handle split[sid:617, orig_amount:200, new_amount:200.0, orig_cost:25.35000038179727, new_cost:25.26, ratio:0.9962894320487976, last_sale_price:26.850000381469727]
[2020-05-06 14:54:01.323286] INFO: Position: after split: PositionStock(asset:Equity(617 [600233.SHA]), amount:200.0, cost_basis:25.26, last_sale_price:26.950000762939453)
[2020-05-06 14:54:01.324217] INFO: Position: returning cash: 20.0
[2020-05-06 14:54:01.429149] INFO: algo: handle_splits get splits [dt:2015-06-10 00:00:00+00:00] [asset:Equity(40 [000819.SZA]), ratio:0.9515395760536194]
[2020-05-06 14:54:01.430398] INFO: Position: position stock handle split[sid:40, orig_amount:100, new_amount:105.0, orig_cost:52.49000167945881, new_cost:49.95, ratio:0.9515395760536194, last_sale_price:54.39000701904297]
[2020-05-06 14:54:01.431487] INFO: Position: after split: PositionStock(asset:Equity(40 [000819.SZA]), amount:105.0, cost_basis:49.95, last_sale_price:57.160003662109375)
[2020-05-06 14:54:01.432427] INFO: Position: returning cash: 5.05
[2020-05-06 14:54:01.496651] INFO: algo: handle_splits get splits [dt:2015-06-12 00:00:00+00:00] [asset:Equity(838 [002048.SZA]), ratio:0.9965563416481018]
[2020-05-06 14:54:01.497878] INFO: Position: position stock handle split[sid:838, orig_amount:200, new_amount:200.0, orig_cost:29.440002441506014, new_cost:29.34, ratio:0.9965563416481018, last_sale_price:28.93999671936035]
[2020-05-06 14:54:01.498925] INFO: Position: after split: PositionStock(asset:Equity(838 [002048.SZA]), amount:200.0, cost_basis:29.34, last_sale_price:29.040000915527344)
[2020-05-06 14:54:01.499824] INFO: Position: returning cash: 20.0
[2020-05-06 14:54:01.615618] INFO: algo: handle_splits get splits [dt:2015-06-18 00:00:00+00:00] [asset:Equity(1344 [600990.SHA]), ratio:0.9995265603065491]
[2020-05-06 14:54:01.715677] INFO: algo: handle_splits get splits [dt:2015-06-25 00:00:00+00:00] [asset:Equity(2025 [600850.SHA]), ratio:0.996485710144043]
[2020-05-06 14:54:01.716963] INFO: Position: position stock handle split[sid:2025, orig_amount:100, new_amount:100.0, orig_cost:77.3700027486122, new_cost:77.1, ratio:0.996485710144043, last_sale_price:70.8899917602539]
[2020-05-06 14:54:01.717985] INFO: Position: after split: PositionStock(asset:Equity(2025 [600850.SHA]), amount:100.0, cost_basis:77.1, last_sale_price:71.13999938964844)
[2020-05-06 14:54:01.718873] INFO: Position: returning cash: 25.0
[2020-05-06 14:54:01.858947] INFO: algo: handle_splits get splits [dt:2015-07-10 00:00:00+00:00] [asset:Equity(2331 [600500.SHA]), ratio:0.9872798323631287]
[2020-05-06 14:54:01.860819] INFO: Position: position stock handle split[sid:2331, orig_amount:1800, new_amount:1823.0, orig_cost:9.289999961911114, new_cost:9.17, ratio:0.9872798323631287, last_sale_price:10.089999198913574]
[2020-05-06 14:54:01.862206] INFO: Position: after split: PositionStock(asset:Equity(2331 [600500.SHA]), amount:1823.0, cost_basis:9.17, last_sale_price:10.219999313354492)
[2020-05-06 14:54:01.863496] INFO: Position: returning cash: 1.93
[2020-05-06 14:54:02.453907] INFO: algo: handle_splits get splits [dt:2015-08-12 00:00:00+00:00] [asset:Equity(1738 [600196.SHA]), ratio:0.9899605512619019]
[2020-05-06 14:54:03.051928] INFO: algo: handle_splits get splits [dt:2015-09-25 00:00:00+00:00] [asset:Equity(975 [600355.SHA]), ratio:0.5]
[2020-05-06 14:54:06.351446] INFO: algo: handle_splits get splits [dt:2016-04-18 00:00:00+00:00] [asset:Equity(2536 [600116.SHA]), ratio:0.3316228687763214]
[2020-05-06 14:54:06.617799] INFO: algo: handle_splits get splits [dt:2016-05-06 00:00:00+00:00] [asset:Equity(1934 [002700.SZA]), ratio:0.9972490072250366]
[2020-05-06 14:54:06.619224] INFO: Position: position stock handle split[sid:1934, orig_amount:600, new_amount:601.0, orig_cost:14.519999549287565, new_cost:14.48, ratio:0.9972490072250366, last_sale_price:14.5]
[2020-05-06 14:54:06.620351] INFO: Position: after split: PositionStock(asset:Equity(1934 [002700.SZA]), amount:601.0, cost_basis:14.48, last_sale_price:14.539999008178711)
[2020-05-06 14:54:06.621299] INFO: Position: returning cash: 9.5
[2020-05-06 14:54:06.873890] INFO: algo: handle_splits get splits [dt:2016-05-30 00:00:00+00:00] [asset:Equity(723 [000028.SZA]), ratio:0.9950543642044067]
[2020-05-06 14:54:06.925128] INFO: algo: handle_splits get splits [dt:2016-06-01 00:00:00+00:00] [asset:Equity(2410 [002548.SZA]), ratio:0.9923235177993774]
[2020-05-06 14:54:07.017509] INFO: algo: handle_splits get splits [dt:2016-06-07 00:00:00+00:00] [asset:Equity(2016 [002180.SZA]), ratio:0.5713980793952942]
[2020-05-06 14:54:07.331531] INFO: algo: handle_splits get splits [dt:2016-06-24 00:00:00+00:00] [asset:Equity(2163 [600623.SHA]), ratio:0.9949135184288025]
[2020-05-06 14:54:07.333402] INFO: Position: position stock handle split[sid:2163, orig_amount:800, new_amount:804.0, orig_cost:19.309999975264954, new_cost:19.21, ratio:0.9949135184288025, last_sale_price:19.559999465942383]
[2020-05-06 14:54:07.334971] INFO: Position: after split: PositionStock(asset:Equity(2163 [600623.SHA]), amount:804.0, cost_basis:19.21, last_sale_price:19.65999984741211)
[2020-05-06 14:54:07.336483] INFO: Position: returning cash: 1.76
[2020-05-06 14:54:11.522738] INFO: Performance: Simulated 559 trading days out of 559.
[2020-05-06 14:54:11.523893] INFO: Performance: first open: 2015-01-05 09:30:00+00:00
[2020-05-06 14:54:11.524865] INFO: Performance: last close: 2017-04-20 15:00:00+00:00
[2020-05-06 14:54:19.049681] INFO: moduleinvoker: backtest.v8 运行完成[47.843479s].
[2020-05-06 14:54:19.051058] INFO: moduleinvoker: trade.v4 运行完成[47.87524s].