[增强] 期货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文件进行组合回测。
- 拆分组合回测引擎和回测引擎组合回测引擎支持bar/tick级别的组合回测
- 增加定时器推动策略on_timer
- 增加定时推送策略持仓event
- 增加CtaPro模板支持精细化策略持久模板
- 增加CtaPro期货模板支持FAK委托自动换月等
8、增强主引擎包括

View File

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

View File

@ -12,7 +12,7 @@ from vnpy.data.renko.rebuild_future import *
# Mongo数据库得地址renko数据库名tick文件缓存目录
setting = {
"host": "192.168.0.207",
"host": "127.0.0.1",
"db_name": FUTURE_RENKO_DB_NAME,
"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='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
# 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 copy
import traceback
import csv
from datetime import datetime, timedelta
from queue import Queue
from threading import Thread
from time import time
from concurrent.futures import ThreadPoolExecutor
from vnpy.event import Event, EventEngine
from vnpy.trader.event import *
from vnpy.trader.constant import Direction
from vnpy.trader.event import (
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.utility import get_trading_date, load_json, save_json
from vnpy.data.mongo.mongo_data import MongoData
@ -246,7 +256,7 @@ class AccountRecorder(BaseEngine):
self.save_setting()
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())
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)
if price:
data.update({'cur_price': price})
except:
except: # noqa
pass
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,数据:{}'
.format(db_name, col_name, execute_ms, d))
except Exception as ex:
except Exception as ex: # noqa
pass
# ----------------------------------------------------------------------

View File

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

View File

@ -1,17 +1,18 @@
from pathlib import Path
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.utility import BarGenerator, ArrayManager
from .cta_position import CtaPosition
from .cta_line_bar import CtaLineBar, CtaMinuteBar, CtaHourBar, CtaDayBar, CtaWeekBar
from .base import APP_NAME, StopOrder, CtaComponent
from .cta_policy import CtaPolicy
from .cta_grid_trade import CtaGrid, CtaGridTrade
from .base import APP_NAME, StopOrder
from .engine import CtaEngine
from .template import CtaTemplate, CtaSignal, TargetPosTemplate, CtaProTemplate
from .template import CtaTemplate, CtaSignal, TargetPosTemplate, CtaProTemplate, CtaProFutureTemplate
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.
"""
import sys
from abc import ABC
from dataclasses import dataclass, field
from enum import Enum
@ -93,6 +94,7 @@ INTERVAL_DELTA_MAP = {
Interval.DAILY: timedelta(days=1),
}
class CtaComponent(ABC):
""" CTA策略基础组件"""
@ -103,7 +105,6 @@ class CtaComponent(ABC):
"""
self.strategy = strategy
# ----------------------------------------------------------------------
def write_log(self, content: str):
"""记录日志"""
if self.strategy:
@ -111,11 +112,9 @@ class CtaComponent(ABC):
else:
print(content)
# ----------------------------------------------------------------------
def write_error(self, content: str, level: int = ERROR):
"""记录错误日志"""
if self.strategy:
self.strategy.write_log(msg=content, level=level)
else:
print(content, file=sys.stderr)

View File

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

View File

