[增强] 期货CtaPro模板, 组合回测引擎(支持tick级别组合回测)

This commit is contained in:
msincenselee 2020-02-24 16:48:17 +08:00
parent c5f71910d5
commit ee2fd126f4
30 changed files with 3863 additions and 2199 deletions

View File

@ -37,8 +37,11 @@
- 提供单独重启某一策略实例功能,可在线更新策略源码后,重启某一策略实例,不影响其他运行实例。 - 提供单独重启某一策略实例功能,可在线更新策略源码后,重启某一策略实例,不影响其他运行实例。
- 支持单策略多合约行情订阅,支持指数合约行情订阅 - 支持单策略多合约行情订阅,支持指数合约行情订阅
- 提供组合回测引擎能够直接加载cta_strategy_pro_setting.json文件进行组合回测。 - 提供组合回测引擎能够直接加载cta_strategy_pro_setting.json文件进行组合回测。
- 拆分组合回测引擎和回测引擎组合回测引擎支持bar/tick级别的组合回测
- 增加定时器推动策略on_timer - 增加定时器推动策略on_timer
- 增加定时推送策略持仓event - 增加定时推送策略持仓event
- 增加CtaPro模板支持精细化策略持久模板
- 增加CtaPro期货模板支持FAK委托自动换月等
8、增强主引擎包括 8、增强主引擎包括

View File

@ -5,8 +5,6 @@ qdarkstyle
requests requests
websocket-client websocket-client
peewee peewee
pymysql
psycopg2
mongoengine mongoengine
numpy numpy
pandas>=0.24.2 pandas>=0.24.2
@ -21,3 +19,5 @@ deap
pyzmq pyzmq
wmi wmi
QScintilla QScintilla
pytdx
pykalman

View File

@ -12,7 +12,7 @@ from vnpy.data.renko.rebuild_future import *
# Mongo数据库得地址renko数据库名tick文件缓存目录 # Mongo数据库得地址renko数据库名tick文件缓存目录
setting = { setting = {
"host": "192.168.0.207", "host": "127.0.0.1",
"db_name": FUTURE_RENKO_DB_NAME, "db_name": FUTURE_RENKO_DB_NAME,
"cache_folder": os.path.join(vnpy_root, 'tick_data', 'tdx', 'future') "cache_folder": os.path.join(vnpy_root, 'tick_data', 'tdx', 'future')
} }
@ -20,8 +20,12 @@ builder = FutureRenkoRebuilder(setting)
# 生成单个 # 生成单个
# builder.start(symbol='RB99',min_diff=1, height=10, start_date='2019-04-01', end_date='2019-09-10') # builder.start(symbol='RB99',min_diff=1, height=10, start_date='2019-04-01', end_date='2019-09-10')
# 生成多个 # 生成多个
builder.start(symbol='J99', price_tick=0.5, height=[10], start_date='2016-01-01', end_date='2016-02-16') #builder.start(symbol='J99', price_tick=0.5, height=[10, 'K3'], start_date='2016-01-01', end_date='2016-02-16')
# 在数据库最新renko基础上开始追加数据
builder.start(symbol='J99', price_tick=0.5, height=[10, 'K3', 'K5'], start_date='2016-01-01', refill=True)
# 导出csv # 导出csv
# builder.export(symbol='RB99',height=10, start_date='2019-04-01', end_date='2019-09-10') # builder.export(symbol='RB99',height=10, start_date='2019-04-01', end_date='2019-09-10')

View File

@ -14,21 +14,31 @@
''' '''
import json
import os
import sys import sys
import copy import copy
import traceback import traceback
import csv
from datetime import datetime, timedelta from datetime import datetime, timedelta
from queue import Queue from queue import Queue
from threading import Thread from threading import Thread
from time import time from time import time
from concurrent.futures import ThreadPoolExecutor
from vnpy.event import Event, EventEngine from vnpy.event import Event, EventEngine
from vnpy.trader.event import * from vnpy.trader.event import (
from vnpy.trader.constant import Direction EVENT_TIMER,
EVENT_ACCOUNT,
EVENT_ORDER,
EVENT_TRADE,
EVENT_POSITION,
EVENT_HISTORY_TRADE,
EVENT_HISTORY_ORDER,
EVENT_FUNDS_FLOW,
EVENT_STRATEGY_POS,
EVENT_ERROR,
EVENT_WARNING,
EVENT_CRITICAL,
)
# from vnpy.trader.constant import Direction
from vnpy.trader.engine import BaseEngine, MainEngine from vnpy.trader.engine import BaseEngine, MainEngine
from vnpy.trader.utility import get_trading_date, load_json, save_json from vnpy.trader.utility import get_trading_date, load_json, save_json
from vnpy.data.mongo.mongo_data import MongoData from vnpy.data.mongo.mongo_data import MongoData
@ -246,7 +256,7 @@ class AccountRecorder(BaseEngine):
self.save_setting() self.save_setting()
except Exception as ex: except Exception as ex:
self.main_engine.writeError(u'更新数据日期异常:{}'.format(str(ex))) self.main_engine.write_error(u'更新数据日期异常:{}'.format(str(ex)))
self.write_log(traceback.format_exc()) self.write_log(traceback.format_exc())
def get_begin_day(self, gw_name: str, data_type: str): def get_begin_day(self, gw_name: str, data_type: str):
@ -393,7 +403,7 @@ class AccountRecorder(BaseEngine):
price = self.main_engine.get_price(pos.vt_symbol) price = self.main_engine.get_price(pos.vt_symbol)
if price: if price:
data.update({'cur_price': price}) data.update({'cur_price': price})
except: except: # noqa
pass pass
self.update_data(db_name=ACCOUNT_DB_NAME, col_name=TODAY_POSITION_COL, fld=fld, data=data) self.update_data(db_name=ACCOUNT_DB_NAME, col_name=TODAY_POSITION_COL, fld=fld, data=data)
@ -561,7 +571,7 @@ class AccountRecorder(BaseEngine):
self.write_log(u'运行 {}.{} 更新 耗时:{}ms >200ms,数据:{}' self.write_log(u'运行 {}.{} 更新 耗时:{}ms >200ms,数据:{}'
.format(db_name, col_name, execute_ms, d)) .format(db_name, col_name, execute_ms, d))
except Exception as ex: except Exception as ex: # noqa
pass pass
# ---------------------------------------------------------------------- # ----------------------------------------------------------------------

