[update] 股票:复权因子计算,盘后自动除权更新仓位,委托/成交显示中文名

This commit is contained in:
msincenselee 2021-09-09 12:23:33 +08:00
parent b149b6cc18
commit 5b7d660304
9 changed files with 156 additions and 84 deletions

View File

@ -0,0 +1,134 @@
# flake8: noqa
# encoding: UTF-8
# 功能:
# 每日夜盘扫描一次
# 根据账号所在目录下得cta_stock_setting.json检查所有运行中网格策略实例的持仓合约
# 如果当日存在除权除息情况,就进行计算更新
# AUTHOR:李来佳
# WeChat/QQ: 28888502
# 广东华富资产管理
import sys, os, copy, csv, json
import sys, os, platform
from datetime import datetime, timedelta
import pandas as pd
import traceback
import matplotlib
import json
from pymongo import *
from datetime import datetime
VNPY_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
if VNPY_ROOT not in sys.path:
print(f'append {VNPY_ROOT} into sys.path')
sys.path.append(VNPY_ROOT)
os.environ["VNPY_TESTING"] = "1"
from vnpy.trader.utility import load_json, save_json, append_data
from vnpy.data.stock.adjust_factor import get_adjust_factor, get_stock_base
from vnpy.trader.util_wechat import send_wx_msg
if __name__ == "__main__":
if len(sys.argv) <= 1:
print(u'请输入:{}目录下的子目录作为参数1'.format(os.path.abspath(os.path.join(VNPY_ROOT, 'prod'))))
exit()
# 进行报告的账号目录
account_folder = sys.argv[1]
account_folder = os.path.abspath(os.path.join(VNPY_ROOT, 'prod', account_folder))
# 策略实例配置文件
cta_setting_file = os.path.abspath(os.path.join(account_folder, 'cta_stock_setting.json'))
# 除权调整记录文件
adj_record_file = os.path.abspath(os.path.join(account_folder, 'data', 'adj_records.csv'))
field_names = ['date', 'strategy_name', 'vt_symbol', 'name', 'pre_volume', 'new_volume', 'rate', 'pre_back_adj',
'last_back_adj']
print('开始扫描:{}'.format(cta_setting_file))
if not os.path.exists(cta_setting_file):
print(u'不存在策略实例配置文件{}'.format(cta_setting_file), file=sys.stderr)
exit()
# 获取所有股票基本信息
all_symbols = get_stock_base()
# 读取策略
cta_settings = []
with open(cta_setting_file, encoding='UTF-8') as f:
cta_settings = json.load(f)
# 逐一策略扫描
all_margin_usage = 0
for strategy_name in cta_settings.keys():
# 获取策略实例配置
strategy_setting = cta_settings.get(strategy_name)
# 策略类
strategy_class = strategy_setting.get("class_name", "")
setting = strategy_setting.get('setting', {})
grids = load_json(os.path.abspath(os.path.join(account_folder, 'data', f'{strategy_name}_Grids.json')),
auto_save=False)
changed = False
margin_usage = 0
for grid in grids['dn_grids']:
if not grid['open_status']:
continue
vt_symbol = grid['vt_symbol']
info = all_symbols.get(vt_symbol,{})
name = info.get('name',vt_symbol)
factor = get_adjust_factor(vt_symbol)
if factor is None or len(factor) < 2:
print(f'没有找到{vt_symbol}的除权因子')
continue
# 检查除权日子的最后日期
last_data = factor[-1]
pre_data = factor[-2]
print(f'{vt_symbol}[{name}]复权因子:\n{pre_data} => \n{last_data}')
dividOperateDate = last_data['dividOperateDate']
foreAdjustFactor = float(last_data['foreAdjustFactor'])
adjusted_date = grid['snapshot'].get('adjusted_date', "")
yd_date = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
# 记录的除权执行日期,跟昨日或今日不一样,除权日发生在昨日或今日
if adjusted_date != dividOperateDate and dividOperateDate >= yd_date and foreAdjustFactor == 1.0:
adj_rate = last_data.get('backAdjustFactor') / pre_data.get('backAdjustFactor')
# 当前持仓
cur_volume = grid['volume']
# 除权后的股票新数量
adj_volume = int(cur_volume * adj_rate)
# 更新数量
grid['volume'] = adj_volume
# 更新执行日期
grid['snapshot'].update({'adjusted_date': dividOperateDate})
msg = f'{strategy_name}:{vt_symbol}[{name}]发生除权调整:{cur_volume}=>{adj_volume}'
send_wx_msg(msg)
print(msg)
append_data(adj_record_file, dict_data={
'date': dividOperateDate,
'strategy_name': strategy_name,
'vt_symbol': vt_symbol,
'name': name,
'pre_volume': cur_volume,
'adj_volume': adj_volume,
'rate': adj_rate,
'pre_back_adj': pre_data.get('backAdjustFactor'),
'last_back_adj': last_data.get('backAdjustFactor')
})
changed = True
if changed:
print('保存更新后的Grids.json文件')
save_json(os.path.abspath(os.path.join(account_folder, 'data', f'{strategy_name}_Grids.json')), grids)

