diff --git a/prod/jobs/daily_stock_adjust_grids.py b/prod/jobs/daily_stock_adjust_grids.py new file mode 100644 index 00000000..ec52b983 --- /dev/null +++ b/prod/jobs/daily_stock_adjust_grids.py @@ -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) diff --git a/vnpy/app/cta_stock/back_testing.py b/vnpy/app/cta_stock/back_testing.py index 4522ad92..03a0b01c 100644 --- a/vnpy/app/cta_stock/back_testing.py +++ b/vnpy/app/cta_stock/back_testing.py @@ -63,7 +63,7 @@ from vnpy.trader.utility import ( get_stock_exchange ) 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.data.mongo.mongo_data import MongoData from uuid import uuid1 @@ -584,41 +584,6 @@ class BackTestingEngine(object): 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): """新得tick""" self.last_tick.update({tick.vt_symbol: tick}) diff --git a/vnpy/app/cta_stock/engine.py b/vnpy/app/cta_stock/engine.py index 459a2868..437f1711 100644 --- a/vnpy/app/cta_stock/engine.py +++ b/vnpy/app/cta_stock/engine.py @@ -72,6 +72,7 @@ from vnpy.data.mongo.mongo_data import MongoData from vnpy.trader.setting import SETTINGS from vnpy.data.stock.adjust_factor import get_all_adjust_factor from vnpy.data.stock.stock_base import get_stock_base +from vnpy.data.common import stock_to_adj from .base import ( APP_NAME, 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 = adj_data.set_index("dividOperateDate") # 调用转换方法,对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(): bar_datetime = dt #- timedelta(seconds=bar_interval_seconds) @@ -1209,40 +1210,6 @@ class CtaEngine(BaseEngine): 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): """ diff --git a/vnpy/app/cta_stock/portfolio_testing.py b/vnpy/app/cta_stock/portfolio_testing.py index cf4a701d..097eab3d 100644 --- a/vnpy/app/cta_stock/portfolio_testing.py +++ b/vnpy/app/cta_stock/portfolio_testing.py @@ -35,7 +35,7 @@ from vnpy.trader.utility import ( get_csv_last_dt ) -from .back_testing import BackTestingEngine +from .back_testing import BackTestingEngine, stock_to_adj class PortfolioTestingEngine(BackTestingEngine): @@ -146,7 +146,7 @@ class PortfolioTestingEngine(BackTestingEngine): format="%Y-%m-%d %H:%M:%S") adj_data = adj_data.set_index("dividOperateDate") # 调用转换方法,对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: self.write_log(f'加载数据[{vt_symbol}] ,缓存{fq_name}文件=>{fq_bar_file}') diff --git a/vnpy/app/cta_stock/template.py b/vnpy/app/cta_stock/template.py index c80c5e65..09bd97ed 100644 --- a/vnpy/app/cta_stock/template.py +++ b/vnpy/app/cta_stock/template.py @@ -283,7 +283,8 @@ class CtaTemplate(ABC): } if 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 self.active_orders.update({vt_orderid: d}) if direction == Direction.LONG: diff --git a/vnpy/component/cta_line_bar.py b/vnpy/component/cta_line_bar.py index 2208975e..877ee148 100644 --- a/vnpy/component/cta_line_bar.py +++ b/vnpy/component/cta_line_bar.py @@ -6512,7 +6512,7 @@ class CtaLineBar(object): 'type': 'line' } 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 = { 'name': 'EMA{}'.format(self.para_ema4_len), 'attr_name': 'line_ema4', @@ -6520,7 +6520,7 @@ class CtaLineBar(object): 'type': 'line' } 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 = { 'name': 'EMA{}'.format(self.para_ema5_len), 'attr_name': 'line_ema5', @@ -7036,8 +7036,8 @@ class CtaMinuteBar(CtaLineBar): bar_len = len(self.line_bar) - minutes_passed = (tick.datetime - datetime.strptime(tick.datetime.strftime('%Y-%m-%d'), - '%Y-%m-%d')).total_seconds() / 60 + minutes_passed = tick.datetime.hour * 60 + tick.datetime.minute + if self.underly_symbol in MARKET_ZJ: if int(tick.datetime.strftime('%H%M')) > 1130 and int(tick.datetime.strftime('%H%M')) < 1600: # 扣除11:30到13:00的中场休息的90分钟 diff --git a/vnpy/component/cta_utility.py b/vnpy/component/cta_utility.py index 246b0f1a..40add07e 100644 --- a/vnpy/component/cta_utility.py +++ b/vnpy/component/cta_utility.py @@ -1299,7 +1299,7 @@ def check_zs_3rd(big_kline, direction = 1 if signal_direction == Direction.LONG else -1 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: diff --git a/vnpy/data/common.py b/vnpy/data/common.py index 525f86ec..12206eab 100644 --- a/vnpy/data/common.py +++ b/vnpy/data/common.py @@ -153,14 +153,16 @@ def stock_to_adj(raw_data: pd.DataFrame, 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的日期索引来扩展索引 + # 按价格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.loc[raw_data.index] # 提取需要的日期索 + adj_factor = adj_factor[~adj_factor.index.duplicated(keep='first')] # 去重 # 把复权因子,作为adj字段,补充到raw_data中 raw_data['adj'] = adj_factor diff --git a/vnpy/trader/util_monitor.py b/vnpy/trader/util_monitor.py index 1ad3070d..a148a18f 100644 --- a/vnpy/trader/util_monitor.py +++ b/vnpy/trader/util_monitor.py @@ -96,6 +96,7 @@ class TradeMonitor(BasicMonitor): "tradeid": {"display": "成交号 ", "update": False}, "orderid": {"display": "委托号", "update": False}, "symbol": {"display": "代码", "update": False}, + "name": {"display": "名称", "update": False}, "exchange": {"display": "交易所", "update": False}, "direction": {"display": "方向", "update": False}, "offset": {"display": "开平", "update": False}, @@ -121,6 +122,7 @@ class OrderMonitor(BasicMonitor): headers: Dict[str, dict] = { "orderid": {"display": "委托号", "update": False}, "symbol": {"display": "代码", "update": False}, + "name": {"display": "名称", "update": False}, "exchange": {"display": "交易所", "update": False}, "type": {"display": "类型", "update": False}, "direction": {"display": "方向", "update": False}, @@ -148,6 +150,7 @@ class PositionMonitor(BasicMonitor): headers = { "symbol": {"display": "代码", "update": False}, + "name": {"display": "名称", "update": False}, "exchange": {"display": "交易所", "update": False}, "direction": {"display": "方向", "update": False}, "volume": {"display": "数量", "update": True},