@ -12,6 +12,7 @@ import sys
import traceback
import talib as ta
import numpy as np
import csv
from collections import OrderedDict
from datetime import datetime, timedelta
@ -27,7 +28,7 @@ from vnpy.app.cta_strategy_pro.base import (
MARKET_ZJ)
from vnpy.app.cta_strategy_pro.cta_period import CtaPeriod, Period
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
@ -1062,7 +1063,6 @@ class CtaLineBar(object):
self.line_bar.append(self.cur_bar) # 推入到lineBar队列
# ----------------------------------------------------------------------
def generate_bar(self, tick: TickData):
"""生成 line Bar """
@ -1184,7 +1184,6 @@ class CtaLineBar(object):
if not endtick:
self.last_tick = tick
# ----------------------------------------------------------------------
def __count_pre_high_low(self):
"""计算 K线的前周期最高和最低"""
@ -1206,7 +1205,6 @@ class CtaLineBar(object):
del self.line_pre_low[0]
self.line_pre_low.append(preLow)
# ----------------------------------------------------------------------
def __count_sar(self):
"""计算K线的SAR"""
@ -1331,7 +1329,6 @@ class CtaLineBar(object):
if len(self.line_sar) > self.max_hold_bars:
del self.line_sar[0]
# ----------------------------------------------------------------------
def __count_ma(self):
"""计算K线的MA1 和MA2"""
@ -1486,7 +1483,8 @@ class CtaLineBar(object):
if self.para_ma1_len > 0:
count_len = min(self.bar_len, self.para_ma1_len)
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)
# 计算斜率
@ -1497,7 +1495,8 @@ class CtaLineBar(object):
if self.para_ma2_len > 0:
count_len = min(self.bar_len, self.para_ma2_len)
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)
# 计算斜率
@ -1508,7 +1507,8 @@ class CtaLineBar(object):
if self.para_ma3_len > 0:
count_len = min(self.bar_len, self.para_ma3_len)
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)
# 计算斜率
@ -1558,7 +1558,6 @@ class CtaLineBar(object):
return self.line_ma3_atan[-1]
return self._rt_ma3_atan
# ----------------------------------------------------------------------
def __count_ema(self):
"""计算K线的EMA1 和EMA2"""
@ -1613,7 +1612,6 @@ class CtaLineBar(object):
del self.line_ema3[0]
self.line_ema3.append(barEma3)
# ----------------------------------------------------------------------
def rt_count_ema(self):
"""计算K线的EMA1 和EMA2"""
@ -1827,7 +1825,7 @@ class CtaLineBar(object):
if self.para_atr1_len > 0:
count_len = min(self.bar_len, self.para_atr1_len)
cur_atr1 = ta.ATR(self.high_array[-count_len * 2:], self.low_array[-count_len * 2:],
self.close_array[-count_len * 2:], count_len)
self.close_array[-count_len * 2:], count_len)
self.cur_atr1 = round(cur_atr1[-1], self.round_n)
if len(self.line_atr1) > self.max_hold_bars:
del self.line_atr1[0]
@ -1836,7 +1834,7 @@ class CtaLineBar(object):
if self.para_atr2_len > 0:
count_len = min(self.bar_len, self.para_atr2_len)
cur_atr2 = ta.ATR(self.high_array[-count_len * 2:], self.low_array[-count_len * 2:],
self.close_array[-count_len * 2:], count_len)
self.close_array[-count_len * 2:], count_len)
self.cur_atr2 = round(cur_atr2[-1], self.round_n)
if len(self.line_atr2) > self.max_hold_bars:
del self.line_atr2[0]
@ -1844,8 +1842,8 @@ class CtaLineBar(object):
if self.para_atr3_len > 0:
count_len = min(self.bar_len, self.para_atr3_len)
cur_atr3 = ta.ATR(self.high_array[-count_len * 2 :], self.low_array[-count_len * 2:],
self.close_array[-count_len * 2:], count_len)
cur_atr3 = ta.ATR(self.high_array[-count_len * 2:], self.low_array[-count_len * 2:],
self.close_array[-count_len * 2:], count_len)
self.cur_atr3 = round(cur_atr3[-1], self.round_n)
if len(self.line_atr3) > self.max_hold_bars:
@ -1853,7 +1851,6 @@ class CtaLineBar(object):
self.line_atr3.append(self.cur_atr3)
# ----------------------------------------------------------------------
def __count_vol_ma(self):
"""计算平均成交量"""
@ -1869,7 +1866,6 @@ class CtaLineBar(object):
del self.line_vol_ma[0]
self.line_vol_ma.append(avgVol)
# ----------------------------------------------------------------------
def __count_rsi(self):
"""计算K线的RSI"""
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._rt_bias3
# ----------------------------------------------------------------------
def write_log(self, content):
"""记录CTA日志"""
self.strategy.write_log(u'[' + self.name + u']' + content)
@ -4586,7 +4581,6 @@ class CtaMinuteBar(CtaLineBar):
# 实时计算
self.rt_executed = False
# ----------------------------------------------------------------------
def generate_bar(self, tick):
"""
生成 line Bar
@ -4821,8 +4815,6 @@ class CtaHourBar(CtaLineBar):
# 实时计算
self.rt_executed = False
# ----------------------------------------------------------------------
def generate_bar(self, tick):
"""
生成 line Bar
@ -5047,7 +5039,6 @@ class CtaDayBar(CtaLineBar):
# 实时计算
self.rt_executed = False
# ----------------------------------------------------------------------
def generate_bar(self, tick):
"""
生成 line Bar
@ -5280,7 +5271,6 @@ class CtaWeekBar(CtaLineBar):
'%Y-%m-%d %H:%M:%S')
return friday_night_dt
# ----------------------------------------------------------------------
def generate_bar(self, tick):
"""
生成 line Bar
@ -5338,4 +5328,3 @@ class CtaWeekBar(CtaLineBar):
self.rt_executed = False
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.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):
"""
@ -18,7 +23,7 @@ class CtaPolicy(CtaComponent):
构造
:param strategy:
"""
super(CtaPolicy,self).__init__(strategy=strategy, kwargs=kwargs)
super().__init__(strategy=strategy, kwargs=kwargs)
self.create_time = None
self.save_time = None
@ -67,7 +72,8 @@ class CtaPolicy(CtaComponent):
从持久化文件中获取
: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 = {}
if os.path.exists(json_file):