View File

@ -63,7 +63,7 @@ from vnpy.trader.utility import (
get_stock_exchange get_stock_exchange
) )
from vnpy.data.stock.adjust_factor import get_all_adjust_factor from vnpy.data.stock.adjust_factor import get_all_adjust_factor
from vnpy.data.common import stock_to_adj
from vnpy.trader.util_logger import setup_logger from vnpy.trader.util_logger import setup_logger
from vnpy.data.mongo.mongo_data import MongoData from vnpy.data.mongo.mongo_data import MongoData
from uuid import uuid1 from uuid import uuid1
@ -584,41 +584,6 @@ class BackTestingEngine(object):
margin_rate=margin_rate margin_rate=margin_rate
) )
def stock_to_adj(self, raw_data, adj_data, adj_type):
"""
股票数据复权转换
:param raw_data: 不复权数据
:param adj_data: 复权记录 ( 从barstock下载的复权记录列表=df
:param adj_type: 复权类型: fore 前复权, 其他后复权
:return:
"""
if adj_type == 'fore':
adj_factor = adj_data["foreAdjustFactor"]
adj_factor = adj_factor / adj_factor.iloc[-1] # 保证最后一个复权因子是1
else:
adj_factor = adj_data["backAdjustFactor"]
adj_factor = adj_factor / adj_factor.iloc[0] # 保证第一个复权因子是1
# 把raw_data的第一个日期插入复权因子df使用后填充
if adj_factor.index[0] != raw_data.index[0]:
adj_factor.loc[raw_data.index[0]] = np.nan
adj_factor.sort_index(inplace=True)
adj_factor = adj_factor.ffill()
adj_factor = adj_factor.reindex(index=raw_data.index) # 按价格dataframe的日期索引来扩展索引
adj_factor = adj_factor.ffill() # 向前(向未来)填充扩展后的空单元格
# 把复权因子作为adj字段补充到raw_data中
raw_data['adj'] = adj_factor
# 逐一复权高低开平和成交量
for col in ['open', 'high', 'low', 'close']:
raw_data[col] = raw_data[col] * raw_data['adj'] # 价格乘上复权系数
raw_data['volume'] = raw_data['volume'] / raw_data['adj'] # 成交量除以复权系数
return raw_data
def new_tick(self, tick): def new_tick(self, tick):
"""新得tick""" """新得tick"""
self.last_tick.update({tick.vt_symbol: tick}) self.last_tick.update({tick.vt_symbol: tick})

View File