View File

@ -4,7 +4,6 @@ import traceback
from datetime import datetime from datetime import datetime
from threading import Thread from threading import Thread
from pathlib import Path from pathlib import Path
from inspect import getfile
from vnpy.event import Event, EventEngine from vnpy.event import Event, EventEngine
from vnpy.trader.engine import BaseEngine, MainEngine from vnpy.trader.engine import BaseEngine, MainEngine

View File

@ -1,17 +1,18 @@
from pathlib import Path from pathlib import Path
from vnpy.trader.app import BaseApp from vnpy.trader.app import BaseApp
from vnpy.trader.constant import Direction,Offset,Status from vnpy.trader.constant import Direction,Offset,Status,Color
from vnpy.trader.object import TickData, BarData, TradeData, OrderData from vnpy.trader.object import TickData, BarData, TradeData, OrderData
from vnpy.trader.utility import BarGenerator, ArrayManager from vnpy.trader.utility import BarGenerator, ArrayManager
from .cta_position import CtaPosition from .cta_position import CtaPosition
from .cta_line_bar import CtaLineBar, CtaMinuteBar, CtaHourBar, CtaDayBar, CtaWeekBar from .cta_line_bar import CtaLineBar, CtaMinuteBar, CtaHourBar, CtaDayBar, CtaWeekBar
from .base import APP_NAME, StopOrder, CtaComponent
from .cta_policy import CtaPolicy from .cta_policy import CtaPolicy
from .cta_grid_trade import CtaGrid, CtaGridTrade from .cta_grid_trade import CtaGrid, CtaGridTrade
from .base import APP_NAME, StopOrder
from .engine import CtaEngine from .engine import CtaEngine
from .template import CtaTemplate, CtaSignal, TargetPosTemplate, CtaProTemplate from .template import CtaTemplate, CtaSignal, TargetPosTemplate, CtaProTemplate, CtaProFutureTemplate
class CtaStrategyProApp(BaseApp): class CtaStrategyProApp(BaseApp):
"""""" """"""

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
""" """
Defines constants and objects used in CtaStrategyPro App. Defines constants and objects used in CtaStrategyPro App.
""" """
import sys
from abc import ABC from abc import ABC
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum from enum import Enum
@ -93,6 +94,7 @@ INTERVAL_DELTA_MAP = {
Interval.DAILY: timedelta(days=1), Interval.DAILY: timedelta(days=1),
} }
class CtaComponent(ABC): class CtaComponent(ABC):
""" CTA策略基础组件""" """ CTA策略基础组件"""
@ -103,7 +105,6 @@ class CtaComponent(ABC):
""" """
self.strategy = strategy self.strategy = strategy
# ----------------------------------------------------------------------
def write_log(self, content: str): def write_log(self, content: str):
"""记录日志""" """记录日志"""
if self.strategy: if self.strategy:
@ -111,11 +112,9 @@ class CtaComponent(ABC):
else: else:
print(content) print(content)
# ----------------------------------------------------------------------
def write_error(self, content: str, level: int = ERROR): def write_error(self, content: str, level: int = ERROR):
"""记录错误日志""" """记录错误日志"""
if self.strategy: if self.strategy:
self.strategy.write_log(msg=content, level=level) self.strategy.write_log(msg=content, level=level)
else: else:
print(content, file=sys.stderr) print(content, file=sys.stderr)

View File

@ -9,8 +9,6 @@ import traceback
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime from datetime import datetime
from dataclasses import dataclass, field
from typing import List
from vnpy.trader.utility import get_folder_path from vnpy.trader.utility import get_folder_path
from vnpy.app.cta_strategy_pro.base import Direction, CtaComponent from vnpy.app.cta_strategy_pro.base import Direction, CtaComponent

View File

