diff --git a/.gitignore b/.gitignore index 2a255996cb..7cb11b3ec8 100644 --- a/.gitignore +++ b/.gitignore @@ -30,4 +30,9 @@ dist # Documents _build _static -_templates \ No newline at end of file +_templates + +bar_data/ +st/ +tick_data/ +renko_data/ \ No newline at end of file diff --git a/README.md b/README.md index 8ae7c5e718..8f45d0606a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,3 @@ -

- - -

- - # “当你想放弃时,想想你为什么开始。埃隆·马斯克” github 链接: https://github.com/msincenselee/vnpy @@ -137,10 +131,9 @@ gitee 链接: https://gitee.com/vnpy2/vnpy -大佳 -QQ/Wechat:28888502 +Kim -2020最新套利课程:http://www.uquant.org/course/43 +QQ:27133 -------------------------------------------------------------------------------------------- # 原版 vn.py - 基于python的开源交易平台开发框架 diff --git a/prod/jobs/compress_binance_future.py b/prod/jobs/compress_binance_future.py new file mode 100644 index 0000000000..26d0e6a21a --- /dev/null +++ b/prod/jobs/compress_binance_future.py @@ -0,0 +1,31 @@ +import sys, os, copy, csv, signal, bz2, pickle +from datetime import datetime +import pandas as pd + +vnpy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +if vnpy_root not in sys.path: + sys.path.append(vnpy_root) + +if __name__ == "__main__": + tick_path = os.path.join(vnpy_root, "tick_data", "binance") + today = datetime.now().strftime('%Y%m%d') + for path,dir_list,file_list in os.walk(tick_path): + if today not in path: + dir_day = path.split(os.sep)[-1] + for file_name in file_list: + if "csv" in file_name: + new_file_name = file_name.replace("_" + dir_day, "") + new_file_name = new_file_name.replace("csv", "pkb2") + old_file = os.path.join(path, file_name) + tick_df = pd.read_csv(old_file) + tick_df.set_index('datetime', inplace=True) + tick_df.to_csv(os.path.join(path, file_name), compression="bz2") + os.unlink(old_file) + # if "pkb2" in file_name: + # with bz2.BZ2File(os.path.join(path, file_name), "rb") as f: + # print(os.path.join(path, file_name)) + # data = pickle.load(f) + # tick_df = pd.DataFrame(data) + # tick_df.set_index('datetime', inplace=True) + # tick_df.to_csv(os.path.join(path, file_name), compression="bz2") + print("压缩完成") diff --git a/prod/jobs/refill_tdx_future_ticks.py b/prod/jobs/refill_tdx_future_ticks.py new file mode 100644 index 0000000000..e9298ca06b --- /dev/null +++ b/prod/jobs/refill_tdx_future_ticks.py @@ -0,0 +1,67 @@ +# flake8: noqa +""" +下载通达信指数合约tick => vnpy项目目录/tick_data/tdx/ +""" +import os +import sys +import json +import csv +from collections import OrderedDict +import pandas as pd +from datetime import datetime, timedelta, time +from time import sleep + +vnpy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) +if vnpy_root not in sys.path: + sys.path.append(vnpy_root) + +os.environ["VNPY_TESTING"] = "1" + +from vnpy.data.tdx.tdx_future_data import * +from vnpy.trader.utility import get_csv_last_dt + +if __name__ == "__main__": + + if len(sys.argv) > 1: + filter_underlying_symbols = [s.upper() for s in sys.argv[1:]] + else: + filter_underlying_symbols = [] + + # 保存的1分钟指数 bar目录 + tick_data_folder = os.path.abspath(os.path.join(vnpy_root, 'tick_data', "tdx")) + + # 开始日期(每年大概需要几分钟) + start_date = '20160101' + + # 创建API对象 + api_01 = TdxFutureData() + + # 更新本地合约缓存信息 + api_01.update_mi_contracts() + + # 逐一指数合约下载并更新 + for underlying_symbol in api_01.future_contracts.keys(): + if len(filter_underlying_symbols) > 0 and underlying_symbol not in filter_underlying_symbols: + continue + + index_symbol = underlying_symbol + '99' + print(f'开始更新:{index_symbol}') + start_dt = datetime.strptime(start_date, '%Y%m%d') + end_date = datetime.combine(datetime.now() + timedelta(days=1), time(16, 0)) + print(start_dt, end_date) + i = 0 + while start_dt < end_date: + api_01.get_history_transaction_data(index_symbol, start_dt, tick_data_folder) + start_dt = start_dt + timedelta(days=1) + if start_dt.weekday() == 5: + start_dt = start_dt + timedelta(days=1) + if start_dt.weekday() == 6: + start_dt = start_dt + timedelta(days=1) + + i += 1 + if i > 22: + i = 0 + # sleep(30) + print(start_dt) + print('更新完毕') + os._exit(0) \ No newline at end of file diff --git a/prod/jobs/refill_tdx_stock_bars.py b/prod/jobs/refill_tdx_stock_bars.py index 30d0fc4507..86d8b7f64d 100644 --- a/prod/jobs/refill_tdx_stock_bars.py +++ b/prod/jobs/refill_tdx_stock_bars.py @@ -3,6 +3,8 @@ 下载通达信股票合约1分钟bar => vnpy项目目录/bar_data/ 上海股票 => SSE子目录 深圳股票 => SZSE子目录 + +stock_list.json 需要下载的股票数据写于这里 """ import os import sys diff --git a/prod/linux/tick_record/run.py b/prod/linux/tick_record/run.py index 48688cc688..f3f5aa5675 100644 --- a/prod/linux/tick_record/run.py +++ b/prod/linux/tick_record/run.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -* import os import sys import multiprocessing @@ -7,15 +8,16 @@ # 将repostory的目录i,作为根目录,添加到系统环境中。 -ROOT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) -sys.path.append(ROOT_PATH) -print(f'append {ROOT_PATH} into sys.path') +VNPY_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) +if VNPY_ROOT not in sys.path: + sys.path.append(VNPY_ROOT) + print(f'append {VNPY_ROOT} into sys.path') from vnpy.event import EventEngine from vnpy.trader.setting import SETTINGS from vnpy.trader.engine import MainEngine from vnpy.trader.utility import load_json -from vnpy.gateway.ctp import CtpGateway +from vnpy.gateway.binancef import BinancefGateway from vnpy.app.tick_recorder import TickRecorderApp from vnpy.app.cta_strategy.base import EVENT_CTA_LOG @@ -25,7 +27,7 @@ SETTINGS["log.console"] = True -ctp_setting = load_json('connect_ctp.json') +binance_setting = load_json('connect_binancef.json') def run_child(): @@ -36,7 +38,7 @@ def run_child(): event_engine = EventEngine() main_engine = MainEngine(event_engine) - main_engine.add_gateway(CtpGateway) + main_engine.add_gateway(BinancefGateway) main_engine.add_app(TickRecorderApp) main_engine.write_log("主引擎创建成功") @@ -44,8 +46,8 @@ def run_child(): event_engine.register(EVENT_CTA_LOG, log_engine.process_log_event) main_engine.write_log("注册日志事件监听") - main_engine.connect(ctp_setting, "CTP") - main_engine.write_log("连接CTP接口") + main_engine.connect(binance_setting, "BINANCEF") + main_engine.write_log("连接币安接口") sleep(10) @@ -57,28 +59,12 @@ def run_parent(): """ Running in the parent process. """ - print("启动CTA策略守护父进程") - - # Chinese futures market trading period (day/night) - DAY_START = time(8, 45) - DAY_END = time(15, 30) - - NIGHT_START = time(20, 45) - NIGHT_END = time(2, 45) + print("启动行情记录守护父进程") child_process = None while True: - current_time = datetime.now().time() - trading = False - - # Check whether in trading period - if ( - (current_time >= DAY_START and current_time <= DAY_END) - or (current_time >= NIGHT_START) - or (current_time <= NIGHT_END) - ): - trading = True + trading = True # Start child process in trading period if trading and child_process is None: @@ -87,14 +73,6 @@ def run_parent(): child_process.start() print("子进程启动成功") - # 非记录时间则退出子进程 - if not trading and child_process is not None: - print("关闭子进程") - child_process.terminate() - child_process.join() - child_process = None - print("子进程关闭成功") - sleep(5) diff --git a/vnpy/app/cta_backtester/ui/widget.py b/vnpy/app/cta_backtester/ui/widget.py index 7c8c505c18..2aeef68338 100644 --- a/vnpy/app/cta_backtester/ui/widget.py +++ b/vnpy/app/cta_backtester/ui/widget.py @@ -241,6 +241,9 @@ def init_ui(self): self.size_line.setText(str(setting["size"])) self.pricetick_line.setText(str(setting["pricetick"])) self.capital_line.setText(str(setting["capital"])) + if setting['start']: + self.start_date_edit.setDate(datetime.strptime(setting['start'], "%Y-%m-%d")) + self.end_date_edit.setDate(datetime.strptime(setting['end'], "%Y-%m-%d")) if not setting["inverse"]: self.inverse_combo.setCurrentIndex(0) @@ -319,6 +322,8 @@ def start_backtesting(self): "pricetick": pricetick, "capital": capital, "inverse": inverse, + "start": start.strftime("%Y-%m-%d"), + "end": end.strftime("%Y-%m-%d"), } save_json(self.setting_filename, backtesting_setting) diff --git a/vnpy/app/cta_crypto/back_testing.py b/vnpy/app/cta_crypto/back_testing.py index 8c0b4af1eb..37f26674a0 100644 --- a/vnpy/app/cta_crypto/back_testing.py +++ b/vnpy/app/cta_crypto/back_testing.py @@ -20,11 +20,13 @@ import zlib import pickle from bson import binary +from itertools import product from collections import OrderedDict, defaultdict from datetime import datetime, timedelta from functools import lru_cache from pathlib import Path +import talib from .base import ( EngineType, @@ -207,6 +209,9 @@ def __init__(self, event_engine=None): self.task_id = None self.test_setting = None # 回测设置 self.strategy_setting = None # 所有回测策略得设置 + self.write_log_file = True + self.output_log = True + self.trade_list_csv_file = None def create_fund_kline(self, name, use_renko=False): """ @@ -456,6 +461,9 @@ def prepare_env(self, test_setting): """ self.test_setting = copy.copy(test_setting) + self.output_log = test_setting.get("output_log", True) + self.write_log_file = test_setting.get("write_log_file", True) + self.output('back_testing prepare_env') if 'name' in test_setting: self.set_name(test_setting.get('name')) @@ -468,23 +476,24 @@ def prepare_env(self, test_setting): self.debug = test_setting.get('debug', False) - # 更新数据目录 - if 'data_path' in test_setting: - self.data_path = test_setting.get('data_path') - else: - self.data_path = os.path.abspath(os.path.join(os.getcwd(), 'data')) + if self.write_log_file: + # 更新数据目录 + if 'data_path' in test_setting: + self.data_path = test_setting.get('data_path') + else: + self.data_path = os.path.abspath(os.path.join(os.getcwd(), 'data')) - print(f'数据输出目录:{self.data_path}') + print(f'数据输出目录:{self.data_path}') - # 更新日志目录 - if 'logs_path' in test_setting: - self.logs_path = os.path.abspath(os.path.join(test_setting.get('logs_path'), self.test_name)) - else: - self.logs_path = os.path.abspath(os.path.join(os.getcwd(), 'log', self.test_name)) - print(f'日志输出目录:{self.logs_path}') + # 更新日志目录 + if 'logs_path' in test_setting: + self.logs_path = os.path.abspath(os.path.join(test_setting.get('logs_path'), self.test_name)) + else: + self.logs_path = os.path.abspath(os.path.join(os.getcwd(), 'log', self.test_name)) + print(f'日志输出目录:{self.logs_path}') - # 创建日志 - self.create_logger(debug=self.debug) + # 创建日志 + self.create_logger(debug=self.debug) # 设置资金 if 'init_capital' in test_setting: @@ -1269,43 +1278,45 @@ def write_log(self, msg: str, strategy_name: str = None, level: int = logging.DE """记录日志""" # log = str(self.datetime) + ' ' + content # self.logList.append(log) - - if strategy_name is None: - # 写入本地log日志 - if self.logger: - self.logger.log(msg=msg, level=level) - else: - self.create_logger(debug=self.debug) - else: - if strategy_name in self.strategy_loggers: - self.strategy_loggers[strategy_name].log(msg=msg, level=level) + if self.write_log_file: + if strategy_name is None: + # 写入本地log日志 + if self.logger: + self.logger.log(msg=msg, level=level) + else: + self.create_logger(debug=self.debug) else: - self.create_logger(strategy_name=strategy_name, debug=self.debug) + if strategy_name in self.strategy_loggers: + self.strategy_loggers[strategy_name].log(msg=msg, level=level) + else: + self.create_logger(strategy_name=strategy_name, debug=self.debug) def write_error(self, msg, strategy_name=None): """记录异常""" - if strategy_name is None: - if self.logger: - self.logger.error(msg) - else: - self.create_logger(debug=self.debug) - else: - if strategy_name in self.strategy_loggers: - self.strategy_loggers[strategy_name].error(msg) + if self.write_log_file: + if strategy_name is None: + if self.logger: + self.logger.error(msg) + else: + self.create_logger(debug=self.debug) else: - self.create_logger(strategy_name=strategy_name, debug=self.debug) - try: + if strategy_name in self.strategy_loggers: self.strategy_loggers[strategy_name].error(msg) - except Exception as ex: - print('{}'.format(datetime.now()), file=sys.stderr) - print('could not create cta logger for {},excption:{},trace:{}'.format(strategy_name, str(ex), - traceback.format_exc())) - print(msg, file=sys.stderr) + else: + self.create_logger(strategy_name=strategy_name, debug=self.debug) + try: + self.strategy_loggers[strategy_name].error(msg) + except Exception as ex: + print('{}'.format(datetime.now()), file=sys.stderr) + print('could not create cta logger for {},excption:{},trace:{}'.format(strategy_name, str(ex), + traceback.format_exc())) + print(msg, file=sys.stderr) def output(self, content): """输出内容""" - print(self.test_name + "\t" + content) + if self.output_log: + print(self.test_name + "\t" + content) def realtime_calculate(self): """实时计算交易结果 @@ -1859,11 +1870,11 @@ def export_trade_result(self): s = self.test_name.replace('&', '') s = s.replace(' ', '') - trade_list_csv_file = os.path.abspath(os.path.join(self.get_logs_path(), '{}_trade_list.csv'.format(s))) + self.trade_list_csv_file = os.path.abspath(os.path.join(self.get_logs_path(), '{}_trade_list.csv'.format(s))) - self.write_log(u'save trade records to:{}'.format(trade_list_csv_file)) + self.write_log(u'save trade records to:{}'.format(self.trade_list_csv_file)) import csv - csv_write_file = open(trade_list_csv_file, 'w', encoding='utf8', newline='') + csv_write_file = open(self.trade_list_csv_file, 'w', encoding='utf8', newline='') fieldnames = ['gid', 'strategy', 'vt_symbol', 'open_time', 'open_price', 'direction', 'close_time', 'close_price', @@ -1976,8 +1987,9 @@ def show_backtesting_result(self): self.output(u'无交易结果') return {}, '' - # 导出交易清单 - self.export_trade_result() + if self.write_log_file: + # 导出交易清单 + self.export_trade_result() result_info = OrderedDict() @@ -2010,6 +2022,15 @@ def show_backtesting_result(self): result_info.update({u'每笔最大亏损': d['min_pnl']}) self.output(u'每笔最大亏损:\t%s' % format_number(d['min_pnl'])) + drawdown_list = pd.DataFrame(d["drawdown_list"]) + average_drawdown = 0 + average_drawdown = drawdown_list.mean()[0] + + self.output(u'净值平均回撤: \t%s' % format_number(average_drawdown)) + + lw_drawdown = talib.LINEARREG(np.array(d['drawdown_list']), len(self.daily_list))[-1] + self.output(u'净值线性加权回撤: \t%s' % format_number(lw_drawdown)) + result_info.update({u'净值最大回撤': min(d['drawdown_list'])}) self.output(u'净值最大回撤: \t%s' % format_number(min(d['drawdown_list']))) @@ -2046,10 +2067,11 @@ def show_backtesting_result(self): result_info.update({u'Sharpe Ratio': d['sharpe']}) self.output(u'Sharpe Ratio:\t%s' % format_number(d['sharpe'])) - # 保存回测结果/交易记录/日线统计 至数据库 - self.save_result_to_mongo(result_info) + if self.write_log_file: + # 保存回测结果/交易记录/日线统计 至数据库 + self.save_result_to_mongo(result_info) - return result_info + return d def save_setting_to_mongo(self): """ 保存测试设置到mongo中""" @@ -2240,3 +2262,65 @@ def __init__(self, open_price, open_datetime, exit_price, close_datetime, volume self.slippage = slippage * 2 * abs(self.turnover) # 滑点成本 self.pnl = ((self.exit_price - self.open_price) * volume - self.commission - self.slippage) # 净盈亏 + + +class OptimizationSetting: + """ + Setting for runnning optimization. + """ + + def __init__(self): + """""" + self.params = {} + self.target_name = "" + + def add_parameter( + self, name: str, start: float, end: float = None, step: float = None + ): + """""" + if not end and not step: + self.params[name] = [start] + return + + if start >= end: + print("参数优化起始点必须小于终止点") + return + + if step <= 0: + print("参数优化步进必须大于0") + return + + value = start + value_list = [] + + while value <= end: + value_list.append(value) + value += step + + self.params[name] = value_list + + def set_target(self, target_name: str): + """""" + self.target_name = target_name + + def generate_setting(self): + """""" + keys = self.params.keys() + values = self.params.values() + products = list(product(*values)) + + settings = [] + for p in products: + setting = dict(zip(keys, p)) + settings.append(setting) + + return settings + + def generate_setting_ga(self): + """""" + settings_ga = [] + settings = self.generate_setting() + for d in settings: + param = [tuple(i) for i in d.items()] + settings_ga.append(param) + return settings_ga \ No newline at end of file diff --git a/vnpy/app/cta_crypto/engine.py b/vnpy/app/cta_crypto/engine.py index 24408e8d77..441916cf23 100644 --- a/vnpy/app/cta_crypto/engine.py +++ b/vnpy/app/cta_crypto/engine.py @@ -63,6 +63,7 @@ from vnpy.trader.util_logger import setup_logger, logging from vnpy.trader.util_wechat import send_wx_msg +from vnpy.trader.converter import OffsetConverter from .base import ( APP_NAME, @@ -140,6 +141,8 @@ def __init__(self, main_engine: MainEngine, event_engine: EventEngine): self.vt_tradeids = set() # for filtering duplicate trade + self.offset_converter = OffsetConverter(self.main_engine) + self.positions = {} self.last_minute = None @@ -254,6 +257,8 @@ def process_order_event(self, event: Event): """""" order = event.data + self.offset_converter.update_order(order) + strategy = self.orderid_strategy_map.get(order.vt_orderid, None) if not strategy: return @@ -290,6 +295,8 @@ def process_trade_event(self, event: Event): return self.vt_tradeids.add(trade.vt_tradeid) + self.offset_converter.update_trade(trade) + strategy = self.orderid_strategy_map.get(trade.vt_orderid, None) if not strategy: return @@ -348,6 +355,8 @@ def process_position_event(self, event: Event): self.positions.update({position.vt_positionid: position}) + self.offset_converter.update_position(position) + def check_unsubscribed_symbols(self): """检查未订阅合约""" @@ -842,6 +851,10 @@ def get_position(self, vt_symbol: str, direction: Direction = Direction.NET, gat vt_position_id = f"{gateway_name}.{vt_symbol}.{direction.value}" return self.main_engine.get_position(vt_position_id) + def get_position_holding(self, vt_symbol: str, gateway_name: str = ''): + """ 查询合约在账号的持仓(包含多空)""" + return self.offset_converter.get_position_holding(vt_symbol, gateway_name) + def get_engine_type(self): """""" return self.engine_type diff --git a/vnpy/app/cta_stock/back_testing.py b/vnpy/app/cta_stock/back_testing.py index ff8a2489f0..68def0f264 100644 --- a/vnpy/app/cta_stock/back_testing.py +++ b/vnpy/app/cta_stock/back_testing.py @@ -1828,6 +1828,15 @@ def show_backtesting_result(self): result_info.update({u'每笔最大亏损': d['min_pnl']}) self.output(u'每笔最大亏损:\t%s' % format_number(d['min_pnl'])) + drawdown_list = pd.DataFrame(d["drawdown_list"]) + average_drawdown = 0 + average_drawdown = drawdown_list.mean()[0] + + self.output(u'净值平均回撤: \t%s' % format_number(average_drawdown)) + + lw_drawdown = talib.LINEARREG(np.array(d['drawdown_list']), len(self.daily_list))[-1] + self.output(u'净值线性加权回撤: \t%s' % format_number(lw_drawdown)) + result_info.update({u'净值最大回撤': min(d['drawdown_list'])}) self.output(u'净值最大回撤: \t%s' % format_number(min(d['drawdown_list']))) diff --git a/vnpy/app/cta_strategy_pro/back_testing.py b/vnpy/app/cta_strategy_pro/back_testing.py index 0b5b55794b..ad0895ac3b 100644 --- a/vnpy/app/cta_strategy_pro/back_testing.py +++ b/vnpy/app/cta_strategy_pro/back_testing.py @@ -2134,6 +2134,15 @@ def show_backtesting_result(self): result_info.update({u'每笔最大亏损': d['min_pnl']}) self.output(u'每笔最大亏损:\t%s' % format_number(d['min_pnl'])) + drawdown_list = pd.DataFrame(d["drawdown_list"]) + average_drawdown = 0 + average_drawdown = drawdown_list.mean()[0] + + self.output(u'净值平均回撤: \t%s' % format_number(average_drawdown)) + + lw_drawdown = talib.LINEARREG(np.array(d['drawdown_list']), len(self.daily_list))[-1] + self.output(u'净值线性加权回撤: \t%s' % format_number(lw_drawdown)) + result_info.update({u'净值最大回撤': min(d['drawdown_list'])}) self.output(u'净值最大回撤: \t%s' % format_number(min(d['drawdown_list']))) diff --git a/vnpy/app/cta_strategy_pro/template_spread.py b/vnpy/app/cta_strategy_pro/template_spread.py index aa8f9f80f6..bd50138f93 100644 --- a/vnpy/app/cta_strategy_pro/template_spread.py +++ b/vnpy/app/cta_strategy_pro/template_spread.py @@ -45,7 +45,6 @@ class CtaSpreadTemplate(CtaTemplate): force_trading_close = False # 强制平仓 history_orders = {} - # 逻辑过程日志 dist_fieldnames = ['datetime', 'symbol', 'volume', 'price', 'operation', 'signal', 'stop_price', 'target_price', @@ -378,7 +377,6 @@ def get_positions(self): self.write_log(u'当前持仓:{}'.format(pos_list)) return pos_list - def on_start(self): """启动策略(必须由用户继承实现)""" # 订阅主动腿/被动腿合约 @@ -415,11 +413,11 @@ def on_trade(self, trade: TradeData): if not order_info: if trade.vt_orderid in self.history_orders.keys(): order_info = self.history_orders.get(trade.vt_orderid) - # 找到委托记录 + # 找到委托记录 if order_info is not None: # 委托单记录 =》 找到 Grid grid = order_info.get('grid') - if grid: + if grid and order_info.get('offset', None) == Offset.OPEN: # 更新平均开仓/平仓得价格,数量 self.update_grid_trade(trade, grid) @@ -452,8 +450,8 @@ def update_pos(self, price, volume, operation, dt): self.position.open_pos(Direction.LONG, volume=volume) dist_record['long_pos'] = self.position.long_pos dist_record['short_pos'] = self.position.short_pos - trade.offset=Offset.OPEN - trade.direction=Direction.LONG + trade.offset = Offset.OPEN + trade.direction = Direction.LONG if operation == 'short': self.position.open_pos(Direction.SHORT, volume=volume) @@ -481,7 +479,6 @@ def update_pos(self, price, volume, operation, dt): if self.backtesting: self.cta_engine.append_trade(trade) - def save_dist(self, dist_data): """ 保存策略逻辑过程记录=》 csv文件按 @@ -660,7 +657,7 @@ def on_order_all_traded(self, order: OrderData): grid.traded_volume = 0 # 平仓完毕(cover, sell) - if order.offset != Offset.OPEN: + if order_info.get("offset", None) != Offset.OPEN: grid.open_status = False grid.close_status = True @@ -753,7 +750,7 @@ def on_order_open_canceled(self, order: OrderData): grid.order_ids.remove(order.vt_orderid) # 网格的所有委托单已经执行完毕 - #if len(grid.order_ids) == 0: + # if len(grid.order_ids) == 0: # grid.order_status = False self.gt.save() @@ -785,7 +782,7 @@ def on_order_open_canceled(self, order: OrderData): vt_orderids = self.buy(price=buy_price, volume=order_volume, vt_symbol=order_vt_symbol, - lock=order_exchange==Exchange.CFFEX, + lock=order_exchange == Exchange.CFFEX, order_type=OrderType.FAK, order_time=self.cur_datetime, grid=grid) @@ -827,7 +824,7 @@ def on_order_open_canceled(self, order: OrderData): vt_orderids = self.short(price=short_price, volume=order_volume, vt_symbol=order_vt_symbol, - lock=order_exchange==Exchange.CFFEX, + lock=order_exchange == Exchange.CFFEX, order_type=OrderType.FAK, order_time=self.cur_datetime, grid=grid) @@ -860,7 +857,7 @@ def on_order_open_canceled(self, order: OrderData): self.write_log(f'移除grid中order_ids:{order.vt_orderid}') grid.order_ids.remove(order.vt_orderid) - #if not grid.order_ids: + # if not grid.order_ids: # grid.order_status = False self.gt.save() @@ -886,7 +883,7 @@ def on_order_close_canceled(self, order: OrderData): old_order['traded'] = order.traded # order_time = old_order['order_time'] order_vt_symbol = copy(old_order['vt_symbol']) - order_symbol,order_exchange = extract_vt_symbol(order_vt_symbol) + order_symbol, order_exchange = extract_vt_symbol(order_vt_symbol) order_volume = old_order['volume'] - old_order['traded'] if order_volume <= 0: @@ -900,7 +897,7 @@ def on_order_close_canceled(self, order: OrderData): order_price = old_order['price'] order_type = old_order.get('order_type', OrderType.LIMIT) - order_retry = old_order.get('retry',1) + order_retry = old_order.get('retry', 1) grid = old_order.get('grid', None) if order_retry > 10: msg = u'{} 平仓撤单 {}/{}手, 重试平仓次数{}>10' \ @@ -911,7 +908,7 @@ def on_order_close_canceled(self, order: OrderData): if order.vt_orderid in grid.order_ids: self.write_log(f'移除grid中order_ids:{order.vt_orderid}') grid.order_ids.remove(order.vt_orderid) - #if not grid.order_ids: + # if not grid.order_ids: # grid.order_status = False self.gt.save() self.write_log(u'更新网格=>{}'.format(grid.__dict__)) @@ -940,7 +937,7 @@ def on_order_close_canceled(self, order: OrderData): vt_orderids = self.cover(price=cover_price, volume=order_volume, vt_symbol=order_vt_symbol, - lock=order_exchange==Exchange.CFFEX, + lock=order_exchange == Exchange.CFFEX, order_type=OrderType.FAK, order_time=self.cur_datetime, grid=grid) @@ -977,7 +974,7 @@ def on_order_close_canceled(self, order: OrderData): vt_orderids = self.sell(price=sell_price, volume=order_volume, vt_symbol=order_vt_symbol, - lock=order_exchange==Exchange.CFFEX, + lock=order_exchange == Exchange.CFFEX, order_type=OrderType.FAK, order_time=self.cur_datetime, grid=grid) @@ -1006,7 +1003,7 @@ def on_order_close_canceled(self, order: OrderData): if order.vt_orderid in grid.order_ids: self.write_log(f'移除grid中order_ids:{order.vt_orderid}') grid.order_ids.remove(order.vt_orderid) - #if len(grid.order_ids) == 0: + # if len(grid.order_ids) == 0: # grid.order_status = False self.gt.save() self.active_orders.update({order.vt_orderid: old_order}) @@ -1061,7 +1058,7 @@ def cancel_logic(self, dt, force=False, reopen=False): if vt_orderid in order_grid.order_ids: self.write_log(f'{vt_orderid}存在网格委托队列{order_grid.order_ids}中,移除') order_grid.order_ids.remove(vt_orderid) - #if len(order_grid.order_ids) == 0: + # if len(order_grid.order_ids) == 0: # order_grid.order_status = False continue @@ -1178,7 +1175,7 @@ def check_liquidity(self, direction=None, ask_volume=1, bid_volume=1): return False # 如果设置了方向和volume,检查是否满足 - if direction==Direction.LONG: + if direction == Direction.LONG: if self.cur_act_tick.ask_volume_1 < ask_volume: self.write_log(f'主动腿的卖1委量:{self.cur_act_tick.ask_volume_1}不满足:{ask_volume}') return False @@ -1189,7 +1186,7 @@ def check_liquidity(self, direction=None, ask_volume=1, bid_volume=1): if self.cur_act_tick.bid_volume_1 < bid_volume: self.write_log(f'主动腿的买1委量:{self.cur_act_tick.bid_volume_1}不满足:{bid_volume}') return False - if self.cur_pas_tick.ask_volume_1 < ask_volume : + if self.cur_pas_tick.ask_volume_1 < ask_volume: self.write_log(f'被动腿的卖1委量:{self.cur_pas_tick.ask_volume_1}不满足:{ask_volume}') return False @@ -1247,10 +1244,10 @@ def spd_short(self, grid: CtaGrid, force: bool = False): self.write_log(f'{self.cur_datetime}强制平仓日,不开仓') return [] # 检查流动性缺失 - if not self.check_liquidity( direction=Direction.SHORT, - ask_volume=grid.volume * self.pas_vol_ratio, - bid_volume=grid.volume * self.act_vol_ratio - ) and not force: + if not self.check_liquidity(direction=Direction.SHORT, + ask_volume=grid.volume * self.pas_vol_ratio, + bid_volume=grid.volume * self.act_vol_ratio + ) and not force: return [] # 检查涨跌停距离 if self.check_near_up_nor_down(): @@ -1275,7 +1272,7 @@ def spd_short(self, grid: CtaGrid, force: bool = False): # 开多被动腿(FAK或者限价单) pas_vt_orderids = self.buy(vt_symbol=self.pas_vt_symbol, - lock=self.pas_exchange==Exchange.CFFEX, + lock=self.pas_exchange == Exchange.CFFEX, price=self.cur_pas_tick.ask_price_1, volume=grid.volume * self.pas_vol_ratio, order_type=self.order_type, @@ -1320,7 +1317,7 @@ def spd_buy(self, grid: CtaGrid, force: bool = False): direction=Direction.LONG, ask_volume=grid.volume * self.act_vol_ratio, bid_volume=grid.volume * self.pas_vol_ratio - ) \ + ) \ and not force: return [] # 检查涨跌停距离 @@ -1333,7 +1330,7 @@ def spd_buy(self, grid: CtaGrid, force: bool = False): # 开多主动腿(FAK 或者限价单) act_vt_orderids = self.buy(vt_symbol=self.act_vt_symbol, - lock=self.act_exchange==Exchange.CFFEX, + lock=self.act_exchange == Exchange.CFFEX, price=self.cur_act_tick.ask_price_1, volume=grid.volume * self.act_vol_ratio, order_type=self.order_type, @@ -1346,7 +1343,7 @@ def spd_buy(self, grid: CtaGrid, force: bool = False): # 开空被动腿 pas_vt_orderids = self.short(vt_symbol=self.pas_vt_symbol, - lock=self.pas_exchange==Exchange.CFFEX, + lock=self.pas_exchange == Exchange.CFFEX, price=self.cur_pas_tick.bid_price_1, volume=grid.volume * self.pas_vol_ratio, order_type=self.order_type, @@ -1425,7 +1422,7 @@ def spd_sell(self, grid: CtaGrid, force: bool = False): # 主动腿多单平仓 act_vt_orderids = self.sell(vt_symbol=self.act_vt_symbol, - lock=self.act_exchange==Exchange.CFFEX, + lock=self.act_exchange == Exchange.CFFEX, price=self.cur_act_tick.bid_price_1, volume=grid.volume * self.act_vol_ratio, order_type=self.order_type, @@ -1502,7 +1499,7 @@ def spd_cover(self, grid: CtaGrid, force: bool = False): # 主动腿空单平仓 act_vt_orderids = self.cover(vt_symbol=self.act_vt_symbol, - lock=self.act_exchange==Exchange.CFFEX, + lock=self.act_exchange == Exchange.CFFEX, price=self.cur_act_tick.ask_price_1, volume=grid.volume * self.act_vol_ratio, order_type=self.order_type, diff --git a/vnpy/app/tick_recorder/engine.py b/vnpy/app/tick_recorder/engine.py index ed05d65f68..11b70297b2 100644 --- a/vnpy/app/tick_recorder/engine.py +++ b/vnpy/app/tick_recorder/engine.py @@ -4,6 +4,8 @@ """ import os import csv +import bz2 +import io from threading import Thread from queue import Queue, Empty from copy import copy @@ -39,17 +41,22 @@ def __init__(self, tick_folder: str): self.last_minute = 0 + self.last_date = None + def save_tick_data(self, tick_list: list = []): """接收外部的保存tick请求""" min = None + last_date = None for tick in tick_list: min = tick.datetime.minute + last_date = tick.datetime key = f'{tick.vt_symbol}_{tick.datetime.hour}-{tick.datetime.minute}' save_list = self.tick_dict[key] save_list.append(tick) if min is not None and min != self.last_minute: self.last_minute = min + self.last_date = last_date self.save_expire_datas() def save_expire_datas(self): @@ -67,6 +74,8 @@ def append_ticks_2_file(self, symbol: str, tick_list: list): return trading_day = tick_list[0].trading_day + if not trading_day: + trading_day = self.last_date.strftime('%Y%m%d') file_folder = os.path.abspath(os.path.join(self.tick_folder, trading_day.replace('-', '/'))) if not os.path.exists(file_folder): @@ -84,7 +93,7 @@ def append_ticks_2_file(self, symbol: str, tick_list: list): # 写入表头 print(f'create and write data into {file_name}') with open(file_name, 'a', encoding='utf8', newline='') as csvWriteFile: - writer = csv.DictWriter(f=csvWriteFile, fieldnames=dict_fieldnames, dialect='excel') + writer = csv.DictWriter(f=csvWriteFile, fieldnames=dict_fieldnames, dialect='excel', extrasaction='ignore') writer.writeheader() for tick in tick_list: d = tick.__dict__ @@ -115,6 +124,7 @@ def __init__(self, main_engine: MainEngine, event_engine: EventEngine): self.tick_recordings = {} self.tick_folder = '' + self.save_folder = '' self.load_setting() diff --git a/vnpy/component/cta_line_bar.py b/vnpy/component/cta_line_bar.py index 36bc75118b..25a7982059 100644 --- a/vnpy/component/cta_line_bar.py +++ b/vnpy/component/cta_line_bar.py @@ -18,6 +18,7 @@ from collections import OrderedDict from datetime import datetime, timedelta from pykalman import KalmanFilter +import operator from vnpy.component.base import ( Direction, @@ -876,6 +877,14 @@ def on_bar(self, bar: BarData): if self.cb_on_bar: self.cb_on_bar(bar=bar) + def pinbar(self, functionName, array: bool = False): + func = operator.methodcaller(functionName, self.open_array, self.high_array, self.low_array, self.close_array) + ret = func(ta) + if array: + return ret + else: + return ret[-1] + def check_rt_funcs(self, func): """ 1.检查调用函数名是否在实时计算函数清单中,如果没有,则添加 @@ -1950,19 +1959,12 @@ def __count_dmi(self): # 多过滤器条件,做多趋势,ADX高于前一天,上升动向> inputDmiMax if self.cur_pdi > self.cur_mdi and self.cur_adx_trend and self.cur_adxr_trend and self.cur_pdi >= self.para_dmi_max: self.signal_adx_long = True - self.write_log(u'{0}[DEBUG]Buy Signal On Bar,Pdi:{1}>Mdi:{2},adx[-1]:{3}>Adx[-2]:{4}' - .format(self.cur_tick.datetime, self.cur_pdi, self.cur_mdi, self.line_adx[-1], - self.line_adx[-2])) else: self.signal_adx_long = False # 空过滤器条件 做空趋势,ADXR高于前一天,下降动向> inputMM if self.cur_pdi < self.cur_mdi and self.cur_adx_trend and self.cur_adxr_trend and self.cur_mdi >= self.para_dmi_max: self.signal_adx_short = True - - self.write_log(u'{0}[DEBUG]Short Signal On Bar,Pdi:{1}Adx[-2]:{4}' - .format(self.cur_tick.datetime, self.cur_pdi, self.cur_mdi, self.line_adx[-1], - self.line_adx[-2])) else: self.signal_adx_short = False @@ -2143,13 +2145,13 @@ def __count_boll(self): if np.isnan(upper_list[-1]): return - if len(self.line_boll_upper) > self.max_hold_bars: + if len(self.line_boll_upper) >= self.max_hold_bars: del self.line_boll_upper[0] - if len(self.line_boll_middle) > self.max_hold_bars: + if len(self.line_boll_middle) >= self.max_hold_bars: del self.line_boll_middle[0] - if len(self.line_boll_lower) > self.max_hold_bars: + if len(self.line_boll_lower) >= self.max_hold_bars: del self.line_boll_lower[0] - if len(self.line_boll_std) > self.max_hold_bars: + if len(self.line_boll_std) >= self.max_hold_bars: del self.line_boll_std[0] # 1标准差 @@ -3258,7 +3260,7 @@ def __count_period(self, bar): if len_boll <= 6 or len_rsi <= 0: return listMid = self.line_boll_middle[-7:-1] - lastMid = self.line_boll_middle[-1] + # lastMid = self.line_boll_middle[-1] malist = ta.MA(np.array(listMid, dtype=float), 5) ma5 = malist[-1] @@ -4713,6 +4715,7 @@ def get_data(self): 'type': 'line'} indicators.update({indicator.get('name'): copy.copy(indicator)}) + # MACD (副图) if isinstance(self.para_macd_fast_len, int) and self.para_macd_fast_len > 0: indicator = { 'name': 'Dif', diff --git a/vnpy/component/cta_renko_bar.py b/vnpy/component/cta_renko_bar.py index 3b7da59a52..66a5dfe33b 100644 --- a/vnpy/component/cta_renko_bar.py +++ b/vnpy/component/cta_renko_bar.py @@ -9,6 +9,7 @@ import traceback import talib as ta import numpy as np +import pandas as pd from collections import OrderedDict from datetime import datetime, timedelta @@ -678,8 +679,14 @@ def on_bar(self, bar): # 添加bar=>lineBar self.line_bar.append(bar) - - bar_close_time = bar.datetime + timedelta(seconds=bar.seconds) + + if bar.seconds > 0: + if type(bar) == pd.Series: + bar_close_time = bar.datetime + timedelta(seconds=bar.seconds.item()) + else: + bar_close_time = bar.datetime + timedelta(seconds=bar.seconds) + else: + bar_close_time = bar.datetime if self.cur_datetime is None or self.cur_datetime < bar_close_time: self.cur_datetime = bar_close_time diff --git a/vnpy/data/renko/rebuild_crypto.py b/vnpy/data/renko/rebuild_crypto.py new file mode 100644 index 0000000000..4d645f5aea --- /dev/null +++ b/vnpy/data/renko/rebuild_crypto.py @@ -0,0 +1,232 @@ +# encoding: UTF-8 + +import os +import copy +import csv +import signal +import traceback +import bz2 +import pickle +import pandas as pd +import sys + +from datetime import datetime, timedelta + +from vnpy.data.tdx.tdx_common import FakeStrategy +from vnpy.data.renko.config import HEIGHT_LIST +from vnpy.component.cta_renko_bar import CtaRenkoBar +from vnpy.trader.object import TickData, RenkoBarData, Exchange, BarData + +class CryptoRenkoRebuild(FakeStrategy): + """ + 币安期货合约砖图bar重建 + """ + def __init__(self, setting: dict = {}): + self.symbol = None + self.price_tick = 1 + self.renko_bars = {} # bar_name: renko_bar + self.tick_folder = setting.get('tick_folder', None) + self.renko_folder = setting.get('renko_folder', None) + + self.last_close_dt_dict = {} + + def start(self, symbol, price_tick, height, start_date='2016-01-01', end_date='2099-01-01', refill=False): + """启动重建工作""" + self.price_tick = price_tick + self.underlying_symbol = symbol + self.symbol = symbol + + self.exchange = Exchange.BINANCE + + if not isinstance(height, list): + height = [height] + + db_last_close_dt = None + self.renko_bars.clear() + self.last_close_dt_dict.clear() + for h in height: + bar_name = '{}_{}'.format(self.symbol, h) + bar_setting = {'name': bar_name, + 'underlying_symbol': self.underlying_symbol, + 'symbol': self.symbol, + 'price_tick': price_tick} + + # 是否使用平滑 + if isinstance(h, str) and h.endswith('s'): + h = h.replace('s', '') + bar_setting.update({'activate_ma_tick': True}) + if 'K' not in h: + h = int(h) + + if isinstance(h, str) and 'K' in h: + kilo_height = int(h.replace('K', '')) + renko_height = price_tick * kilo_height + self.write_log(u'使用价格千分比:{}'.format(h)) + bar_setting.update({'kilo_height': kilo_height}) + else: + self.write_log(u'使用绝对砖块高度数:{}'.format(h)) + renko_height = price_tick * int(h) + bar_setting.update({'height': renko_height}) + + self.renko_bars[bar_name] = CtaRenkoBar(None, cb_on_bar=self.on_bar_renko, setting=bar_setting) + + # 重建 + if refill: + bar, bar_last_close_dt = self.get_last_bar(bar_name) + + if type(bar) == pd.Series: + # 只添加bar,不触发onbar事件 + self.renko_bars[bar_name].add_bar(bar, is_init=True) + # 重新计算砖块高度 + self.renko_bars[bar_name].update_renko_height(bar.close_price, renko_height) + if bar_last_close_dt: + bar_last_close_dt = bar_last_close_dt + timedelta(seconds=bar.seconds.item()) + self.last_close_dt_dict.update({bar_name: bar_last_close_dt}) + if db_last_close_dt: + db_last_close_dt = min(bar_last_close_dt, db_last_close_dt) + else: + db_last_close_dt = bar_last_close_dt + + # 开始时间~结束时间 + start_day = datetime.strptime(start_date, '%Y-%m-%d') + if isinstance(db_last_close_dt, datetime): + if start_day < db_last_close_dt: + start_day = db_last_close_dt + end_day = datetime.strptime(end_date, '%Y-%m-%d') + cur_trading_date = datetime.now().strftime('%Y-%m-%d') + if end_day >= datetime.now(): + end_day = datetime.strptime(cur_trading_date, '%Y-%m-%d') + timedelta(days=1) + self.write_log(u'结束日期=》{}'.format(cur_trading_date)) + + days = (end_day - start_day).days + 1 + today = datetime.now().date() + self.write_log(u'数据范围:{}~{},{}天'.format(start_day.strftime('%Y-%m-%d'), end_day.strftime('%Y-%m-%d'), days)) + + self.loaded = False + + for i in range(days): + trading_day = start_day + timedelta(days=i) + + data = "" + if today != trading_day.date(): + file_name = self.symbol + "." + self.exchange.value + ".pkb2" + tick_file = os.path.join(self.tick_folder, trading_day.strftime('%Y%m%d'), file_name) + if os.path.exists(tick_file): + tick_df = pd.read_csv(tick_file, compression='bz2') + self.run(tick_df) + else: + file_name = self.symbol + "." + self.exchange.value + "_" + today.strftime("%Y%m%d") + ".csv" + tick_file = os.path.join(self.tick_folder, trading_day.strftime('%Y%m%d'), file_name) + if os.path.exists(tick_file): + tick_df = pd.read_csv(tick_file) + self.run(tick_df) + + def run(self, tick_df): + """ + 写入 Tick数据 + """ + first_dt = tick_df.iloc[0]['datetime'] + if type(first_dt) == str: + if "." in first_dt: + datetime_format = "%Y-%m-%d %H:%M:%S.%f" + else: + datetime_format = "%Y-%m-%d %H:%M:%S" + tick_df["datetime"] = pd.to_datetime(tick_df["datetime"], format=datetime_format) + tick_df.drop_duplicates(subset=['datetime'], keep='first', inplace=True) + tick_df.set_index('datetime', inplace=True) + + for dt, tick_data in tick_df.iterrows(): + tick = TickData( + gateway_name=tick_data.get("gateway_name"), + symbol=tick_data.get("symbol"), + exchange=Exchange.BINANCE, + datetime=dt, + date=dt.strftime('%Y-%m-%d'), + time=dt.strftime('%H:%M:%S'), + trading_day=dt.strftime('%Y-%m-%d'), + last_price=float(tick_data.get("last_price")), + volume=tick_data.get("volume") + ) + for bar_name, renko_bar in self.renko_bars.items(): + + # 如果tick时间比数据库的记录时间还早,丢弃 + last_dt = self.last_close_dt_dict.get(bar_name, None) + if last_dt and tick.datetime < last_dt: + continue + + renko_bar.on_tick(tick) + + pass + + def get_last_bar(self, renko_name): + """ + 取出最后一个bar的数据 + """ + last_renko_close_dt = None + bar = None + csv_filename = os.path.join(self.renko_folder, renko_name + ".csv") + if os.path.exists(csv_filename): + renko_data = pd.read_csv(csv_filename) + renko_data = renko_data.rename(columns={ + 'close': 'close_price', + 'high': 'high_price', + 'low': 'low_price', + 'open': 'open_price',}) + bar = renko_data.iloc[-1] + last_renko_close_dt = bar.datetime + if type(last_renko_close_dt) == str: + if "." in last_renko_close_dt: + datetime_format = "%Y-%m-%d %H:%M:%S.%f" + else: + datetime_format = "%Y-%m-%d %H:%M:%S" + last_renko_close_dt = datetime.strptime(last_renko_close_dt, datetime_format) + bar.datetime = last_renko_close_dt + + return bar, last_renko_close_dt + + def on_bar_renko(self, bar, bar_name): + """bar到达,入库""" + + # 从数据库加载的内容,不重新写入 + if type(bar.exchange) == str: + return + + d = copy.copy(bar.__dict__) + d.pop('row_data', None) + # 转换数据,解决vnpy2.0中对象命名不合理得地方 + d.update({'exchange': bar.exchange.value}) + d.update({'color': bar.color.value}) + d.update({'open': d.pop('open_price')}) + d.update({'close': d.pop('close_price')}) + d.update({'high': d.pop('high_price')}) + d.update({'low': d.pop('low_price')}) + + if "high_time" not in d.keys(): + d.update({"high_time": ''}) + + if "highTime" not in d.keys(): + d.update({"highTime": ''}) + + if "low_time" not in d.keys(): + d.update({"low_time": ''}) + + if "lowTime" not in d.keys(): + d.update({"lowTime": ''}) + + if not os.path.exists(self.renko_folder): + os.makedirs(self.renko_folder, exist_ok=True) + + dict_fieldnames = sorted(list(d)) + dict_fieldnames.remove('datetime') + dict_fieldnames.insert(0, 'datetime') + + csv_filename = os.path.join(self.renko_folder, bar_name + ".csv") + if not os.path.exists(csv_filename): + with open(csv_filename, 'a', encoding='utf8', newline='') as csvWriteFile: + writer = csv.DictWriter(f=csvWriteFile, fieldnames=dict_fieldnames, dialect='excel', extrasaction='ignore') + writer.writeheader() + writer.writerow(d) + else: + with open(csv_filename, 'a', encoding='utf8', newline='') as csvWriteFile: + writer = csv.DictWriter(f=csvWriteFile, fieldnames=dict_fieldnames, dialect='excel', extrasaction='ignore') + writer.writerow(d) diff --git a/vnpy/data/renko/test_rebuild_crypto.py b/vnpy/data/renko/test_rebuild_crypto.py new file mode 100644 index 0000000000..1fb4284dd1 --- /dev/null +++ b/vnpy/data/renko/test_rebuild_crypto.py @@ -0,0 +1,22 @@ +# flake8: noqa +import os +import sys + +vnpy_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) +if vnpy_root not in sys.path: + sys.path.append(vnpy_root) + +os.environ["VNPY_TESTING"] = "1" + +from vnpy.data.renko.rebuild_crypto import CryptoRenkoRebuild + +setting = { + "tick_folder": os.path.join(vnpy_root, 'tick_data', 'binance'), + "renko_folder": os.path.join(vnpy_root, 'renko_data', 'binance') +} +builder = CryptoRenkoRebuild(setting) + +# 生成单个 +# builder.start(symbol='RB99',min_diff=1, height=10, start_date='2019-04-01', end_date='2019-09-10') +# 生成多个 +builder.start(symbol='ETHUSDT', price_tick=0.01, height=[3, 5, 10, 'K3', 'K5', 'K10'], start_date='2019-01-01', end_date='2022-02-16') diff --git a/vnpy/data/tdx/tdx_stock_data.py b/vnpy/data/tdx/tdx_stock_data.py index c16bae1010..d4cef22c38 100644 --- a/vnpy/data/tdx/tdx_stock_data.py +++ b/vnpy/data/tdx/tdx_stock_data.py @@ -129,7 +129,10 @@ def connect(self, is_reconnect: bool = False): if len(self.best_ip) == 0: from pytdx.util.best_ip import select_best_ip - self.best_ip = select_best_ip(_type='socket', proxy_ip=self.proxy_ip, proxy_port=self.proxy_port) + if len(self.proxy_ip) > 0 and self.proxy_port > 0: + self.best_ip = select_best_ip(_type='socket', proxy_ip=self.proxy_ip, proxy_port=self.proxy_port) + else: + self.best_ip = select_best_ip(_type='socket') self.config.update({'best_ip': self.best_ip}) save_cache_config(self.config, TDX_STOCK_CONFIG) diff --git a/vnpy/gateway/binancef/binancef_gateway.py b/vnpy/gateway/binancef/binancef_gateway.py index e766d45e7c..94ae59bcb2 100644 --- a/vnpy/gateway/binancef/binancef_gateway.py +++ b/vnpy/gateway/binancef/binancef_gateway.py @@ -60,7 +60,11 @@ ORDERTYPE_VT2BINANCEF: Dict[OrderType, str] = { OrderType.LIMIT: "LIMIT", - OrderType.MARKET: "MARKET" + OrderType.MARKET: "MARKET", + OrderType.TAKE_PROFIT_MARKET : "TAKE_PROFIT_MARKET", + OrderType.TAKE_PROFIT: "TAKE_PROFIT", + OrderType.STOP_LOSS: "STOP_LOSS", + OrderType.STOP_LOSS_LIMIT: "STOP_LOSS_LIMIT" } ORDERTYPE_BINANCEF2VT: Dict[str, OrderType] = {v: k for k, v in ORDERTYPE_VT2BINANCEF.items()} @@ -68,6 +72,11 @@ Direction.LONG: "BUY", Direction.SHORT: "SELL" } + +SIDE_VT2BINANCEF: Dict[Direction, str] = { + Direction.LONG: "LONG", + Direction.SHORT: "SHORT" +} DIRECTION_BINANCEF2VT: Dict[str, Direction] = {v: k for k, v in DIRECTION_VT2BINANCEF.items()} INTERVAL_VT2BINANCEF: Dict[Interval, str] = { @@ -331,7 +340,7 @@ def query_account(self) -> Request: self.add_request( method="GET", - path="/fapi/v1/account", + path="/fapi/v2/account", callback=self.on_query_account, data=data ) @@ -409,6 +418,14 @@ def send_order(self, req: OrderRequest) -> str: self.gateway.write_log(f'委托返回订单更新:{order.__dict__}') self.gateway.on_order(order) + if req.offset == Offset.OPEN: + positionSide = SIDE_VT2BINANCEF[req.direction] + else: + if req.direction == Direction.LONG: + positionSide = SIDE_VT2BINANCEF[Direction.LONG] + else: + positionSide = SIDE_VT2BINANCEF[Direction.SHORT] + data = { "security": Security.SIGNED } @@ -421,7 +438,8 @@ def send_order(self, req: OrderRequest) -> str: "price": float(req.price), "quantity": float(req.volume), "newClientOrderId": orderid, - "newOrderRespType": "ACK" + "newOrderRespType": "ACK", + "positionSide": positionSide } if req.type == OrderType.MARKET: params.pop('timeInForce', None) diff --git a/vnpy/trader/constant.py b/vnpy/trader/constant.py index 2172fc3b89..781672b216 100644 --- a/vnpy/trader/constant.py +++ b/vnpy/trader/constant.py @@ -73,6 +73,10 @@ class OrderType(Enum): STOP = "STOP" FAK = "FAK" FOK = "FOK" + TAKE_PROFIT_MARKET = "市价止赢" + TAKE_PROFIT = "止盈单" + STOP_LOSS = "止损单" + STOP_LOSS_LIMIT = "限价止损单" class OptionType(Enum): diff --git a/vnpy/trader/ui/editor.py b/vnpy/trader/ui/editor.py index a335da2a09..4b9ca4a148 100644 --- a/vnpy/trader/ui/editor.py +++ b/vnpy/trader/ui/editor.py @@ -54,6 +54,7 @@ def init_central(self) -> None: def init_menu(self) -> None: """""" bar = self.menuBar() + bar.setNativeMenuBar(False) file_menu = bar.addMenu("文件") self.add_menu_action(file_menu, "新建文件", self.new_file, "Ctrl+N") diff --git a/vnpy/trader/ui/kline/kline.py b/vnpy/trader/ui/kline/kline.py index e7b6e4dd68..f93eb87869 100644 --- a/vnpy/trader/ui/kline/kline.py +++ b/vnpy/trader/ui/kline/kline.py @@ -170,7 +170,8 @@ def onRight(self): # 鼠标左单击 def onLClick(self, pos): - print('single left click') + # print('single left click') + pass # 鼠标右单击 @@ -178,7 +179,8 @@ def onRClick(self, pos): pass def onDoubleClick(self, pos): - print('double click') + # print('double click') + pass # 鼠标左释放 @@ -733,7 +735,7 @@ def plot_all(self, redraw=True, xMin=0, xMax=-1): self.pi_volume.setLimits(xMin=xMin, xMax=xMax) self.plot_volume(redraw, xMin, xMax) # K线副图,成交量 - self.plot_sub(0, len(self.datas)) # K线副图,持仓量 + self.plot_sub(xMin, xMax) # K线副图,持仓量 self.refresh() def refresh(self): @@ -793,7 +795,7 @@ def onDown(self): x = int(x) y = self.datas[x][2] self.crosshair.signal.emit((x, y)) - print(u'onDown:countK:{},x:{},y:{},index:{}'.format(self.countK, x, y, self.index)) + # print(u'onDown:countK:{},x:{},y:{},index:{}'.format(self.countK, x, y, self.index)) except Exception as ex: print(u'{}.onDown() exception:{},trace:{}'.format(self.title, str(ex), traceback.format_exc())) @@ -810,7 +812,7 @@ def onUp(self): x = int(x) y = self.datas[x]['close'] self.crosshair.signal.emit((x, y)) - print(u'onUp:countK:{},x:{},y:{},index:{}'.format(self.countK, x, y, self.index)) + # print(u'onUp:countK:{},x:{},y:{},index:{}'.format(self.countK, x, y, self.index)) except Exception as ex: print(u'{}.onDown() exception:{},trace:{}'.format(self.title, str(ex), traceback.format_exc())) @@ -825,7 +827,7 @@ def onLeft(self): self.refresh() self.crosshair.signal.emit((x, y)) - print(u'onLeft:countK:{},x:{},y:{},index:{}'.format(self.countK, x, y, self.index)) + # print(u'onLeft:countK:{},x:{},y:{},index:{}'.format(self.countK, x, y, self.index)) except Exception as ex: print(u'{}.onLeft() exception:{},trace:{}'.format(self.title, str(ex), traceback.format_exc())) @@ -854,7 +856,7 @@ def onDoubleClick(self, pos): time_value = self.axisTime.xdict.get(x, None) self.index = x - print(u'{} doubleclick: {},x:{},index:{}'.format(self.title, time_value, x, self.index)) + # print(u'{} doubleclick: {},x:{},index:{}'.format(self.title, time_value, x, self.index)) if self.relocate_notify_func is not None and time_value is not None: self.relocate_notify_func(self.windowId, time_value, self.countK) @@ -882,7 +884,7 @@ def relocate(self, window_id, t_value, count_k): y = self.datas[x]['close'] self.refresh() self.crosshair.signal.emit((x, y)) - print(u'{} reloacate to :{},{}'.format(self.title, x, y)) + # print(u'{} reloacate to :{},{}'.format(self.title, x, y)) except Exception as ex: print(u'{}.relocate() exception:{},trace:{}'.format(self.title, str(ex), traceback.format_exc())) @@ -1587,7 +1589,7 @@ def init_ui(self): continue # 创建K线图表 - canvas = KLineWidget(display_vol=False, display_sub=True) + canvas = KLineWidget(display_vol=True, display_sub=True) canvas.show() # K线标题 canvas.KLtitle.setText(f'{kline_name}', size='18pt') @@ -1776,6 +1778,7 @@ def init_ui(self): # 创建菜单 menubar = self.menuBar() + menubar.setNativeMenuBar(False) file_menu = menubar.addMenu("File") file_menu.addAction("Cascade") file_menu.addAction("Tiled") diff --git a/vnpy/trader/ui/kline/kline_widgets.py b/vnpy/trader/ui/kline/kline_widgets.py index cd1e17c505..9968ecc57b 100644 --- a/vnpy/trader/ui/kline/kline_widgets.py +++ b/vnpy/trader/ui/kline/kline_widgets.py @@ -230,6 +230,7 @@ def initUi(self): # 创建菜单 menubar = self.menuBar() + menubar.setNativeMenuBar(False) file_menu = menubar.addMenu("File") file_menu.addAction("Cascade") file_menu.addAction("Tiled") diff --git a/vnpy/trader/ui/mainwindow.py b/vnpy/trader/ui/mainwindow.py index fb19fd55f4..45d41bf2fb 100644 --- a/vnpy/trader/ui/mainwindow.py +++ b/vnpy/trader/ui/mainwindow.py @@ -99,6 +99,7 @@ def init_dock(self) -> None: def init_menu(self) -> None: """""" bar = self.menuBar() + bar.setNativeMenuBar(False) # System menu sys_menu = bar.addMenu("系统")