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("系统")