@ -12,6 +12,7 @@ import sys
import traceback import traceback
import talib as ta import talib as ta
import numpy as np import numpy as np
import csv
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -27,7 +28,7 @@ from vnpy.app.cta_strategy_pro.base import (
MARKET_ZJ) MARKET_ZJ)
from vnpy.app.cta_strategy_pro.cta_period import CtaPeriod, Period from vnpy.app.cta_strategy_pro.cta_period import CtaPeriod, Period
from vnpy.trader.object import BarData, TickData from vnpy.trader.object import BarData, TickData
from vnpy.trader.constant import Interval, Color, Exchange from vnpy.trader.constant import Interval, Color
from vnpy.trader.utility import round_to, get_trading_date, get_underlying_symbol from vnpy.trader.utility import round_to, get_trading_date, get_underlying_symbol
@ -1062,7 +1063,6 @@ class CtaLineBar(object):
self.line_bar.append(self.cur_bar) # 推入到lineBar队列 self.line_bar.append(self.cur_bar) # 推入到lineBar队列
# ----------------------------------------------------------------------
def generate_bar(self, tick: TickData): def generate_bar(self, tick: TickData):
"""生成 line Bar """ """生成 line Bar """
@ -1184,7 +1184,6 @@ class CtaLineBar(object):
if not endtick: if not endtick:
self.last_tick = tick self.last_tick = tick
# ----------------------------------------------------------------------
def __count_pre_high_low(self): def __count_pre_high_low(self):
"""计算 K线的前周期最高和最低""" """计算 K线的前周期最高和最低"""
@ -1206,7 +1205,6 @@ class CtaLineBar(object):
del self.line_pre_low[0] del self.line_pre_low[0]
self.line_pre_low.append(preLow) self.line_pre_low.append(preLow)
# ----------------------------------------------------------------------
def __count_sar(self): def __count_sar(self):
"""计算K线的SAR""" """计算K线的SAR"""
@ -1331,7 +1329,6 @@ class CtaLineBar(object):
if len(self.line_sar) > self.max_hold_bars: if len(self.line_sar) > self.max_hold_bars:
del self.line_sar[0] del self.line_sar[0]
# ----------------------------------------------------------------------
def __count_ma(self): def __count_ma(self):
"""计算K线的MA1 和MA2""" """计算K线的MA1 和MA2"""
@ -1486,7 +1483,8 @@ class CtaLineBar(object):
if self.para_ma1_len > 0: if self.para_ma1_len > 0:
count_len = min(self.bar_len, self.para_ma1_len) count_len = min(self.bar_len, self.para_ma1_len)
if count_len > 0: if count_len > 0:
close_ma_array = ta.MA(np.append(self.close_array[-count_len:], [self.line_bar[-1].close_price]), count_len) close_ma_array = ta.MA(np.append(self.close_array[-count_len:], [self.line_bar[-1].close_price]),
count_len)
self._rt_ma1 = round(float(close_ma_array[-1]), self.round_n) self._rt_ma1 = round(float(close_ma_array[-1]), self.round_n)
# 计算斜率 # 计算斜率
@ -1497,7 +1495,8 @@ class CtaLineBar(object):
if self.para_ma2_len > 0: if self.para_ma2_len > 0:
count_len = min(self.bar_len, self.para_ma2_len) count_len = min(self.bar_len, self.para_ma2_len)
if count_len > 0: if count_len > 0:
close_ma_array = ta.MA(np.append(self.close_array[-count_len:], [self.line_bar[-1].close_price]), count_len) close_ma_array = ta.MA(np.append(self.close_array[-count_len:], [self.line_bar[-1].close_price]),
count_len)
self._rt_ma2 = round(float(close_ma_array[-1]), self.round_n) self._rt_ma2 = round(float(close_ma_array[-1]), self.round_n)
# 计算斜率 # 计算斜率
@ -1508,7 +1507,8 @@ class CtaLineBar(object):
if self.para_ma3_len > 0: if self.para_ma3_len > 0:
count_len = min(self.bar_len, self.para_ma3_len) count_len = min(self.bar_len, self.para_ma3_len)
if count_len > 0: if count_len > 0:
close_ma_array = ta.MA(np.append(self.close_array[-count_len:], [self.line_bar[-1].close_price]), count_len) close_ma_array = ta.MA(np.append(self.close_array[-count_len:], [self.line_bar[-1].close_price]),
count_len)
self._rt_ma3 = round(float(close_ma_array[-1]), self.round_n) self._rt_ma3 = round(float(close_ma_array[-1]), self.round_n)
# 计算斜率 # 计算斜率
@ -1558,7 +1558,6 @@ class CtaLineBar(object):
return self.line_ma3_atan[-1] return self.line_ma3_atan[-1]
return self._rt_ma3_atan return self._rt_ma3_atan
# ----------------------------------------------------------------------
def __count_ema(self): def __count_ema(self):
"""计算K线的EMA1 和EMA2""" """计算K线的EMA1 和EMA2"""
@ -1613,7 +1612,6 @@ class CtaLineBar(object):
del self.line_ema3[0] del self.line_ema3[0]
self.line_ema3.append(barEma3) self.line_ema3.append(barEma3)
# ----------------------------------------------------------------------
def rt_count_ema(self): def rt_count_ema(self):
"""计算K线的EMA1 和EMA2""" """计算K线的EMA1 和EMA2"""
@ -1853,7 +1851,6 @@ class CtaLineBar(object):
self.line_atr3.append(self.cur_atr3) self.line_atr3.append(self.cur_atr3)
# ----------------------------------------------------------------------
def __count_vol_ma(self): def __count_vol_ma(self):
"""计算平均成交量""" """计算平均成交量"""
@ -1869,7 +1866,6 @@ class CtaLineBar(object):
del self.line_vol_ma[0] del self.line_vol_ma[0]
self.line_vol_ma.append(avgVol) self.line_vol_ma.append(avgVol)
# ----------------------------------------------------------------------
def __count_rsi(self): def __count_rsi(self):
"""计算K线的RSI""" """计算K线的RSI"""
if self.para_rsi1_len <= 0 and self.para_rsi2_len <= 0: if self.para_rsi1_len <= 0 and self.para_rsi2_len <= 0:
@ -3943,7 +3939,6 @@ class CtaLineBar(object):
return self.line_bias3[-1] return self.line_bias3[-1]
return self._rt_bias3 return self._rt_bias3
# ----------------------------------------------------------------------
def write_log(self, content): def write_log(self, content):
"""记录CTA日志""" """记录CTA日志"""
self.strategy.write_log(u'[' + self.name + u']' + content) self.strategy.write_log(u'[' + self.name + u']' + content)
@ -4586,7 +4581,6 @@ class CtaMinuteBar(CtaLineBar):
# 实时计算 # 实时计算
self.rt_executed = False self.rt_executed = False
# ----------------------------------------------------------------------
def generate_bar(self, tick): def generate_bar(self, tick):
""" """
生成 line Bar 生成 line Bar
@ -4821,8 +4815,6 @@ class CtaHourBar(CtaLineBar):
# 实时计算 # 实时计算
self.rt_executed = False self.rt_executed = False
# ----------------------------------------------------------------------
def generate_bar(self, tick): def generate_bar(self, tick):
""" """
生成 line Bar 生成 line Bar
@ -5047,7 +5039,6 @@ class CtaDayBar(CtaLineBar):
# 实时计算 # 实时计算
self.rt_executed = False self.rt_executed = False
# ----------------------------------------------------------------------
def generate_bar(self, tick): def generate_bar(self, tick):
""" """
生成 line Bar 生成 line Bar
@ -5280,7 +5271,6 @@ class CtaWeekBar(CtaLineBar):
'%Y-%m-%d %H:%M:%S') '%Y-%m-%d %H:%M:%S')
return friday_night_dt return friday_night_dt
# ----------------------------------------------------------------------
def generate_bar(self, tick): def generate_bar(self, tick):
""" """
生成 line Bar 生成 line Bar
@ -5338,4 +5328,3 @@ class CtaWeekBar(CtaLineBar):
self.rt_executed = False self.rt_executed = False
self.last_tick = tick self.last_tick = tick