@ -72,6 +72,7 @@ from vnpy.data.mongo.mongo_data import MongoData
from vnpy.trader.setting import SETTINGS from vnpy.trader.setting import SETTINGS
from vnpy.data.stock.adjust_factor import get_all_adjust_factor from vnpy.data.stock.adjust_factor import get_all_adjust_factor
from vnpy.data.stock.stock_base import get_stock_base from vnpy.data.stock.stock_base import get_stock_base
from vnpy.data.common import stock_to_adj
from .base import ( from .base import (
APP_NAME, APP_NAME,
EVENT_CTA_LOG, EVENT_CTA_LOG,
@ -1169,7 +1170,7 @@ class CtaEngine(BaseEngine):
adj_data["dividOperateDate"] = pd.to_datetime(adj_data["dividOperateDate"], format="%Y-%m-%d %H:%M:%S") adj_data["dividOperateDate"] = pd.to_datetime(adj_data["dividOperateDate"], format="%Y-%m-%d %H:%M:%S")
adj_data = adj_data.set_index("dividOperateDate") adj_data = adj_data.set_index("dividOperateDate")
# 调用转换方法对open,high,low,close, volume进行复权, fore, 前复权, 其他,后复权 # 调用转换方法对open,high,low,close, volume进行复权, fore, 前复权, 其他,后复权
symbol_df = self.stock_to_adj(symbol_df, adj_data, adj_type='fore') symbol_df = stock_to_adj(symbol_df, adj_data, adj_type='fore')
for dt, bar_data in symbol_df.iterrows(): for dt, bar_data in symbol_df.iterrows():
bar_datetime = dt #- timedelta(seconds=bar_interval_seconds) bar_datetime = dt #- timedelta(seconds=bar_interval_seconds)
@ -1209,40 +1210,6 @@ class CtaEngine(BaseEngine):
return bars return bars
def stock_to_adj(self, raw_data, adj_data, adj_type):
"""
股票数据复权转换
:param raw_data: 不复权数据
:param adj_data: 复权记录 ( 从barstock下载的复权记录列表=df
:param adj_type: 复权类型
:return:
"""
if adj_type == 'fore':
adj_factor = adj_data["foreAdjustFactor"]
adj_factor = adj_factor / adj_factor.iloc[-1] # 保证最后一个复权因子是1
else:
adj_factor = adj_data["backAdjustFactor"]
adj_factor = adj_factor / adj_factor.iloc[0] # 保证第一个复权因子是1
# 把raw_data的第一个日期插入复权因子df使用后填充
if adj_factor.index[0] != raw_data.index[0]:
adj_factor.loc[raw_data.index[0]] = np.nan
adj_factor.sort_index(inplace=True)
adj_factor = adj_factor.ffill()
adj_factor = adj_factor.reindex(index=raw_data.index) # 按价格dataframe的日期索引来扩展索引
adj_factor = adj_factor.ffill() # 向前(向未来)填充扩展后的空单元格
# 把复权因子作为adj字段补充到raw_data中
raw_data['adj'] = adj_factor
# 逐一复权高低开平和成交量
for col in ['open', 'high', 'low', 'close']:
raw_data[col] = raw_data[col] * raw_data['adj'] # 价格乘上复权系数
raw_data['volume'] = raw_data['volume'] / raw_data['adj'] # 成交量除以复权系数
return raw_data
def resample_bars(self, df, x_min=None, x_hour=None, to_day=False): def resample_bars(self, df, x_min=None, x_hour=None, to_day=False):
""" """

View File

@ -35,7 +35,7 @@ from vnpy.trader.utility import (
get_csv_last_dt get_csv_last_dt
) )
from .back_testing import BackTestingEngine from .back_testing import BackTestingEngine, stock_to_adj
class PortfolioTestingEngine(BackTestingEngine): class PortfolioTestingEngine(BackTestingEngine):
@ -146,7 +146,7 @@ class PortfolioTestingEngine(BackTestingEngine):
format="%Y-%m-%d %H:%M:%S") format="%Y-%m-%d %H:%M:%S")
adj_data = adj_data.set_index("dividOperateDate") adj_data = adj_data.set_index("dividOperateDate")
# 调用转换方法对open,high,low,close, volume进行复权, fore, 前复权, 其他,后复权 # 调用转换方法对open,high,low,close, volume进行复权, fore, 前复权, 其他,后复权
symbol_df = self.stock_to_adj(symbol_df, adj_data, adj_type='fore' if qfq else "") symbol_df = stock_to_adj(symbol_df, adj_data, adj_type='fore' if qfq else "")
if auto_generate_fq: if auto_generate_fq:
self.write_log(f'加载数据[{vt_symbol}] ,缓存{fq_name}文件=>{fq_bar_file}') self.write_log(f'加载数据[{vt_symbol}] ,缓存{fq_name}文件=>{fq_bar_file}')