View File

@ -15,16 +15,16 @@ class CtaPosition(CtaComponent):
def __init__(self, strategy, **kwargs):
super(CtaPosition, self).__init__(strategy=strategy, kwargs=kwargs)
self.long_pos = 0 # 多仓持仓(正数)
self.short_pos = 0 # 空仓持仓(负数)
self.pos = 0 # 持仓状态 0:空仓/对空平等; >=1 净多仓 <=-1 净空仓
self.maxPos = sys.maxsize # 最大持仓量(多仓+空仓总量)
self.long_pos = 0 # 多仓持仓(正数)
self.short_pos = 0 # 空仓持仓(负数)
self.pos = 0 # 持仓状态 0:空仓/对空平等; >=1 净多仓 <=-1 净空仓
self.maxPos = sys.maxsize # 最大持仓量(多仓+空仓总量)
def open_pos(self, direction: Direction, volume: float):
"""开、加仓"""
# volume: 正整数
if direction == Direction.LONG: # 加多仓
if direction == Direction.LONG: # 加多仓
if (max(self.pos, self.long_pos) + volume) > self.maxPos:
self.write_error(content=f'开仓异常,净:{self.pos},多:{self.long_pos},加多:{volume},超过:{self.maxPos}')
@ -34,7 +34,7 @@ class CtaPosition(CtaComponent):
self.long_pos += volume
self.pos += volume
if direction == Direction.SHORT: # 加空仓
if direction == Direction.SHORT: # 加空仓
if (min(self.pos, self.short_pos) - volume) < (0 - self.maxPos):
self.write_error(content=f'开仓异常,净:{self.pos},空:{self.short_pos},加空:{volume},超过:{self.maxPos}')
@ -45,11 +45,11 @@ class CtaPosition(CtaComponent):
return True
def close_pos(self, direction: Direction, volume:float):
def close_pos(self, direction: Direction, volume: float):
"""平、减仓"""
# vol: 正整数
if direction == Direction.LONG: # 平空仓 Cover
if direction == Direction.LONG: # 平空仓 Cover
if self.short_pos + volume > 0:
self.write_error(u'平仓异常,超出仓位。净:{0},空:{1},平仓:{2}'.format(self.pos, self.short_pos, volume))
@ -61,7 +61,7 @@ class CtaPosition(CtaComponent):
# 更新上层策略的pos。该方法不推荐使用
self.strategy.pos = self.pos
if direction == Direction.SHORT: # 平多仓
if direction == Direction.SHORT: # 平多仓
if self.long_pos - volume < 0:
self.write_error(u'平仓异常,超出仓位。净:{0},多:{1},平仓:{2}'.format(self.pos, self.long_pos, volume))

View File