View File

@ -7,6 +7,11 @@ from collections import OrderedDict
from vnpy.app.cta_strategy_pro.base import CtaComponent from vnpy.app.cta_strategy_pro.base import CtaComponent
from vnpy.trader.utility import get_folder_path from vnpy.trader.utility import get_folder_path
TNS_STATUS_OBSERVATE = 'observate'
TNS_STATUS_ORDERING = 'ordering'
TNS_STATUS_OPENED = 'opened'
TNS_STATUS_CLOSED = 'closed'
class CtaPolicy(CtaComponent): class CtaPolicy(CtaComponent):
""" """
@ -18,7 +23,7 @@ class CtaPolicy(CtaComponent):
构造 构造
:param strategy: :param strategy:
""" """
super(CtaPolicy,self).__init__(strategy=strategy, kwargs=kwargs) super().__init__(strategy=strategy, kwargs=kwargs)
self.create_time = None self.create_time = None
self.save_time = None self.save_time = None
@ -67,7 +72,8 @@ class CtaPolicy(CtaComponent):
从持久化文件中获取 从持久化文件中获取
:return: :return:
""" """
json_file = os.path.abspath(os.path.join(get_folder_path('data'), u'{}_Policy.json'.format(self.strategy.strategy_name))) json_file = os.path.abspath(
os.path.join(get_folder_path('data'), u'{}_Policy.json'.format(self.strategy.strategy_name)))
json_data = {} json_data = {}
if os.path.exists(json_file): if os.path.exists(json_file):

View File