View File

@ -283,7 +283,8 @@ class CtaTemplate(ABC):
} }
if grid: if grid:
d.update({'grid': grid}) d.update({'grid': grid})
grid.order_ids.append(vt_orderid) if len(vt_orderid) > 0:
grid.order_ids.append(vt_orderid)
grid.order_time = order_time grid.order_time = order_time
self.active_orders.update({vt_orderid: d}) self.active_orders.update({vt_orderid: d})
if direction == Direction.LONG: if direction == Direction.LONG:

View File

@ -6512,7 +6512,7 @@ class CtaLineBar(object):
'type': 'line' 'type': 'line'
} }
indicators.update({indicator.get('name'): copy.copy(indicator)}) indicators.update({indicator.get('name'): copy.copy(indicator)})
if isinstance(self.para_ema4_len, int) and self.para_ema4_len > 0: if getattr(self,'para_ema4_len',0) > 0: #isinstance(self.para_ema4_len, int) and self.para_ema4_len > 0:
indicator = { indicator = {
'name': 'EMA{}'.format(self.para_ema4_len), 'name': 'EMA{}'.format(self.para_ema4_len),
'attr_name': 'line_ema4', 'attr_name': 'line_ema4',
@ -6520,7 +6520,7 @@ class CtaLineBar(object):
'type': 'line' 'type': 'line'
} }
indicators.update({indicator.get('name'): copy.copy(indicator)}) indicators.update({indicator.get('name'): copy.copy(indicator)})
if isinstance(self.para_ema5_len, int) and self.para_ema5_len > 0: if getattr(self, 'para_ema5_len',0) > 0: #isinstance(self.para_ema5_len, int) and self.para_ema5_len > 0:
indicator = { indicator = {
'name': 'EMA{}'.format(self.para_ema5_len), 'name': 'EMA{}'.format(self.para_ema5_len),
'attr_name': 'line_ema5', 'attr_name': 'line_ema5',
@ -7036,8 +7036,8 @@ class CtaMinuteBar(CtaLineBar):
bar_len = len(self.line_bar) bar_len = len(self.line_bar)
minutes_passed = (tick.datetime - datetime.strptime(tick.datetime.strftime('%Y-%m-%d'), minutes_passed = tick.datetime.hour * 60 + tick.datetime.minute
'%Y-%m-%d')).total_seconds() / 60
if self.underly_symbol in MARKET_ZJ: if self.underly_symbol in MARKET_ZJ:
if int(tick.datetime.strftime('%H%M')) > 1130 and int(tick.datetime.strftime('%H%M')) < 1600: if int(tick.datetime.strftime('%H%M')) > 1130 and int(tick.datetime.strftime('%H%M')) < 1600:
# 扣除11:30到13:00的中场休息的90分钟 # 扣除11:30到13:00的中场休息的90分钟

View File

@ -1299,7 +1299,7 @@ def check_zs_3rd(big_kline,
direction = 1 if signal_direction == Direction.LONG else -1 direction = 1 if signal_direction == Direction.LONG else -1
if not big_kline.pre_duan or not big_kline.cur_bi_zs: if not big_kline.pre_duan or not big_kline.cur_bi_zs:
return return False
# 排除,须满足:当前段的方向 == 信号方向, 当前笔的方向 != 信号方向 # 排除,须满足:当前段的方向 == 信号方向, 当前笔的方向 != 信号方向
if big_kline.cur_duan.direction != direction or big_kline.cur_bi.direction == direction: if big_kline.cur_duan.direction != direction or big_kline.cur_bi.direction == direction:

View File

@ -153,14 +153,16 @@ def stock_to_adj(raw_data: pd.DataFrame,
adj_factor = adj_data["backAdjustFactor"] adj_factor = adj_data["backAdjustFactor"]
adj_factor = adj_factor / adj_factor.iloc[0] # 保证第一个复权因子是1 adj_factor = adj_factor / adj_factor.iloc[0] # 保证第一个复权因子是1
# 把raw_data的第一个日期插入复权因子df使用后填充
if adj_factor.index[0] != raw_data.index[0]:
adj_factor.loc[raw_data.index[0]] = np.nan
adj_factor.sort_index(inplace=True) adj_factor.sort_index(inplace=True)
adj_factor = adj_factor.ffill()
adj_factor = adj_factor.reindex(index=raw_data.index) # 按价格dataframe的日期索引来扩展索引 # 按价格dataframe的日期索引来扩展索引
adj_factor2 = adj_factor.reindex(index=raw_data.index) # 得到dataframe的日期索引
adj_factor = adj_factor2.append(adj_factor) # 加入复权因子的日期索引
adj_factor = adj_factor.sort_index()
adj_factor = adj_factor.ffill() # 向前(向未来)填充扩展后的空单元格 adj_factor = adj_factor.ffill() # 向前(向未来)填充扩展后的空单元格
adj_factor = adj_factor.loc[raw_data.index] # 提取需要的日期索
adj_factor = adj_factor[~adj_factor.index.duplicated(keep='first')] # 去重
# 把复权因子作为adj字段补充到raw_data中 # 把复权因子作为adj字段补充到raw_data中
raw_data['adj'] = adj_factor raw_data['adj'] = adj_factor

View File

@ -96,6 +96,7 @@ class TradeMonitor(BasicMonitor):
"tradeid": {"display": "成交号 ", "update": False}, "tradeid": {"display": "成交号 ", "update": False},
"orderid": {"display": "委托号", "update": False}, "orderid": {"display": "委托号", "update": False},
"symbol": {"display": "代码", "update": False}, "symbol": {"display": "代码", "update": False},
"name": {"display": "名称", "update": False},
"exchange": {"display": "交易所", "update": False}, "exchange": {"display": "交易所", "update": False},
"direction": {"display": "方向", "update": False}, "direction": {"display": "方向", "update": False},
"offset": {"display": "开平", "update": False}, "offset": {"display": "开平", "update": False},
@ -121,6 +122,7 @@ class OrderMonitor(BasicMonitor):
headers: Dict[str, dict] = { headers: Dict[str, dict] = {
"orderid": {"display": "委托号", "update": False}, "orderid": {"display": "委托号", "update": False},
"symbol": {"display": "代码", "update": False}, "symbol": {"display": "代码", "update": False},
"name": {"display": "名称", "update": False},
"exchange": {"display": "交易所", "update": False}, "exchange": {"display": "交易所", "update": False},
"type": {"display": "类型", "update": False}, "type": {"display": "类型", "update": False},
"direction": {"display": "方向", "update": False}, "direction": {"display": "方向", "update": False},
@ -148,6 +150,7 @@ class PositionMonitor(BasicMonitor):
headers = { headers = {
"symbol": {"display": "代码", "update": False}, "symbol": {"display": "代码", "update": False},
"name": {"display": "名称", "update": False},
"exchange": {"display": "交易所", "update": False}, "exchange": {"display": "交易所", "update": False},
"direction": {"display": "方向", "update": False}, "direction": {"display": "方向", "update": False},
"volume": {"display": "数量", "update": True}, "volume": {"display": "数量", "update": True},