@ -1,14 +1,13 @@
""""""
import importlib
import csv
import os
import sys
import traceback
from collections import defaultdict
from pathlib import Path
from typing import Any, Callable
from datetime import datetime, timedelta
from datetime import datetime
from collections import OrderedDict
from concurrent.futures import ThreadPoolExecutor
from copy import copy
@ -19,10 +18,8 @@ from vnpy.trader.engine import BaseEngine, MainEngine
from vnpy.trader.object import (
OrderRequest,
SubscribeRequest,
HistoryRequest,
LogData,
TickData,
BarData,
ContractData
)
from vnpy.trader.event import (
@ -37,8 +34,6 @@ from vnpy.trader.event import (
from vnpy.trader.constant import (
Direction,
OrderType,
Interval,
Exchange,
Offset,
Status
)
@ -359,12 +354,8 @@ class CtaEngine(BaseEngine):
if stop_order.vt_symbol != tick.vt_symbol:
continue
long_triggered = (
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
)
long_triggered = 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
if long_triggered or short_triggered:
strategy = self.strategies[stop_order.strategy_name]
@ -1340,7 +1331,7 @@ class CtaEngine(BaseEngine):
pos_list.append(pos)
except Exception as ex:
self.write_error(u'分解SPD失败')
self.write_error(f'分解SPD失败:{str(ex)}')
# update local pos dict
self.strategy_pos_dict.update({name: pos_list})
@ -1439,16 +1430,21 @@ class CtaEngine(BaseEngine):
Update setting file.
"""
strategy = self.strategies[strategy_name]
strategy_config = self.strategy_setting.get('strategy_name', {})
self.strategy_setting[strategy_name] = {
# 原配置
old_config = self.strategy_setting.get('strategy_name', {})
new_config = {
"class_name": strategy.__class__.__name__,
"vt_symbol": strategy.vt_symbol,
"auto_init": auto_init,
"auto_start": auto_start,
"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)
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,
StopOrder,
Direction,
Offset,
TickData,
BarData,
TradeData,
@ -112,7 +111,7 @@ class TurtleSignalStrategy(CtaTemplate):
self.send_short_orders(self.entry_down)
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()
@ -149,7 +148,7 @@ class TurtleSignalStrategy(CtaTemplate):
if self.pos >= 4:
return
if self.cur_mi_price <= price - self.atr_value/2:
if self.cur_mi_price <= price - self.atr_value / 2:
return
t = self.pos / self.fixed_size

View File

@ -2,7 +2,6 @@ from vnpy.app.cta_strategy_pro import (
CtaTemplate,
StopOrder,
Direction,
Offset,
TickData,
BarData,
TradeData,
@ -10,7 +9,6 @@ from vnpy.app.cta_strategy_pro import (
BarGenerator,
ArrayManager,
)
from vnpy.trader.utility import round_to
@ -189,7 +187,7 @@ class TurtleSignalStrategy_v2(CtaTemplate):
self.write_log(f'买入委托编号:{refs}')
if t == 1 and self.cur_mi_price > price:
buy_price = round_to(price + self.atr_value * 0.5 , self.symbol_price_tick)
buy_price = round_to(price + self.atr_value * 0.5, self.symbol_price_tick)
self.write_log(u'发出做多停止单,触发价格为: {}'.format(buy_price))
refs = self.buy(buy_price, self.invest_pos, True)
if len(refs) > 0:

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
""""""
import logging
from copy import copy
from collections import defaultdict
from datetime import datetime
@ -182,20 +182,24 @@ class RiskManagerEngine(BaseEngine):
if vt_accountid:
account = self.account_dict.get(vt_accountid, None)
if account:
return account.balance, \
account.available, \
round(account.frozen * 100 / (account.balance + 0.01), 2), \
self.percent_limit
return (
account.balance,
account.available,
round(account.frozen * 100 / (account.balance + 0.01), 2),
self.percent_limit
)
if len(self.account_dict.values()) > 0:
account = list(self.account_dict.values())[0]
return account.balance, \
account.available, \
round(account.frozen * 100 / (account.balance + 0.01), 2), \
self.percent_limit
return (
account.balance,
account.available,
round(account.frozen * 100 / (account.balance + 0.01), 2),
self.percent_limit
)
else:
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")
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

View File

@ -1,5 +1,5 @@
from typing import List, Dict, Type
from collections import deque
import pyqtgraph as pg
from vnpy.trader.ui import QtGui, QtWidgets, QtCore
@ -11,8 +11,7 @@ from .base import (
to_int, NORMAL_FONT
)
from .axis import DatetimeAxis
from .item import ChartItem
from .item import ChartItem, CandleItem, VolumeItem
pg.setConfigOptions(antialias=True)
@ -21,10 +20,10 @@ class ChartWidget(pg.PlotWidget):
""""""
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)
self.title = title
self._manager: BarManager = BarManager()
self._plots: Dict[str, pg.PlotItem] = {}
@ -34,14 +33,14 @@ class ChartWidget(pg.PlotWidget):
self._first_plot: pg.PlotItem = None
self._cursor: ChartCursor = None
self._right_ix: int = 0 # Index of most right data
self._bar_count: int = self.MIN_BAR_COUNT # Total bar visible in chart
self._right_ix: int = 0 # Index of most right data
self._bar_count: int = self.MIN_BAR_COUNT # Total bar visible in chart
self._init_ui()
def _init_ui(self) -> None:
""""""
self.setWindowTitle("ChartWidget of vn.py")
self.setWindowTitle(self.title)
self._layout = pg.GraphicsLayout()
self._layout.setContentsMargins(10, 10, 10, 10)
@ -59,11 +58,11 @@ class ChartWidget(pg.PlotWidget):
self, self._manager, self._plots, self._item_plot_map)
def add_plot(
self,
plot_name: str,
minimum_height: int = 80,
maximum_height: int = None,
hide_x_axis: bool = False
self,
plot_name: str,
minimum_height: int = 80,
maximum_height: int = None,
hide_x_axis: bool = False
) -> None:
"""
Add plot area.
@ -111,20 +110,23 @@ class ChartWidget(pg.PlotWidget):
self._layout.addItem(plot)
def add_item(
self,
item_class: Type[ChartItem],
item_name: str,
plot_name: str
self,
item_class: Type[ChartItem],
item_name: str,
plot_name: str
):
"""
Add chart item.
"""
# 创建显示的对象蜡烛图bar图散点线等
item = item_class(self._manager)
self._items[item_name] = item
# 获取设置的显示区域,例如主图/volume/附图等
plot = self._plots.get(plot_name)
plot.addItem(item)
# 绑定显示对象与显示区域关系
self._item_plot_map[item] = plot
def get_plot(self, plot_name: str) -> pg.PlotItem:
@ -173,6 +175,7 @@ class ChartWidget(pg.PlotWidget):
for item in self._items.values():
item.update_bar(bar)
# 刷新显示区域的最高/最低值
self._update_plot_limits()
if self._right_ix >= (self._manager.get_count() - self._bar_count / 2):
@ -306,11 +309,11 @@ class ChartCursor(QtCore.QObject):
""""""
def __init__(
self,
widget: ChartWidget,
manager: BarManager,
plots: Dict[str, pg.GraphicsObject],
item_plot_map: Dict[ChartItem, pg.GraphicsObject]
self,
widget: ChartWidget,
manager: BarManager,
plots: Dict[str, pg.GraphicsObject],
item_plot_map: Dict[ChartItem, pg.GraphicsObject]
):
""""""
super().__init__()
@ -480,7 +483,9 @@ class ChartCursor(QtCore.QObject):
buf[plot] += ("\n\n" + item_info_text)
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.setText(plot_info_text)
info.show()
@ -532,3 +537,124 @@ class ChartCursor(QtCore.QObject):
for label in list(self._y_labels.values()) + [self._x_label]:
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']
FUTURE_RENKO_DB_NAME = 'FutureRenko_Db'
STOCK_RENKO_DB_NAME = 'StockRenko_Db'
FUTURE_RENKO_DB_NAME = 'FutureRenko'
STOCK_RENKO_DB_NAME = 'StockRenko'

View File

@ -144,7 +144,6 @@ OPTIONTYPE_CTP2VT = {
MAX_FLOAT = sys.float_info.max
symbol_exchange_map = {}
symbol_name_map = {}
symbol_size_map = {}
@ -223,7 +222,7 @@ class CtpGateway(BaseGateway):
self.combiner_conf_dict = c.get_config()
if len(self.combiner_conf_dict) > 0:
self.write_log(u'加载的自定义价差/价比配置:{}'.format(self.combiner_conf_dict))
except Exception as ex: # noqa
except Exception as ex: # noqa
pass
if not self.td_api:
self.td_api = CtpTdApi(self)
@ -790,8 +789,14 @@ class CtpTdApi(TdApi):
account.close_profit = data['CloseProfit']
account.holding_profit = data['PositionProfit']
account.trading_day = str(data['TradingDay'])
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]
if '-' not in account.trading_day and len(account.trading_day) == 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)
@ -1121,6 +1126,7 @@ def adjust_price(price: float) -> float:
price = 0
return price
class TdxMdApi():
"""
通达信数据行情API实现
@ -1745,38 +1751,58 @@ class TickCombiner(object):
self.gateway.on_tick(spread_tick)
if self.is_ratio:
ratio_tick = TickData(gateway_name=self.gateway_name,
symbol=self.symbol,
exchange=Exchange.SPD,
datetime=tick.datetime)
ratio_tick = TickData(
gateway_name=self.gateway_name,
symbol=self.symbol,
exchange=Exchange.SPD,
datetime=tick.datetime
)
ratio_tick.trading_day = tick.trading_day
ratio_tick.date = tick.date
ratio_tick.time = tick.time
# 比率tick
ratio_tick.ask_price_1 = round_to(target=self.price_tick,
value=100 * self.last_leg1_tick.ask_price_1 * self.leg1_ratio / (
self.last_leg2_tick.bid_price_1 * self.leg2_ratio))
ratio_tick.ask_volume_1 = min(self.last_leg1_tick.ask_volume_1, self.last_leg2_tick.bid_volume_1)
ratio_tick.ask_price_1 = 100 * self.last_leg1_tick.ask_price_1 * self.leg1_ratio \
/ (self.last_leg2_tick.bid_price_1 * self.leg2_ratio) # noqa
ratio_tick.ask_price_1 = round_to(
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.lastPrice = round_to(target=self.price_tick,
value=(ratio_tick.ask_price_1 + ratio_tick.bid_price_1) / 2)
ratio_tick.last_price = (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:
ratio_tick.pre_close = round_to(target=self.price_tick,
value=100 * self.last_leg1_tick.pre_close * self.leg1_ratio / (
self.last_leg2_tick.pre_close * self.leg2_ratio))
ratio_tick.pre_close = 100 * self.last_leg1_tick.pre_close * self.leg1_ratio / (
self.last_leg2_tick.pre_close * self.leg2_ratio) # noqa
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:
ratio_tick.open_price = round_to(target=self.price_tick,
value=100 * self.last_leg1_tick.open_price * self.leg1_ratio / (
self.last_leg2_tick.open_price * self.leg2_ratio))
ratio_tick.open_price = 100 * self.last_leg1_tick.open_price * self.leg1_ratio / (
self.last_leg2_tick.open_price * self.leg2_ratio) # noqa
ratio_tick.open_price = round_to(
target=self.price_tick,
value=ratio_tick.open_price
)
# 最高价
if self.ratio_high:
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)
# 动态导入task目录下子任务
app.conf.CELERY_IMPORTS = ['vnpy.task.celery_app.worker_started']
# app.conf.CELERY_IMPORTS = ['vnpy.task.celery_app.worker_started']
def worker_started():

View File

@ -1,4 +1,4 @@
{
"celery_broker": "amqp://admin:admin@192.168.0.202:5672//",
"celery_backend": "amqp://admin:admin@192.168.0.202:5672//"
"celery_broker": "amqp://admin:admin@127.0.0.1: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 ..setting import SETTING_FILENAME, SETTINGS
COLOR_LONG = QtGui.QColor("red")
COLOR_SHORT = QtGui.QColor("green")
COLOR_BID = QtGui.QColor(255, 174, 201)
@ -640,7 +639,7 @@ class TradingWidget(QtWidgets.QWidget):
form1.addRow("方向", self.direction_combo)
form1.addRow("开平", self.offset_combo)
form1.addRow("类型", self.order_type_combo)
form1.addRow( self.checkFixed, self.price_line)
form1.addRow(self.checkFixed, self.price_line)
form1.addRow("数量", self.volume_line)
form1.addRow("接口", self.gateway_combo)
form1.addRow(send_button)
@ -899,8 +898,6 @@ class TradingWidget(QtWidgets.QWidget):
except Exception as ex:
self.main_engine.write_log(u'tradingWg.autoFillSymbol exception:{}'.format(str(ex)))
#----------------------------------------------------------------------
def close_position(self, cell):
"""根据持仓信息自动填写交易组件"""
try:
@ -926,13 +923,14 @@ class TradingWidget(QtWidgets.QWidget):
self.volume_line.setText(str(pos.volume))
if pos.direction in [Direction.LONG, Direction.NET]:
self.direction_combo.setCurrentText(Direction.SHORT)
self.direction_combo.setCurrentText(Direction.SHORT.value)
else:
self.direction_combo.setCurrentText(Direction.LONG)
self.direction_combo.setCurrentText(Direction.LONG.value)
except Exception as ex:
self.main_engine.write_log(u'tradingWg.closePosition exception:{}'.format(str(ex)))
class ActiveOrderMonitor(OrderMonitor):
"""
Monitor which shows active order only.

View File

@ -394,7 +394,7 @@ def save_df_to_excel(file_name, sheet_name, df):
import openpyxl
from openpyxl.utils.dataframe import dataframe_to_rows
# from openpyxl.drawing.image import Image
except: # noqa
except: # noqa
print(u'can not import openpyxl', file=sys.stderr)
if 'openpyxl' not in sys.modules:
@ -407,7 +407,7 @@ def save_df_to_excel(file_name, sheet_name, df):
try:
# 读取文件
wb = openpyxl.load_workbook(file_name)
except: # noqa
except: # noqa
# 创建一个excel workbook
wb = openpyxl.Workbook()
ws = wb.active
@ -416,7 +416,7 @@ def save_df_to_excel(file_name, sheet_name, df):
# 定位WorkSheet
if ws is None:
ws = wb[sheet_name]
except: # noqa
except: # noqa
# 创建一个WorkSheet
ws = wb.create_sheet()
ws.title = sheet_name
@ -450,7 +450,7 @@ def save_text_to_excel(file_name, sheet_name, text):
import openpyxl
# from openpyxl.utils.dataframe import dataframe_to_rows
# from openpyxl.drawing.image import Image
except: # noqa
except: # noqa
print(u'can not import openpyxl', file=sys.stderr)
if 'openpyxl' not in sys.modules:
@ -461,7 +461,7 @@ def save_text_to_excel(file_name, sheet_name, text):
try:
# 读取文件
wb = openpyxl.load_workbook(file_name)
except: # noqa
except: # noqa
# 创建一个excel workbook
wb = openpyxl.Workbook()
ws = wb.active
@ -470,7 +470,7 @@ def save_text_to_excel(file_name, sheet_name, text):
# 定位WorkSheet
if ws is None:
ws = wb[sheet_name]
except: # noqa
except: # noqa
# 创建一个WorkSheet
ws = wb.create_sheet()
ws.title = sheet_name
@ -516,7 +516,7 @@ def save_images_to_excel(file_name, sheet_name, image_names):
try:
# 读取文件
wb = openpyxl.load_workbook(file_name)
except: # noqa
except: # noqa
# 创建一个excel workbook
wb = openpyxl.Workbook()
ws = wb.active
@ -612,6 +612,7 @@ def display_dual_axis(df, columns1, columns2=[], invert_yaxis1=False, invert_yax
else:
plt.show()
class BarGenerator:
"""
For:
@ -624,11 +625,11 @@ class BarGenerator:
"""
def __init__(
self,
on_bar: Callable,
window: int = 0,
on_window_bar: Callable = None,
interval: Interval = Interval.MINUTE
self,
on_bar: Callable,
window: int = 0,
on_window_bar: Callable = None,
interval: Interval = Interval.MINUTE
):
"""Constructor"""
self.bar = None
@ -1225,7 +1226,7 @@ def get_bars(csv_file: str,
symbol: str,
exchange: Exchange,
start_date: datetime = None,
end_date: datetime = None,):
end_date: datetime = None, ):
"""
获取bar
数据存储目录: 项目/bar_data

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