@ -1,14 +1,13 @@
"""""" """"""
import importlib import importlib
import csv
import os import os
import sys import sys
import traceback import traceback
from collections import defaultdict from collections import defaultdict
from pathlib import Path from pathlib import Path
from typing import Any, Callable from typing import Any, Callable
from datetime import datetime, timedelta from datetime import datetime
from collections import OrderedDict from collections import OrderedDict
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from copy import copy from copy import copy
@ -19,10 +18,8 @@ from vnpy.trader.engine import BaseEngine, MainEngine
from vnpy.trader.object import ( from vnpy.trader.object import (
OrderRequest, OrderRequest,
SubscribeRequest, SubscribeRequest,
HistoryRequest,
LogData, LogData,
TickData, TickData,
BarData,
ContractData ContractData
) )
from vnpy.trader.event import ( from vnpy.trader.event import (
@ -37,8 +34,6 @@ from vnpy.trader.event import (
from vnpy.trader.constant import ( from vnpy.trader.constant import (
Direction, Direction,
OrderType, OrderType,
Interval,
Exchange,
Offset, Offset,
Status Status
) )
@ -359,12 +354,8 @@ class CtaEngine(BaseEngine):
if stop_order.vt_symbol != tick.vt_symbol: if stop_order.vt_symbol != tick.vt_symbol:
continue continue
long_triggered = ( long_triggered = stop_order.direction == Direction.LONG and tick.last_price >= stop_order.price
stop_order.direction == Direction.LONG and tick.last_price >= stop_order.price short_triggered = stop_order.direction == Direction.SHORT and tick.last_price <= stop_order.price
)
short_triggered = (
stop_order.direction == Direction.SHORT and tick.last_price <= stop_order.price
)
if long_triggered or short_triggered: if long_triggered or short_triggered:
strategy = self.strategies[stop_order.strategy_name] strategy = self.strategies[stop_order.strategy_name]
@ -1340,7 +1331,7 @@ class CtaEngine(BaseEngine):
pos_list.append(pos) pos_list.append(pos)
except Exception as ex: except Exception as ex:
self.write_error(u'分解SPD失败') self.write_error(f'分解SPD失败:{str(ex)}')
# update local pos dict # update local pos dict
self.strategy_pos_dict.update({name: pos_list}) self.strategy_pos_dict.update({name: pos_list})
@ -1439,16 +1430,21 @@ class CtaEngine(BaseEngine):
Update setting file. Update setting file.
""" """
strategy = self.strategies[strategy_name] strategy = self.strategies[strategy_name]
# 原配置
strategy_config = self.strategy_setting.get('strategy_name', {}) old_config = self.strategy_setting.get('strategy_name', {})
new_config = {
self.strategy_setting[strategy_name] = {
"class_name": strategy.__class__.__name__, "class_name": strategy.__class__.__name__,
"vt_symbol": strategy.vt_symbol, "vt_symbol": strategy.vt_symbol,
"auto_init": auto_init, "auto_init": auto_init,
"auto_start": auto_start, "auto_start": auto_start,
"setting": setting "setting": setting
} }
if old_config:
self.write_log(f'{strategy_name} 配置变更:\n{old_config} \n=> \n{new_config}')
self.strategy_setting[strategy_name] = new_config
save_json(self.setting_filename, self.strategy_setting) save_json(self.setting_filename, self.strategy_setting)
def remove_strategy_setting(self, strategy_name: str): def remove_strategy_setting(self, strategy_name: str):

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@ from vnpy.app.cta_strategy_pro import (
CtaTemplate, CtaTemplate,
StopOrder, StopOrder,
Direction, Direction,
Offset,
TickData, TickData,
BarData, BarData,
TradeData, TradeData,
@ -112,7 +111,7 @@ class TurtleSignalStrategy(CtaTemplate):
self.send_short_orders(self.entry_down) self.send_short_orders(self.entry_down)
cover_price = min(self.short_stop, self.exit_up) cover_price = min(self.short_stop, self.exit_up)
ret = self.cover(cover_price, abs(self.pos), True) self.cover(cover_price, abs(self.pos), True)
self.put_event() self.put_event()

View File

@ -2,7 +2,6 @@ from vnpy.app.cta_strategy_pro import (
CtaTemplate, CtaTemplate,
StopOrder, StopOrder,
Direction, Direction,
Offset,
TickData, TickData,
BarData, BarData,
TradeData, TradeData,
@ -10,7 +9,6 @@ from vnpy.app.cta_strategy_pro import (
BarGenerator, BarGenerator,
ArrayManager, ArrayManager,
) )
from vnpy.trader.utility import round_to from vnpy.trader.utility import round_to

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
"""""" """"""
import logging
from copy import copy from copy import copy
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime
@ -182,20 +182,24 @@ class RiskManagerEngine(BaseEngine):
if vt_accountid: if vt_accountid:
account = self.account_dict.get(vt_accountid, None) account = self.account_dict.get(vt_accountid, None)
if account: if account:
return account.balance, \ return (
account.available, \ account.balance,
round(account.frozen * 100 / (account.balance + 0.01), 2), \ account.available,
round(account.frozen * 100 / (account.balance + 0.01), 2),
self.percent_limit self.percent_limit
)
if len(self.account_dict.values()) > 0: if len(self.account_dict.values()) > 0:
account = list(self.account_dict.values())[0] account = list(self.account_dict.values())[0]
return account.balance, \ return (
account.available, \ account.balance,
round(account.frozen * 100 / (account.balance + 0.01), 2), \ account.available,
round(account.frozen * 100 / (account.balance + 0.01), 2),
self.percent_limit self.percent_limit
)
else: else:
return 0, 0, 0, 0 return 0, 0, 0, 0
def write_log(self, msg: str): def write_log(self, msg: str, source: str = "", level: int = logging.DEBUG):
"""""" """"""
log = LogData(msg=msg, gateway_name="RiskManager") log = LogData(msg=msg, gateway_name="RiskManager")
event = Event(type=EVENT_LOG, data=log) event = Event(type=EVENT_LOG, data=log)

View File

@ -1,2 +1,2 @@
from .widget import ChartWidget from .widget import ChartWidget, KlineWidget
from .item import CandleItem, VolumeItem from .item import CandleItem, VolumeItem

View File

@ -1,5 +1,5 @@
from typing import List, Dict, Type from typing import List, Dict, Type
from collections import deque
import pyqtgraph as pg import pyqtgraph as pg
from vnpy.trader.ui import QtGui, QtWidgets, QtCore from vnpy.trader.ui import QtGui, QtWidgets, QtCore
@ -11,8 +11,7 @@ from .base import (
to_int, NORMAL_FONT to_int, NORMAL_FONT
) )
from .axis import DatetimeAxis from .axis import DatetimeAxis
from .item import ChartItem from .item import ChartItem, CandleItem, VolumeItem
pg.setConfigOptions(antialias=True) pg.setConfigOptions(antialias=True)
@ -21,10 +20,10 @@ class ChartWidget(pg.PlotWidget):
"""""" """"""
MIN_BAR_COUNT = 100 MIN_BAR_COUNT = 100
def __init__(self, parent: QtWidgets.QWidget = None): def __init__(self, parent: QtWidgets.QWidget = None, title: str = "ChartWidget of vn.py"):
"""""" """"""
super().__init__(parent) super().__init__(parent)
self.title = title
self._manager: BarManager = BarManager() self._manager: BarManager = BarManager()
self._plots: Dict[str, pg.PlotItem] = {} self._plots: Dict[str, pg.PlotItem] = {}
@ -41,7 +40,7 @@ class ChartWidget(pg.PlotWidget):
def _init_ui(self) -> None: def _init_ui(self) -> None:
"""""" """"""
self.setWindowTitle("ChartWidget of vn.py") self.setWindowTitle(self.title)
self._layout = pg.GraphicsLayout() self._layout = pg.GraphicsLayout()
self._layout.setContentsMargins(10, 10, 10, 10) self._layout.setContentsMargins(10, 10, 10, 10)
@ -119,12 +118,15 @@ class ChartWidget(pg.PlotWidget):
""" """
Add chart item. Add chart item.
""" """
# 创建显示的对象蜡烛图bar图散点线等
item = item_class(self._manager) item = item_class(self._manager)
self._items[item_name] = item self._items[item_name] = item
# 获取设置的显示区域,例如主图/volume/附图等
plot = self._plots.get(plot_name) plot = self._plots.get(plot_name)
plot.addItem(item) plot.addItem(item)
# 绑定显示对象与显示区域关系
self._item_plot_map[item] = plot self._item_plot_map[item] = plot
def get_plot(self, plot_name: str) -> pg.PlotItem: def get_plot(self, plot_name: str) -> pg.PlotItem:
@ -173,6 +175,7 @@ class ChartWidget(pg.PlotWidget):
for item in self._items.values(): for item in self._items.values():
item.update_bar(bar) item.update_bar(bar)
# 刷新显示区域的最高/最低值
self._update_plot_limits() self._update_plot_limits()
if self._right_ix >= (self._manager.get_count() - self._bar_count / 2): if self._right_ix >= (self._manager.get_count() - self._bar_count / 2):
@ -480,7 +483,9 @@ class ChartCursor(QtCore.QObject):
buf[plot] += ("\n\n" + item_info_text) buf[plot] += ("\n\n" + item_info_text)
for plot_name, plot in self._plots.items(): for plot_name, plot in self._plots.items():
plot_info_text = buf[plot] plot_info_text = buf.get(plot, None)
if not plot_info_text:
continue
info = self._infos[plot_name] info = self._infos[plot_name]
info.setText(plot_info_text) info.setText(plot_info_text)
info.show() info.show()
@ -532,3 +537,124 @@ class ChartCursor(QtCore.QObject):
for label in list(self._y_labels.values()) + [self._x_label]: for label in list(self._y_labels.values()) + [self._x_label]:
label.hide() label.hide()
class KlineWidget(ChartWidget):
""" k线widget支持多widget主图/volume/附图"""
clsId = 0
def __init__(self, parent: QtWidgets.QWidget = None,
title: str = "kline",
display_volume: bool = False,
display_sub: bool = False):
super().__init__(parent, title)
KlineWidget.clsId += 1
self.windowId = str(KlineWidget.clsId)
# 所有K线上指标
self.main_color_pool = deque(['red', 'green', 'yellow', 'white'])
self.main_indicator_data = {} # 主图指标数据字典key是指标value是list
self.main_indicator_colors = {} # 主图指标颜色字典key是指标value是list
self.main_indicator_plots = {} # 主图指标的所有画布字典key是指标value是plot)
self.display_volume = display_volume
self.display_sub = display_sub
# 所有副图上指标
self.sub_color_pool = deque(['red', 'green', 'yellow', 'white'])
self.sub_indicator_data = {}
self.sub_indicator_colors = {}
self.sub_indicator_plots = {}
self.main_plot_name = f'{self.windowId}_main'
self.volume_plot_name = f'{self.windowId}_volume'
self.sub_plot_name = f'{self.windowId}_sub'
self.main_plot = None
self.volume_plot = None
self.sub_plot = None
if self.display_volume or self.display_sub:
self.add_plot(self.main_plot_name, hide_x_axis=True) # 主图
self.add_item(CandleItem, "candle", self.main_plot_name) # 往主图区域,加入
if self.display_volume:
self.add_plot(self.volume_plot_name, maximum_height=60) # volume 附图
self.add_item(VolumeItem, "volume", self.volume_plot_name)
self.volume_plot = self.get_plot(self.volume_plot_name)
if self.display_sub:
self.add_plot(self.sub_plot_name, maximum_height=180) # 附图
self.sub_plot = self.get_plot(self.sub_plot_name)
else:
self.add_plot(self.main_plot_name, hide_x_axis=False) # 主图
self.add_item(CandleItem, "candle", self.main_plot_name) # 往主图区域,加入
self.add_cursor()
self.main_plot = self.get_plot(self.main_plot_name)
def add_indicator(self, indicator: str, is_main: bool = True):
"""
新增指标信号图
:param indicator: 指标/信号的名称如ma10
:param is_main: 是否为主图
:return:
"""
if is_main:
if indicator in self.main_indicator_plots:
self.main_plot.removeItem(self.main_indicator_plots[indicator]) # 存在该指标/信号,先移除原有画布
self.main_indicator_plots[indicator] = self.main_plot.plot() # 为该指标/信号,创建新的主图画布,登记字典
self.main_indicator_colors[indicator] = self.main_color_pool[0] # 登记该指标/信号使用的颜色
self.main_color_pool.append(self.main_color_pool.popleft()) # 调整剩余颜色
if indicator not in self.main_indicator_data:
self.main_indicator_data[indicator] = []
else:
if indicator in self.sub_indicator_plots:
self.sub_plot.removeItem(self.sub_indicator_plots[indicator]) # 若存在该指标/信号,先移除原有的附图画布
self.sub_indicator_plots[indicator] = self.sub_plot.plot() # 为该指标/信号,创建新的主图画布,登记字典
self.sub_indicator_colors[indicator] = self.sub_color_pool[0] # 登记该指标/信号使用的颜色
self.sub_color_pool.append(self.sub_color_pool.popleft()) # 调整剩余颜色
if indicator not in self.sub_indicator_data:
self.sub_indicator_data[indicator] = []
def clear_indicator(self, main=True):
"""清空指标图形"""
# 清空信号图
if main:
for indicator in self.main_indicator_plots:
self.main_plot.removeItem(self.main_indicator_plots[indicator])
self.main_indicator_data = {}
self.main_indicator_plots = {}
else:
for indicator in self.sub_indicator_plots:
self.sub_plot.removeItem(self.sub_indicator_plots[indicator])
self.sub_indicator_data = {}
self.sub_indicator_plots = {}
def plot_indicator(self, datas: dict, is_main=True, clear=False):
"""
刷新指标/信号图( 新数据
:param datas: 所有数据
:param is_main: 是否为主图
:param clear: 是否要清除旧数据
:return:
"""
if clear:
self.clear_indicator(is_main) # 清除主图/副图
if is_main:
for indicator in datas:
self.add_indicator(indicator, is_main) # 逐一添加主图信号/指标
self.main_indicator_data[indicator] = datas[indicator] # 更新组件数据字典
# 调用该信号/指标画布(plotDataItem.setData()),更新数据,更新画笔颜色,更新名称
self.main_indicator_plots[indicator].setData(datas[indicator],
pen=self.main_indicator_colors[indicator][0],
name=indicator)
else:
for indicator in datas:
self.add_indicator(indicator, is_main) # 逐一增加子图指标/信号
self.sub_indicator_data[indicator] = datas[indicator] # 更新组件数据字典
# 调用该信号/指标画布(plotDataItem.setData()),更新数据,更新画笔颜色,更新名称
self.sub_indicator_plots[indicator].setData(datas[indicator],
pen=self.sub_indicator_colors[indicator][0], name=indicator)

View File

@ -3,5 +3,5 @@
HEIGHT_LIST = [3, 5, 10, 'K3', 'K5', 'K10'] HEIGHT_LIST = [3, 5, 10, 'K3', 'K5', 'K10']
FUTURE_RENKO_DB_NAME = 'FutureRenko_Db' FUTURE_RENKO_DB_NAME = 'FutureRenko'
STOCK_RENKO_DB_NAME = 'StockRenko_Db' STOCK_RENKO_DB_NAME = 'StockRenko'

View File

@ -144,7 +144,6 @@ OPTIONTYPE_CTP2VT = {
MAX_FLOAT = sys.float_info.max MAX_FLOAT = sys.float_info.max
symbol_exchange_map = {} symbol_exchange_map = {}
symbol_name_map = {} symbol_name_map = {}
symbol_size_map = {} symbol_size_map = {}
@ -791,7 +790,13 @@ class CtpTdApi(TdApi):
account.holding_profit = data['PositionProfit'] account.holding_profit = data['PositionProfit']
account.trading_day = str(data['TradingDay']) account.trading_day = str(data['TradingDay'])
if '-' not in account.trading_day and len(account.trading_day) == 8: if '-' not in account.trading_day and len(account.trading_day) == 8:
account.trading_day = account.trading_day[0:4] + '-' + account.trading_day[4:6] + '-' + account.trading_day[6:8] account.trading_day = '-'.join(
[
account.trading_day[0:4],
account.trading_day[4:6],
account.trading_day[6:8]
]
)
self.gateway.on_account(account) self.gateway.on_account(account)
@ -1121,6 +1126,7 @@ def adjust_price(price: float) -> float:
price = 0 price = 0
return price return price
class TdxMdApi(): class TdxMdApi():
""" """
通达信数据行情API实现 通达信数据行情API实现
@ -1745,38 +1751,58 @@ class TickCombiner(object):
self.gateway.on_tick(spread_tick) self.gateway.on_tick(spread_tick)
if self.is_ratio: if self.is_ratio:
ratio_tick = TickData(gateway_name=self.gateway_name, ratio_tick = TickData(
gateway_name=self.gateway_name,
symbol=self.symbol, symbol=self.symbol,
exchange=Exchange.SPD, exchange=Exchange.SPD,
datetime=tick.datetime) datetime=tick.datetime
)
ratio_tick.trading_day = tick.trading_day ratio_tick.trading_day = tick.trading_day
ratio_tick.date = tick.date ratio_tick.date = tick.date
ratio_tick.time = tick.time ratio_tick.time = tick.time
# 比率tick # 比率tick
ratio_tick.ask_price_1 = round_to(target=self.price_tick, ratio_tick.ask_price_1 = 100 * self.last_leg1_tick.ask_price_1 * self.leg1_ratio \
value=100 * self.last_leg1_tick.ask_price_1 * self.leg1_ratio / ( / (self.last_leg2_tick.bid_price_1 * self.leg2_ratio) # noqa
self.last_leg2_tick.bid_price_1 * self.leg2_ratio)) ratio_tick.ask_price_1 = round_to(
ratio_tick.ask_volume_1 = min(self.last_leg1_tick.ask_volume_1, self.last_leg2_tick.bid_volume_1) target=self.price_tick,
value=ratio_tick.ask_price_1
)
ratio_tick.ask_volume_1 = min(self.last_leg1_tick.ask_volume_1, self.last_leg2_tick.bid_volume_1)
ratio_tick.bid_price_1 = 100 * self.last_leg1_tick.bid_price_1 * self.leg1_ratio \
/ (self.last_leg2_tick.ask_price_1 * self.leg2_ratio) # noqa
ratio_tick.bid_price_1 = round_to(
target=self.price_tick,
value=ratio_tick.bid_price_1
)
ratio_tick.bid_price_1 = round_to(target=self.price_tick,
value=100 * self.last_leg1_tick.bid_price_1 * self.leg1_ratio / (
self.last_leg2_tick.ask_price_1 * self.leg2_ratio))
ratio_tick.bid_volume_1 = min(self.last_leg1_tick.bid_volume_1, self.last_leg2_tick.ask_volume_1) ratio_tick.bid_volume_1 = min(self.last_leg1_tick.bid_volume_1, self.last_leg2_tick.ask_volume_1)
ratio_tick.lastPrice = round_to(target=self.price_tick, ratio_tick.last_price = (ratio_tick.ask_price_1 + ratio_tick.bid_price_1) / 2
value=(ratio_tick.ask_price_1 + ratio_tick.bid_price_1) / 2) ratio_tick.last_price = round_to(
target=self.price_tick,
value=ratio_tick.last_price
)
# 昨收盘价 # 昨收盘价
if self.last_leg2_tick.pre_close > 0 and self.last_leg1_tick.pre_close > 0: if self.last_leg2_tick.pre_close > 0 and self.last_leg1_tick.pre_close > 0:
ratio_tick.pre_close = round_to(target=self.price_tick, ratio_tick.pre_close = 100 * self.last_leg1_tick.pre_close * self.leg1_ratio / (
value=100 * self.last_leg1_tick.pre_close * self.leg1_ratio / ( self.last_leg2_tick.pre_close * self.leg2_ratio) # noqa
self.last_leg2_tick.pre_close * self.leg2_ratio)) ratio_tick.pre_close = round_to(
target=self.price_tick,
value=ratio_tick.pre_close
)
# 开盘价 # 开盘价
if self.last_leg2_tick.open_price > 0 and self.last_leg1_tick.open_price > 0: if self.last_leg2_tick.open_price > 0 and self.last_leg1_tick.open_price > 0:
ratio_tick.open_price = round_to(target=self.price_tick, ratio_tick.open_price = 100 * self.last_leg1_tick.open_price * self.leg1_ratio / (
value=100 * self.last_leg1_tick.open_price * self.leg1_ratio / ( self.last_leg2_tick.open_price * self.leg2_ratio) # noqa
self.last_leg2_tick.open_price * self.leg2_ratio)) ratio_tick.open_price = round_to(
target=self.price_tick,
value=ratio_tick.open_price
)
# 最高价 # 最高价
if self.ratio_high: if self.ratio_high:
self.ratio_high = max(self.ratio_high, ratio_tick.ask_price_1) self.ratio_high = max(self.ratio_high, ratio_tick.ask_price_1)

0
vnpy/task/__init__.py Normal file
View File

View File

@ -40,7 +40,7 @@ print(u'Celery 使用redis配置:\nbroker:{}\nbackend:{}'.format(broker, backend
app = Celery('vnpy_task', broker=broker) app = Celery('vnpy_task', broker=broker)
# 动态导入task目录下子任务 # 动态导入task目录下子任务
app.conf.CELERY_IMPORTS = ['vnpy.task.celery_app.worker_started'] # app.conf.CELERY_IMPORTS = ['vnpy.task.celery_app.worker_started']
def worker_started(): def worker_started():

View File

@ -1,4 +1,4 @@
{ {
"celery_broker": "amqp://admin:admin@192.168.0.202:5672//", "celery_broker": "amqp://admin:admin@127.0.0.1:5672//",
"celery_backend": "amqp://admin:admin@192.168.0.202:5672//" "celery_backend": "amqp://admin:admin@127.0.0.1:5672//"
} }

View File

@ -24,7 +24,6 @@ from ..object import OrderRequest, SubscribeRequest
from ..utility import load_json, save_json from ..utility import load_json, save_json
from ..setting import SETTING_FILENAME, SETTINGS from ..setting import SETTING_FILENAME, SETTINGS
COLOR_LONG = QtGui.QColor("red") COLOR_LONG = QtGui.QColor("red")
COLOR_SHORT = QtGui.QColor("green") COLOR_SHORT = QtGui.QColor("green")
COLOR_BID = QtGui.QColor(255, 174, 201) COLOR_BID = QtGui.QColor(255, 174, 201)
@ -899,8 +898,6 @@ class TradingWidget(QtWidgets.QWidget):
except Exception as ex: except Exception as ex:
self.main_engine.write_log(u'tradingWg.autoFillSymbol exception:{}'.format(str(ex))) self.main_engine.write_log(u'tradingWg.autoFillSymbol exception:{}'.format(str(ex)))
#----------------------------------------------------------------------
def close_position(self, cell): def close_position(self, cell):
"""根据持仓信息自动填写交易组件""" """根据持仓信息自动填写交易组件"""
try: try:
@ -926,13 +923,14 @@ class TradingWidget(QtWidgets.QWidget):
self.volume_line.setText(str(pos.volume)) self.volume_line.setText(str(pos.volume))
if pos.direction in [Direction.LONG, Direction.NET]: if pos.direction in [Direction.LONG, Direction.NET]:
self.direction_combo.setCurrentText(Direction.SHORT) self.direction_combo.setCurrentText(Direction.SHORT.value)
else: else:
self.direction_combo.setCurrentText(Direction.LONG) self.direction_combo.setCurrentText(Direction.LONG.value)
except Exception as ex: except Exception as ex:
self.main_engine.write_log(u'tradingWg.closePosition exception:{}'.format(str(ex))) self.main_engine.write_log(u'tradingWg.closePosition exception:{}'.format(str(ex)))
class ActiveOrderMonitor(OrderMonitor): class ActiveOrderMonitor(OrderMonitor):
""" """
Monitor which shows active order only. Monitor which shows active order only.

View File

@ -612,6 +612,7 @@ def display_dual_axis(df, columns1, columns2=[], invert_yaxis1=False, invert_yax
else: else:
plt.show() plt.show()
class BarGenerator: class BarGenerator:
""" """
For: For:

View File

@ -0,0 +1 @@
celery -A vnpy.task.celery_app purge

View File

@ -0,0 +1 @@
celery worker -c 2 -A vnpy.task.celery_app -P eventlet -l debug -f celery.log