[增强] 期货CtaPro模板, 组合回测引擎(支持tick级别组合回测)
This commit is contained in:
parent
c5f71910d5
commit
ee2fd126f4
@ -37,8 +37,11 @@
|
||||
- 提供单独重启某一策略实例功能,可在线更新策略源码后,重启某一策略实例,不影响其他运行实例。
|
||||
- 支持单策略多合约行情订阅,支持指数合约行情订阅
|
||||
- 提供组合回测引擎,能够直接加载cta_strategy_pro_setting.json文件进行组合回测。
|
||||
- 拆分组合回测引擎和回测引擎,组合回测引擎支持bar/tick级别的组合回测
|
||||
- 增加定时器,推动策略on_timer
|
||||
- 增加定时推送策略持仓event
|
||||
- 增加CtaPro模板,支持精细化策略持久模板,
|
||||
- 增加CtaPro期货模板,支持FAK委托,自动换月等
|
||||
|
||||
8、增强主引擎,包括:
|
||||
|
||||
|
@ -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
|
||||
|
@ -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')
|
@ -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
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
""""""
|
||||
|
2159
vnpy/app/cta_strategy_pro/back_testing.py
Normal file
2159
vnpy/app/cta_strategy_pro/back_testing.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"""
|
||||
|
||||
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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), \
|
||||
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), \
|
||||
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)
|
||||
|
@ -1,2 +1,2 @@
|
||||
from .widget import ChartWidget
|
||||
from .widget import ChartWidget, KlineWidget
|
||||
from .item import CandleItem, VolumeItem
|
||||
|
@ -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] = {}
|
||||
@ -41,7 +40,7 @@ class ChartWidget(pg.PlotWidget):
|
||||
|
||||
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)
|
||||
@ -119,12 +118,15 @@ class ChartWidget(pg.PlotWidget):
|
||||
"""
|
||||
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):
|
||||
@ -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)
|
||||
|
@ -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'
|
||||
|
@ -144,7 +144,6 @@ OPTIONTYPE_CTP2VT = {
|
||||
|
||||
MAX_FLOAT = sys.float_info.max
|
||||
|
||||
|
||||
symbol_exchange_map = {}
|
||||
symbol_name_map = {}
|
||||
symbol_size_map = {}
|
||||
@ -791,7 +790,13 @@ class CtpTdApi(TdApi):
|
||||
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]
|
||||
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,
|
||||
ratio_tick = TickData(
|
||||
gateway_name=self.gateway_name,
|
||||
symbol=self.symbol,
|
||||
exchange=Exchange.SPD,
|
||||
datetime=tick.datetime)
|
||||
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
0
vnpy/task/__init__.py
Normal 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():
|
||||
|
@ -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//"
|
||||
}
|
||||
|
@ -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)
|
||||
@ -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.
|
||||
|
@ -612,6 +612,7 @@ def display_dual_axis(df, columns1, columns2=[], invert_yaxis1=False, invert_yax
|
||||
else:
|
||||
plt.show()
|
||||
|
||||
|
||||
class BarGenerator:
|
||||
"""
|
||||
For:
|
||||
|
1
win_clean_celery_jobs.bat
Normal file
1
win_clean_celery_jobs.bat
Normal file
@ -0,0 +1 @@
|
||||
celery -A vnpy.task.celery_app purge
|
1
win_start_celery_worker.bat
Normal file
1
win_start_celery_worker.bat
Normal file
@ -0,0 +1 @@
|
||||
celery worker -c 2 -A vnpy.task.celery_app -P eventlet -l debug -f celery.log
|
Loading…
Reference in New Issue
Block a user