支持数字货币,增加多周期数据展示
This commit is contained in:
parent
fd8ff33731
commit
7f76e50501
0
vnpy/data/binance/__init__.py
Normal file
0
vnpy/data/binance/__init__.py
Normal file
119
vnpy/data/binance/binance_data.py
Normal file
119
vnpy/data/binance/binance_data.py
Normal file
@ -0,0 +1,119 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
# 从binance下载数据
|
||||
from datetime import datetime, timezone
|
||||
import sys
|
||||
import requests
|
||||
import execjs
|
||||
import traceback
|
||||
from vnpy.trader.app.ctaStrategy.ctaBase import CtaBarData
|
||||
from vnpy.api.binance.client import Client
|
||||
from vnpy.trader.vtFunction import getJsonPath
|
||||
from vnpy.trader.vtGlobal import globalSetting
|
||||
import json
|
||||
|
||||
PERIOD_MAPPING = {}
|
||||
PERIOD_MAPPING['1min'] = '1m'
|
||||
PERIOD_MAPPING['3min'] = '3m'
|
||||
PERIOD_MAPPING['5min'] = '5m'
|
||||
PERIOD_MAPPING['15min'] = '15m'
|
||||
PERIOD_MAPPING['30min'] = '30m'
|
||||
PERIOD_MAPPING['1hour'] = '1h'
|
||||
PERIOD_MAPPING['2hour'] = '2h'
|
||||
PERIOD_MAPPING['4hour'] = '4h'
|
||||
PERIOD_MAPPING['6hour'] = '6h'
|
||||
PERIOD_MAPPING['8hour'] = '8h'
|
||||
PERIOD_MAPPING['12hour'] = '12h'
|
||||
PERIOD_MAPPING['1day'] = '1d'
|
||||
PERIOD_MAPPING['3day'] = '3d'
|
||||
PERIOD_MAPPING['1week'] = '1w'
|
||||
PERIOD_MAPPING['1month'] = '1M'
|
||||
|
||||
SYMBOL_LIST = ['ltc_btc', 'eth_btc', 'etc_btc', 'bch_btc', 'btc_usdt', 'eth_usdt', 'ltc_usdt', 'etc_usdt', 'bch_usdt',
|
||||
'etc_eth','bt1_btc','bt2_btc','btg_btc','qtum_btc','hsr_btc','neo_btc','gas_btc',
|
||||
'qtum_usdt','hsr_usdt','neo_usdt','gas_usdt']
|
||||
|
||||
|
||||
class BinanceData(object):
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def __init__(self, strategy):
|
||||
"""
|
||||
构造函数
|
||||
:param strategy: 上层策略,主要用与使用strategy.writeCtaLog()
|
||||
"""
|
||||
self.strategy = strategy
|
||||
self.client = None
|
||||
self.init_client()
|
||||
|
||||
def init_client(self):
|
||||
fileName = globalSetting.get('gateway_name', '') + '_connect.json'
|
||||
filePath = getJsonPath(fileName, __file__)
|
||||
try:
|
||||
with open(filePath, 'r') as f:
|
||||
# 解析json文件
|
||||
setting = json.load(f)
|
||||
apiKey = setting.get('accessKey',None)
|
||||
secretKey = setting.get('secretKey',None)
|
||||
|
||||
if apiKey is not None and secretKey is not None:
|
||||
self.client = Client(apiKey, secretKey)
|
||||
|
||||
except IOError:
|
||||
self.strategy.writeCtaError(u'BINANCE读取连接配置{}出错,请检查'.format(filePath))
|
||||
return
|
||||
|
||||
def get_bars(self, symbol, period, callback, bar_is_completed=False,bar_freq=1, start_dt=None):
|
||||
"""
|
||||
返回k线数据
|
||||
symbol:合约
|
||||
period: 周期: 1min,3min,5min,15min,30min,1day,3day,1hour,2hour,4hour,6hour,12hour
|
||||
"""
|
||||
if symbol not in SYMBOL_LIST:
|
||||
self.strategy.writeCtaError(u'{} 合约{}不在下载清单中'.format(datetime.now(), symbol))
|
||||
return False
|
||||
if period not in PERIOD_MAPPING:
|
||||
self.strategy.writeCtaError(u'{} 周期{}不在下载清单中'.format(datetime.now(), period))
|
||||
return False
|
||||
if self.client is None:
|
||||
return False
|
||||
|
||||
binance_symbol = symbol.upper().replace('_' , '')
|
||||
binance_period = PERIOD_MAPPING.get(period)
|
||||
|
||||
self.strategy.writeCtaLog('{}开始下载binance:{} {}数据.'.format(datetime.now(), binance_symbol, binance_period))
|
||||
|
||||
bars = []
|
||||
try:
|
||||
bars = self.client.get_klines(symbol=binance_symbol, interval=binance_period)
|
||||
for i, bar in enumerate(bars):
|
||||
add_bar = CtaBarData()
|
||||
try:
|
||||
add_bar.vtSymbol = symbol
|
||||
add_bar.symbol = symbol
|
||||
add_bar.datetime = datetime.fromtimestamp(bar[0] / 1000)
|
||||
add_bar.date = add_bar.datetime.strftime('%Y-%m-%d')
|
||||
add_bar.time = add_bar.datetime.strftime('%H:%M:%S')
|
||||
add_bar.tradingDay = add_bar.date
|
||||
add_bar.open = float(bar[1])
|
||||
add_bar.high = float(bar[2])
|
||||
add_bar.low = float(bar[3])
|
||||
add_bar.close = float(bar[4])
|
||||
add_bar.volume = float(bar[5])
|
||||
except Exception as ex:
|
||||
self.strategy.writeCtaError(
|
||||
'error when convert bar:{},ex:{},t:{}'.format(bar, str(ex), traceback.format_exc()))
|
||||
return False
|
||||
|
||||
if start_dt is not None and bar.datetime < start_dt:
|
||||
continue
|
||||
|
||||
if callback is not None:
|
||||
callback(add_bar, bar_is_completed, bar_freq)
|
||||
|
||||
return True
|
||||
except Exception as ex:
|
||||
self.strategy.writeCtaError('exception in get:{},{},{}'.format(binance_symbol,str(ex), traceback.format_exc()))
|
||||
return False
|
||||
|
||||
|
0
vnpy/data/okex/__init__.py
Normal file
0
vnpy/data/okex/__init__.py
Normal file
87
vnpy/data/okex/okex_data.py
Normal file
87
vnpy/data/okex/okex_data.py
Normal file
@ -0,0 +1,87 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
# 从okex下载数据
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import requests
|
||||
import execjs
|
||||
import traceback
|
||||
from vnpy.trader.app.ctaStrategy.ctaBase import CtaBarData, CtaTickData
|
||||
|
||||
period_list = ['1min','3min','5min','15min','30min','1day','1week','1hour','2hour','4hour','6hour','12hour']
|
||||
symbol_list = ['ltc_btc','eth_btc','etc_btc','bch_btc','btc_usdt','eth_usdt','ltc_usdt','etc_usdt','bch_usdt',
|
||||
'etc_eth','bt1_btc','bt2_btc','btg_btc','qtum_btc','hsr_btc','neo_btc','gas_btc',
|
||||
'qtum_usdt','hsr_usdt','neo_usdt','gas_usdt']
|
||||
|
||||
|
||||
class OkexData(object):
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def __init__(self, strategy):
|
||||
"""
|
||||
构造函数
|
||||
:param strategy: 上层策略,主要用与使用strategy.writeCtaLog()
|
||||
"""
|
||||
self.strategy = strategy
|
||||
|
||||
# 设置HTTP请求的尝试次数,建立连接session
|
||||
requests.adapters.DEFAULT_RETRIES = 5
|
||||
self.session = requests.session()
|
||||
self.session.keep_alive = False
|
||||
|
||||
def get_bars(self, symbol, period, callback, bar_is_completed=False,bar_freq=1, start_dt=None):
|
||||
"""
|
||||
返回k线数据
|
||||
symbol:合约
|
||||
period: 周期: 1min,3min,5min,15min,30min,1day,3day,1hour,2hour,4hour,6hour,12hour
|
||||
"""
|
||||
if symbol not in symbol_list:
|
||||
self.strategy.writeCtaError(u'{} {}不在下载清单中'.format(datetime.now(), symbol))
|
||||
return
|
||||
|
||||
url = u'https://www.okex.com/api/v1/kline.do?symbol={}&type={}'.format(symbol, period)
|
||||
self.strategy.writeCtaLog('{}开始下载:{} {}数据.URL:{}'.format(datetime.now(), symbol, period,url))
|
||||
|
||||
content = None
|
||||
try:
|
||||
content = self.session.get(url).content.decode('gbk')
|
||||
except Exception as ex:
|
||||
self.strategy.writeCtaError('exception in get:{},{},{}'.format(url,str(ex), traceback.format_exc()))
|
||||
return
|
||||
|
||||
bars = execjs.eval(content)
|
||||
|
||||
for i, bar in enumerate(bars):
|
||||
if len(bar) < 5:
|
||||
self.strategy.writeCtaError('error when import bar:{}'.format(bar))
|
||||
return False
|
||||
if i == 0:
|
||||
continue
|
||||
add_bar = CtaBarData()
|
||||
try:
|
||||
add_bar.vtSymbol = symbol
|
||||
add_bar.symbol = symbol
|
||||
add_bar.datetime = datetime.fromtimestamp(bar[0] / 1000)
|
||||
add_bar.date = add_bar.datetime.strftime('%Y-%m-%d')
|
||||
add_bar.time = add_bar.datetime.strftime('%H:%M:%S')
|
||||
add_bar.tradingDay = add_bar.date
|
||||
add_bar.open = float(bar[1])
|
||||
add_bar.high = float(bar[2])
|
||||
add_bar.low = float(bar[3])
|
||||
add_bar.close = float(bar[4])
|
||||
add_bar.volume = float(bar[5])
|
||||
except Exception as ex:
|
||||
self.strategy.writeCtaError('error when convert bar:{},ex:{},t:{}'.format(bar, str(ex), traceback.format_exc()))
|
||||
return False
|
||||
|
||||
if start_dt is not None and bar.datetime < start_dt:
|
||||
continue
|
||||
|
||||
if callback is not None:
|
||||
callback(add_bar, bar_is_completed, bar_freq)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
'''
|
||||
本文件中包含的是CTA模块的回测引擎,回测引擎的API和CTA引擎一致,
|
||||
可以使用和实盘相同的代码进行回测。
|
||||
|
||||
修改者: 华富资产/李来佳/28888502
|
||||
'''
|
||||
from __future__ import division
|
||||
|
||||
@ -32,7 +34,6 @@ from vnpy.trader.vtFunction import loadMongoSetting
|
||||
from vnpy.trader.vtEvent import *
|
||||
from vnpy.trader.setup_logger import setup_logger
|
||||
|
||||
|
||||
########################################################################
|
||||
class BacktestingEngine(object):
|
||||
"""
|
||||
@ -125,12 +126,13 @@ class BacktestingEngine(object):
|
||||
self.last_leg1_tick = None
|
||||
self.last_leg2_tick = None
|
||||
self.last_bar = None
|
||||
self.is_7x24 = False
|
||||
|
||||
# csvFile相关
|
||||
self.barTimeInterval = 60 # csv文件,属于K线类型,K线的周期(秒数),缺省是1分钟
|
||||
|
||||
# 费用情况
|
||||
self.avaliable = EMPTY_FLOAT
|
||||
|
||||
self.percent = EMPTY_FLOAT
|
||||
self.percentLimit = 30 # 投资仓位比例上限
|
||||
|
||||
@ -143,6 +145,7 @@ class BacktestingEngine(object):
|
||||
self.netCapital = self.initCapital # 实时资金净值(每日根据capital和持仓浮盈计算)
|
||||
self.maxCapital = self.initCapital # 资金最高净值
|
||||
self.maxNetCapital = self.initCapital
|
||||
self.avaliable = self.initCapital
|
||||
|
||||
self.maxPnl = 0 # 最高盈利
|
||||
self.minPnl = 0 # 最大亏损
|
||||
@ -168,8 +171,10 @@ class BacktestingEngine(object):
|
||||
self.daily_max_drawdown_rate = 0 # 按照日结算价计算
|
||||
|
||||
self.dailyList = []
|
||||
self.exportTradeList = [] # 导出交易记录列表
|
||||
self.daily_first_benchmark = None
|
||||
|
||||
self.exportTradeList = [] # 导出交易记录列表
|
||||
self.export_wenhua_signal = False
|
||||
self.fixCommission = EMPTY_FLOAT # 固定交易费用
|
||||
|
||||
self.logger = None
|
||||
@ -1569,10 +1574,6 @@ class BacktestingEngine(object):
|
||||
self.strategy.onInit()
|
||||
self.output(u'策略初始化完成')
|
||||
|
||||
self.strategy.trading = True
|
||||
self.strategy.onStart()
|
||||
self.output(u'策略启动完成')
|
||||
|
||||
self.output(u'开始回放数据')
|
||||
|
||||
import csv
|
||||
@ -1609,14 +1610,14 @@ class BacktestingEngine(object):
|
||||
if 'trading_date' in row:
|
||||
bar.tradingDay = row['trading_date']
|
||||
else:
|
||||
if bar.datetime.hour >=21:
|
||||
if bar.datetime.hour >=21 and not self.is_7x24:
|
||||
if bar.datetime.isoweekday() == 5:
|
||||
# 星期五=》星期一
|
||||
bar.tradingDay = (barEndTime + timedelta(days=3)).strftime('%Y-%m-%d')
|
||||
else:
|
||||
# 第二天
|
||||
bar.tradingDay = (barEndTime + timedelta(days=1)).strftime('%Y-%m-%d')
|
||||
elif bar.datetime.hour < 8 and bar.datetime.isoweekday() == 6:
|
||||
elif bar.datetime.hour < 8 and bar.datetime.isoweekday() == 6 and not self.is_7x24:
|
||||
# 星期六=>星期一
|
||||
bar.tradingDay = (barEndTime + timedelta(days=2)).strftime('%Y-%m-%d')
|
||||
else:
|
||||
@ -1626,11 +1627,16 @@ class BacktestingEngine(object):
|
||||
if last_tradingDay != bar.tradingDay:
|
||||
if last_tradingDay is not None:
|
||||
self.savingDailyData(datetime.strptime(last_tradingDay, '%Y-%m-%d'), self.capital,
|
||||
self.maxCapital,self.totalCommission)
|
||||
self.maxCapital,self.totalCommission,benchmark=bar.close)
|
||||
last_tradingDay = bar.tradingDay
|
||||
|
||||
self.newBar(bar)
|
||||
|
||||
if not self.strategy.trading and self.strategyStartDate < bar.datetime:
|
||||
self.strategy.trading = True
|
||||
self.strategy.onStart()
|
||||
self.output(u'策略启动完成')
|
||||
|
||||
if self.netCapital < 0:
|
||||
self.writeCtaError(u'净值低于0,回测停止')
|
||||
return
|
||||
@ -1844,7 +1850,7 @@ class BacktestingEngine(object):
|
||||
self.strategy.name = self.strategy.className
|
||||
|
||||
self.strategy.onInit()
|
||||
self.strategy.onStart()
|
||||
#self.strategy.onStart()
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
def saveStrategyData(self):
|
||||
@ -2167,7 +2173,7 @@ class BacktestingEngine(object):
|
||||
self.logger = setup_logger(filename=filename, name=self.strategy_name if len(self.strategy_name) > 0 else 'strategy', debug=debug,backtesing=True)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def writeCtaLog(self, content):
|
||||
def writeCtaLog(self, content,strategy_name=None):
|
||||
"""记录日志"""
|
||||
#log = str(self.dt) + ' ' + content
|
||||
#self.logList.append(log)
|
||||
@ -2178,17 +2184,17 @@ class BacktestingEngine(object):
|
||||
else:
|
||||
self.createLogger()
|
||||
|
||||
def writeCtaError(self, content):
|
||||
def writeCtaError(self, content,strategy_name=None):
|
||||
"""记录异常"""
|
||||
self.output(u'Error:{}'.format(content))
|
||||
self.writeCtaLog(content)
|
||||
|
||||
def writeCtaWarning(self, content):
|
||||
def writeCtaWarning(self, content,strategy_name=None):
|
||||
"""记录告警"""
|
||||
self.output(u'Warning:{}'.format(content))
|
||||
self.writeCtaLog(content)
|
||||
|
||||
def writeCtaNotification(self,content):
|
||||
def writeCtaNotification(self,content,strategy_name=None):
|
||||
"""记录通知"""
|
||||
#print content
|
||||
self.output(u'Notify:{}'.format(content))
|
||||
@ -2613,7 +2619,7 @@ class BacktestingEngine(object):
|
||||
self.avaliable = self.netCapital - occupyMoney
|
||||
self.percent = round(float(occupyMoney * 100 / self.netCapital), 2)
|
||||
|
||||
def savingDailyData(self, d, c, m, commission):
|
||||
def savingDailyData(self, d, c, m, commission, benchmark=0):
|
||||
"""保存每日数据"""
|
||||
dict = {}
|
||||
dict['date'] = d.strftime('%Y/%m/%d')
|
||||
@ -2624,6 +2630,14 @@ class BacktestingEngine(object):
|
||||
long_pos_occupy_money = 0
|
||||
short_pos_occupy_money = 0
|
||||
|
||||
if self.daily_first_benchmark is None and benchmark >0:
|
||||
self.daily_first_benchmark = benchmark
|
||||
|
||||
if benchmark > 0 and self.daily_first_benchmark is not None and self.daily_first_benchmark > 0:
|
||||
benchmark = benchmark / self.daily_first_benchmark
|
||||
else:
|
||||
benchmark = 1
|
||||
|
||||
for longpos in self.longPosition:
|
||||
symbol = '-' if longpos.vtSymbol == EMPTY_STRING else longpos.vtSymbol
|
||||
# 计算持仓浮盈浮亏/占用保证金
|
||||
@ -2673,7 +2687,7 @@ class BacktestingEngine(object):
|
||||
dict['occupyMoney'] = max(long_pos_occupy_money, short_pos_occupy_money)
|
||||
dict['occupyRate'] = dict['occupyMoney'] / dict['capital']
|
||||
dict['commission'] = commission
|
||||
|
||||
dict['benchmark'] = benchmark
|
||||
self.dailyList.append(dict)
|
||||
|
||||
# 更新每日浮动净值
|
||||
@ -2688,6 +2702,45 @@ class BacktestingEngine(object):
|
||||
self.daily_max_drawdown_rate = drawdown_rate
|
||||
self.max_drowdown_rate_time = dict['date']
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def writeWenHuaSignal(self, filehandle, count, bardatetime, price, text):
|
||||
"""
|
||||
输出到文华信号
|
||||
:param filehandle:
|
||||
:param count:
|
||||
:param bardatetime:
|
||||
:param text:
|
||||
:return:
|
||||
"""
|
||||
# 文华信号
|
||||
barDate = bardatetime.strftime('%Y%m%d')
|
||||
barTime = bardatetime.strftime('%H%M')
|
||||
outputMsg = 'AA{}:=DATE={};\n'.format(count, barDate[2:])
|
||||
filehandle.write(outputMsg)
|
||||
outputMsg = 'BB{}:=PERIOD=1&&TIME={};\n'.format(count, barDate[2:], barTime)
|
||||
#filehandle.write(outputMsg)
|
||||
outputMsg = 'CC{}:=PERIOD=2&&TIME>={}&&TIME<={}+3;\n'.format(count, barTime, barTime)
|
||||
#filehandle.write(outputMsg)
|
||||
outputMsg = 'DD{}:=PERIOD=3&&TIME>={}&&TIME<={}+5;\n'.format(count, barTime, barTime)
|
||||
#filehandle.write(outputMsg)
|
||||
outputMsg = 'EE{}:=PERIOD=4&&TIME>={}&&TIME<={}+10;\n'.format(count, barTime, barTime)
|
||||
#filehandle.write(outputMsg)
|
||||
outputMsg = 'FF{}:=PERIOD=5&&TIME>={}&&TIME<={}+30;\n'.format(count, barTime, barTime)
|
||||
filehandle.write(outputMsg)
|
||||
outputMsg = 'GG{}:=PERIOD=6&&TIME>={}&&TIME<={}+60;\n'.format(count, barTime, barTime)
|
||||
filehandle.write(outputMsg)
|
||||
outputMsg = 'HH{}:=PERIOD=7&&TIME>={}&&TIME<={}+120;\n'.format(count, barTime, barTime)
|
||||
filehandle.write(outputMsg)
|
||||
outputMsg = 'II{}:=PERIOD=8;\n'.format(count)
|
||||
filehandle.write(outputMsg)
|
||||
outputMsg = 'DRAWICON(AA{} AND ( FF{} OR GG{} OR HH{} OR II{}), L, \'ICO14\');\n'.format(
|
||||
count, count, count, count, count)
|
||||
filehandle.write(outputMsg)
|
||||
outputMsg = 'DRAWTEXT(AA{} AND (FF{} OR GG{} OR HH{} OR II{}), {}, \'{}\');\n'.format(
|
||||
count, count, count, count, count, price, text)
|
||||
filehandle.write(outputMsg)
|
||||
filehandle.flush()
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def calculateBacktestingResult(self):
|
||||
"""
|
||||
@ -2947,6 +3000,68 @@ class BacktestingEngine(object):
|
||||
for row in self.exportTradeList:
|
||||
writer.writerow(row)
|
||||
|
||||
if self.export_wenhua_signal:
|
||||
wh_records = OrderedDict()
|
||||
for t in self.exportTradeList:
|
||||
if t['Direction'] is 'Long':
|
||||
k = '{}_{}_{}'.format(t['OpenTime'], 'Buy', t['OpenPrice'])
|
||||
# 生成文华用的指标信号
|
||||
v = {'time': datetime.strptime(t['OpenTime'], '%Y-%m-%d %H:%M:%S'), 'price':t['OpenPrice'], 'action': 'Buy', 'volume':t['Volume']}
|
||||
r = wh_records.get(k,None)
|
||||
if r is not None:
|
||||
r['volume'] += t['Volume']
|
||||
else:
|
||||
wh_records[k] = v
|
||||
|
||||
k = '{}_{}_{}'.format(t['CloseTime'], 'Sell', t['ClosePrice'])
|
||||
# 生成文华用的指标信号
|
||||
v = {'time': datetime.strptime(t['CloseTime'], '%Y-%m-%d %H:%M:%S'), 'price': t['ClosePrice'], 'action': 'Sell', 'volume': t['Volume']}
|
||||
r = wh_records.get(k, None)
|
||||
if r is not None:
|
||||
r['volume'] += t['Volume']
|
||||
else:
|
||||
wh_records[k] = v
|
||||
|
||||
else:
|
||||
k = '{}_{}_{}'.format(t['OpenTime'], 'Short', t['OpenPrice'])
|
||||
# 生成文华用的指标信号
|
||||
v = {'time': datetime.strptime(t['OpenTime'], '%Y-%m-%d %H:%M:%S'), 'price': t['OpenPrice'], 'action': 'Short', 'volume': t['Volume']}
|
||||
r = wh_records.get(k, None)
|
||||
if r is not None:
|
||||
r['volume'] += t['Volume']
|
||||
else:
|
||||
wh_records[k] = v
|
||||
k = '{}_{}_{}'.format(t['CloseTime'], 'Cover', t['ClosePrice'])
|
||||
# 生成文华用的指标信号
|
||||
v = {'time': datetime.strptime(t['CloseTime'], '%Y-%m-%d %H:%M:%S'), 'price': t['ClosePrice'], 'action': 'Cover', 'volume': t['Volume']}
|
||||
r = wh_records.get(k, None)
|
||||
if r is not None:
|
||||
r['volume'] += t['Volume']
|
||||
else:
|
||||
wh_records[k] = v
|
||||
|
||||
branchs = 0
|
||||
count = 0
|
||||
wh_signal_file = None
|
||||
for r in list(wh_records.values()):
|
||||
if count % 200 == 0:
|
||||
if wh_signal_file is not None:
|
||||
wh_signal_file.close()
|
||||
|
||||
# 交易记录生成文华对应的公式
|
||||
filename = os.path.abspath(os.path.join(self.get_logs_path(),
|
||||
'{}_WenHua_{}_{}.csv'.format(s, datetime.now().strftime('%Y%m%d_%H%M'), branchs)))
|
||||
branchs += 1
|
||||
self.writeCtaLog(u'save trade records for WenHua:{}'.format(filename))
|
||||
|
||||
wh_signal_file = open(filename, mode='w')
|
||||
|
||||
count += 1
|
||||
if wh_signal_file is not None:
|
||||
self.writeWenHuaSignal(filehandle=wh_signal_file, count=count, bardatetime=r['time'],price=r['price'], text='{}({})'.format(r['action'],r['volume']))
|
||||
if wh_signal_file is not None:
|
||||
wh_signal_file.close()
|
||||
|
||||
# 导出每日净值记录表
|
||||
if not self.dailyList:
|
||||
return
|
||||
@ -2959,7 +3074,7 @@ class BacktestingEngine(object):
|
||||
self.writeCtaLog(u'save daily records to:{}'.format(csvOutputFile2))
|
||||
|
||||
csvWriteFile2 = open(csvOutputFile2, 'w', encoding='utf8',newline='')
|
||||
fieldnames = ['date', 'capital','net', 'maxCapital','rate', 'commission', 'longMoney','shortMoney','occupyMoney','occupyRate','longPos','shortPos']
|
||||
fieldnames = ['date', 'capital','net', 'maxCapital','rate', 'commission', 'longMoney','shortMoney','occupyMoney','occupyRate','longPos','shortPos','benchmark']
|
||||
writer2 = csv.DictWriter(f=csvWriteFile2, fieldnames=fieldnames, dialect='excel')
|
||||
writer2.writeheader()
|
||||
|
||||
@ -3332,38 +3447,5 @@ def optimize(strategyClass, setting, targetName,
|
||||
return (str(setting), targetValue)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 以下内容是一段回测脚本的演示,用户可以根据自己的需求修改
|
||||
# 建议使用ipython notebook或者spyder来做回测
|
||||
# 同样可以在命令模式下进行回测(一行一行输入运行)
|
||||
from strategy.strategyEmaDemo import *
|
||||
|
||||
# 创建回测引擎
|
||||
engine = BacktestingEngine()
|
||||
|
||||
# 设置引擎的回测模式为K线
|
||||
engine.setBacktestingMode(engine.BAR_MODE)
|
||||
|
||||
# 设置回测用的数据起始日期
|
||||
engine.setStartDate('20110101')
|
||||
|
||||
# 载入历史数据到引擎中
|
||||
engine.setDatabase(MINUTE_DB_NAME, 'IF0000')
|
||||
|
||||
# 设置产品相关参数
|
||||
engine.setSlippage(0.2) # 股指1跳
|
||||
engine.setRate(0.3/10000) # 万0.3
|
||||
engine.setSize(300) # 股指合约大小
|
||||
|
||||
# 在引擎中创建策略对象
|
||||
engine.initStrategy(EmaDemoStrategy, {})
|
||||
|
||||
# 开始跑回测
|
||||
engine.runBacktesting()
|
||||
|
||||
# 显示回测结果
|
||||
# spyder或者ipython notebook中运行时,会弹出盈亏曲线图
|
||||
# 直接在cmd中回测则只会打印一些回测数值
|
||||
engine.showBacktestingResult()
|
||||
|
||||
|
@ -112,7 +112,7 @@ class CtaEngine(object):
|
||||
self.strategy_group = EMPTY_STRING
|
||||
|
||||
self.logger = None
|
||||
|
||||
self.strategy_loggers = {}
|
||||
self.createLogger()
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
@ -128,6 +128,7 @@ class CtaEngine(object):
|
||||
req = VtOrderReq()
|
||||
req.symbol = contract.symbol # 合约代码
|
||||
req.exchange = contract.exchange # 交易所
|
||||
req.vtSymbol = contract.vtSymbol
|
||||
req.price = self.roundToPriceTick(contract.priceTick, price) # 价格
|
||||
req.volume = volume # 数量
|
||||
|
||||
@ -410,9 +411,9 @@ class CtaEngine(object):
|
||||
for key in d.keys():
|
||||
d[key] = tick.__getattribute__(key)
|
||||
|
||||
if ctaTick.datetime:
|
||||
if not ctaTick.datetime:
|
||||
# 添加datetime字段
|
||||
ctaTick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y%m%d %H:%M:%S.%f')
|
||||
ctaTick.datetime = datetime.strptime(' '.join([tick.date, tick.time]), '%Y-%m-%d %H:%M:%S.%f')
|
||||
|
||||
# 逐个推送到策略实例中
|
||||
l = self.tickStrategyDict[tick.vtSymbol]
|
||||
@ -471,7 +472,7 @@ class CtaEngine(object):
|
||||
self.posBufferDict[trade.vtSymbol] = posBuffer
|
||||
posBuffer.updateTradeData(trade)
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
def processPositionEvent(self, event):
|
||||
"""处理持仓推送"""
|
||||
@ -615,7 +616,7 @@ class CtaEngine(object):
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# 日志相关
|
||||
def writeCtaLog(self, content):
|
||||
def writeCtaLog(self, content, strategy_name=None):
|
||||
"""快速发出CTA模块日志事件"""
|
||||
log = VtLogData()
|
||||
log.logContent = content
|
||||
@ -623,13 +624,19 @@ class CtaEngine(object):
|
||||
event.dict_['data'] = log
|
||||
self.eventEngine.put(event)
|
||||
|
||||
# 写入本地log日志
|
||||
if self.logger:
|
||||
self.logger.info(content)
|
||||
if strategy_name is None:
|
||||
# 写入本地log日志
|
||||
if self.logger:
|
||||
self.logger.info(content)
|
||||
else:
|
||||
self.createLogger()
|
||||
else:
|
||||
self.createLogger()
|
||||
if strategy_name in self.strategy_loggers:
|
||||
self.strategy_loggers[strategy_name].info(content)
|
||||
else:
|
||||
self.createLogger(strategy_name=strategy_name)
|
||||
|
||||
def createLogger(self):
|
||||
def createLogger(self, strategy_name=None):
|
||||
"""
|
||||
创建日志记录
|
||||
:return:
|
||||
@ -642,25 +649,58 @@ class CtaEngine(object):
|
||||
# 否则,使用缺省保存目录 vnpy/trader/app/ctaStrategy/data
|
||||
path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'logs'))
|
||||
|
||||
filename = os.path.abspath(os.path.join(path, 'ctaEngine'))
|
||||
if strategy_name is None:
|
||||
filename = os.path.abspath(os.path.join(path, 'ctaEngine'))
|
||||
|
||||
print(u'create logger:{}'.format(filename))
|
||||
self.logger = setup_logger(filename=filename, name='ctaEngine', debug=True)
|
||||
print(u'create logger:{}'.format(filename))
|
||||
self.logger = setup_logger(filename=filename, name='ctaEngine', debug=True)
|
||||
else:
|
||||
filename = os.path.abspath(os.path.join(path, str(strategy_name)))
|
||||
print(u'create logger:{}'.format(filename))
|
||||
self.strategy_loggers[strategy_name] = setup_logger(filename=filename, name=str(strategy_name), debug=True)
|
||||
|
||||
def writeCtaError(self, content):
|
||||
def writeCtaError(self, content, strategy_name=None):
|
||||
"""快速发出CTA模块错误日志事件"""
|
||||
if strategy_name is not None:
|
||||
if strategy_name in self.strategy_loggers:
|
||||
self.strategy_loggers[strategy_name].error(content)
|
||||
else:
|
||||
self.createLogger(strategy_name=strategy_name)
|
||||
try:
|
||||
self.strategy_loggers[strategy_name].error(content)
|
||||
except Exception as ex:
|
||||
pass
|
||||
|
||||
self.mainEngine.writeError(content)
|
||||
|
||||
def writeCtaWarning(self, content):
|
||||
def writeCtaWarning(self, content, strategy_name=None):
|
||||
"""快速发出CTA模块告警日志事件"""
|
||||
if strategy_name is not None:
|
||||
if strategy_name in self.strategy_loggers:
|
||||
self.strategy_loggers[strategy_name].warning(content)
|
||||
else:
|
||||
self.createLogger(strategy_name=strategy_name)
|
||||
try:
|
||||
self.strategy_loggers[strategy_name].warning(content)
|
||||
except Exception as ex:
|
||||
pass
|
||||
self.mainEngine.writeWarning(content)
|
||||
|
||||
def writeCtaNotification(self, content):
|
||||
def writeCtaNotification(self, content, strategy_name=None):
|
||||
"""快速发出CTA模块通知事件"""
|
||||
self.mainEngine.writeNotification(content)
|
||||
|
||||
def writeCtaCritical(self, content):
|
||||
def writeCtaCritical(self, content, strategy_name=None):
|
||||
"""快速发出CTA模块异常日志事件"""
|
||||
if strategy_name is not None:
|
||||
if strategy_name in self.strategy_loggers:
|
||||
self.strategy_loggers[strategy_name].critical(content)
|
||||
else:
|
||||
self.createLogger(strategy_name=strategy_name)
|
||||
try:
|
||||
self.strategy_loggers[strategy_name].critical(content)
|
||||
except Exception as ex:
|
||||
pass
|
||||
self.mainEngine.writeCritical(content)
|
||||
|
||||
def sendCtaSignal(self, source, symbol, direction, price, level):
|
||||
@ -830,7 +870,7 @@ class CtaEngine(object):
|
||||
symbols.append(strategy.Leg2Symbol)
|
||||
|
||||
for symbol in symbols:
|
||||
self.writeCtaLog(u'添加合约{0}与策略的匹配目录'.format(symbol))
|
||||
self.writeCtaLog(u'添加合约{}与策略{}的匹配目录'.format(symbol,strategy.name))
|
||||
if symbol in self.tickStrategyDict:
|
||||
l = self.tickStrategyDict[symbol]
|
||||
else:
|
||||
@ -840,6 +880,7 @@ class CtaEngine(object):
|
||||
|
||||
# 3.订阅合约
|
||||
self.writeCtaLog(u'向gateway订阅合约{0}'.format(symbol))
|
||||
self.pendingSubcribeSymbols[symbol] = strategy
|
||||
self.subscribe(strategy=strategy, symbol=symbol)
|
||||
|
||||
# 自动初始化
|
||||
@ -1950,23 +1991,43 @@ class PositionBuffer(object):
|
||||
self.shortPosition = EMPTY_INT
|
||||
self.shortToday = EMPTY_INT
|
||||
self.shortYd = EMPTY_INT
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def updatePositionData(self, pos):
|
||||
"""更新持仓数据"""
|
||||
if pos.direction == DIRECTION_LONG:
|
||||
self.longPosition = pos.position # >=0
|
||||
self.longYd = pos.ydPosition # >=0
|
||||
self.longToday = self.longPosition - self.longYd # >=0
|
||||
if pos.direction == DIRECTION_SHORT:
|
||||
self.shortPosition = pos.position # >=0
|
||||
self.shortYd = pos.ydPosition # >=0
|
||||
self.shortToday = self.shortPosition - self.shortYd # >=0
|
||||
else:
|
||||
self.shortPosition = pos.position # >=0
|
||||
self.shortYd = pos.ydPosition # >=0
|
||||
self.shortToday = self.shortPosition - self.shortYd # >=0
|
||||
|
||||
self.longPosition = pos.position # >=0
|
||||
self.longYd = pos.ydPosition # >=0
|
||||
self.longToday = self.longPosition - self.longYd # >=0
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def updateTradeData(self, trade):
|
||||
"""更新成交数据"""
|
||||
if trade.direction == DIRECTION_LONG:
|
||||
|
||||
if trade.direction == DIRECTION_SHORT:
|
||||
# 空头和多头相同
|
||||
if trade.offset == OFFSET_OPEN:
|
||||
self.shortPosition += trade.volume
|
||||
self.shortToday += trade.volume
|
||||
elif trade.offset == OFFSET_CLOSETODAY:
|
||||
self.longPosition -= trade.volume
|
||||
self.longToday -= trade.volume
|
||||
else:
|
||||
self.longPosition -= trade.volume
|
||||
self.longYd -= trade.volume
|
||||
|
||||
if self.longPosition <= 0:
|
||||
self.longPosition = 0
|
||||
if self.longToday <= 0:
|
||||
self.longToday = 0
|
||||
if self.longYd <= 0:
|
||||
self.longYd = 0
|
||||
else:
|
||||
# 多方开仓,则对应多头的持仓和今仓增加
|
||||
if trade.offset == OFFSET_OPEN:
|
||||
self.longPosition += trade.volume
|
||||
@ -1985,22 +2046,4 @@ class PositionBuffer(object):
|
||||
self.shortToday = 0
|
||||
if self.shortYd <= 0:
|
||||
self.shortYd = 0
|
||||
# 多方平昨,对应空头的持仓和昨仓减少
|
||||
else:
|
||||
# 空头和多头相同
|
||||
if trade.offset == OFFSET_OPEN:
|
||||
self.shortPosition += trade.volume
|
||||
self.shortToday += trade.volume
|
||||
elif trade.offset == OFFSET_CLOSETODAY:
|
||||
self.longPosition -= trade.volume
|
||||
self.longToday -= trade.volume
|
||||
else:
|
||||
self.longPosition -= trade.volume
|
||||
self.longYd -= trade.volume
|
||||
|
||||
if self.longPosition <= 0:
|
||||
self.longPosition = 0
|
||||
if self.longToday <= 0:
|
||||
self.longToday = 0
|
||||
if self.longYd <= 0:
|
||||
self.longYd = 0
|
||||
# 多方平昨,对应空头的持仓和昨仓减少
|
@ -103,6 +103,8 @@ class CtaLineBar(object):
|
||||
self.round_n = 4 # round() 小数点的截断数量
|
||||
self.activeDayJump = False # 隔夜跳空
|
||||
|
||||
self.is_7x24 = False
|
||||
|
||||
# 当前的Tick
|
||||
self.curTick = None
|
||||
self.lastTick = None
|
||||
@ -173,6 +175,7 @@ class CtaLineBar(object):
|
||||
self.paramList.append('inputYb')
|
||||
self.paramList.append('inputYbLen')
|
||||
self.paramList.append('inputYbRef')
|
||||
self.paramList.append('is_7x24')
|
||||
|
||||
self.paramList.append('minDiff')
|
||||
self.paramList.append('shortSymbol')
|
||||
@ -389,14 +392,14 @@ class CtaLineBar(object):
|
||||
# self.writeCtaLog(u'无效的tick时间:{0}'.format(tick.datetime))
|
||||
# return
|
||||
|
||||
if tick.datetime.hour == 8 or tick.datetime.hour == 20:
|
||||
if not self.is_7x24 and (tick.datetime.hour == 8 or tick.datetime.hour == 20 ):
|
||||
self.writeCtaLog(u'竞价排名tick时间:{0}'.format(tick.datetime))
|
||||
return
|
||||
|
||||
self.curTick = tick
|
||||
|
||||
# 3.生成x K线,若形成新Bar,则触发OnBar事件
|
||||
self.__drawLineBar(tick)
|
||||
self.drawLineBar(tick)
|
||||
|
||||
# 更新curPeriod的High,low
|
||||
if self.curPeriod is not None:
|
||||
@ -613,7 +616,7 @@ class CtaLineBar(object):
|
||||
|
||||
return msg
|
||||
|
||||
def __firstTick(self, tick):
|
||||
def firstTick(self, tick):
|
||||
""" K线的第一个Tick数据"""
|
||||
|
||||
self.bar = CtaBarData() # 创建新的K线
|
||||
@ -661,14 +664,14 @@ class CtaLineBar(object):
|
||||
self.lineBar.append(self.bar) # 推入到lineBar队列
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def __drawLineBar(self, tick):
|
||||
def drawLineBar(self, tick):
|
||||
"""生成 line Bar """
|
||||
|
||||
l1 = len(self.lineBar)
|
||||
|
||||
# 保存第一个K线数据
|
||||
if l1 == 0:
|
||||
self.__firstTick(tick)
|
||||
self.firstTick(tick)
|
||||
return
|
||||
|
||||
# 清除480周期前的数据,
|
||||
@ -680,24 +683,25 @@ class CtaLineBar(object):
|
||||
|
||||
# 处理日内的间隔时段最后一个tick,如10:15分,11:30分,15:00 和 2:30分
|
||||
endtick = False
|
||||
if (tick.datetime.hour == 10 and tick.datetime.minute == 15) \
|
||||
or (tick.datetime.hour == 11 and tick.datetime.minute == 30) \
|
||||
or (tick.datetime.hour == 15 and tick.datetime.minute == 00) \
|
||||
or (tick.datetime.hour == 2 and tick.datetime.minute == 30):
|
||||
endtick = True
|
||||
|
||||
# 夜盘1:30收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_SQ2 and tick.datetime.hour == 1 and tick.datetime.minute == 00:
|
||||
endtick = True
|
||||
|
||||
# 夜盘23:00收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_SQ3 and tick.datetime.hour == 23 and tick.datetime.minute == 00:
|
||||
endtick = True
|
||||
# 夜盘23:30收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_ZZ or self.shortSymbol in NIGHT_MARKET_DL:
|
||||
if tick.datetime.hour == 23 and tick.datetime.minute == 30:
|
||||
if not self.is_7x24:
|
||||
if (tick.datetime.hour == 10 and tick.datetime.minute == 15) \
|
||||
or (tick.datetime.hour == 11 and tick.datetime.minute == 30) \
|
||||
or (tick.datetime.hour == 15 and tick.datetime.minute == 00) \
|
||||
or (tick.datetime.hour == 2 and tick.datetime.minute == 30):
|
||||
endtick = True
|
||||
|
||||
# 夜盘1:30收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_SQ2 and tick.datetime.hour == 1 and tick.datetime.minute == 00:
|
||||
endtick = True
|
||||
|
||||
# 夜盘23:00收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_SQ3 and tick.datetime.hour == 23 and tick.datetime.minute == 00:
|
||||
endtick = True
|
||||
# 夜盘23:30收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_ZZ or self.shortSymbol in NIGHT_MARKET_DL:
|
||||
if tick.datetime.hour == 23 and tick.datetime.minute == 30:
|
||||
endtick = True
|
||||
|
||||
# 满足时间要求
|
||||
# 1,秒周期,tick的时间,距离最后一个bar的开始时间,已经超出bar的时间周期(barTimeInterval)
|
||||
# 2,分钟、小时周期,取整=0
|
||||
@ -730,7 +734,7 @@ class CtaLineBar(object):
|
||||
) and not endtick:
|
||||
|
||||
# 创建并推入新的Bar
|
||||
self.__firstTick(tick)
|
||||
self.firstTick(tick)
|
||||
# 触发OnBar事件
|
||||
self.onBar(lastBar)
|
||||
|
||||
@ -750,7 +754,7 @@ class CtaLineBar(object):
|
||||
lastBar.volume = tick.volume
|
||||
else:
|
||||
# 针对新交易日的第一个bar:于上一个bar的时间在14,当前bar的时间不在14点,初始化为tick.volume
|
||||
if self.lineBar[-2].datetime.hour == 14 and tick.datetime.hour != 14 and not endtick:
|
||||
if self.lineBar[-2].datetime.hour == 14 and tick.datetime.hour != 14 and not endtick and not self.is_7x24:
|
||||
lastBar.volume = tick.volume
|
||||
else:
|
||||
# 其他情况,bar为同一交易日,将tick的volume,减去上一bar的dayVolume
|
||||
@ -809,7 +813,7 @@ class CtaLineBar(object):
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def __recountSar(self):
|
||||
"""计算K线的MA1 和MA2"""
|
||||
"""计算K线的SAR"""
|
||||
l = len(self.lineBar)
|
||||
if l < 5:
|
||||
return
|
||||
@ -2716,7 +2720,7 @@ class CtaMinuteBar(CtaLineBar):
|
||||
self.m1_bars_count += bar_freq
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def __drawLineBar(self, tick):
|
||||
def drawLineBar(self, tick):
|
||||
"""
|
||||
生成 line Bar
|
||||
:param tick:
|
||||
@ -2727,7 +2731,7 @@ class CtaMinuteBar(CtaLineBar):
|
||||
|
||||
# 保存第一个K线数据
|
||||
if l1 == 0:
|
||||
self.__firstTick(tick)
|
||||
self.firstTick(tick)
|
||||
return
|
||||
|
||||
# 清除480周期前的数据,
|
||||
@ -2738,13 +2742,33 @@ class CtaMinuteBar(CtaLineBar):
|
||||
lastBar = self.lineBar[-1]
|
||||
is_new_bar = False
|
||||
|
||||
endtick = False
|
||||
if not self.is_7x24:
|
||||
# 处理日内的间隔时段最后一个tick,如10:15分,11:30分,15:00 和 2:30分
|
||||
if (tick.datetime.hour == 10 and tick.datetime.minute == 15) \
|
||||
or (tick.datetime.hour == 11 and tick.datetime.minute == 30) \
|
||||
or (tick.datetime.hour == 15 and tick.datetime.minute == 00) \
|
||||
or (tick.datetime.hour == 2 and tick.datetime.minute == 30):
|
||||
endtick = True
|
||||
|
||||
# 夜盘1:30收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_SQ2 and tick.datetime.hour == 1 and tick.datetime.minute == 00:
|
||||
endtick = True
|
||||
|
||||
# 夜盘23:00收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_SQ3 and tick.datetime.hour == 23 and tick.datetime.minute == 00:
|
||||
endtick = True
|
||||
# 夜盘23:30收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_ZZ or self.shortSymbol in NIGHT_MARKET_DL:
|
||||
if tick.datetime.hour == 23 and tick.datetime.minute == 30:
|
||||
endtick = True
|
||||
# 不在同一交易日,推入新bar
|
||||
if self.curTradingDay != tick.tradingDay:
|
||||
is_new_bar = True
|
||||
self.curTradingDay = tick.tradingDay
|
||||
else:
|
||||
# 同一交易日,看分钟是否一致
|
||||
if tick.datetime.minute != self.last_minute:
|
||||
if tick.datetime.minute != self.last_minute and not endtick:
|
||||
self.m1_bars_count += 1
|
||||
self.last_minute = tick.datetime.minute
|
||||
|
||||
@ -2753,7 +2777,7 @@ class CtaMinuteBar(CtaLineBar):
|
||||
|
||||
if is_new_bar:
|
||||
# 创建并推入新的Bar
|
||||
self.__firstTick(tick)
|
||||
self.firstTick(tick)
|
||||
self.m1_bars_count = 1
|
||||
# 触发OnBar事件
|
||||
self.onBar(lastBar)
|
||||
@ -2913,7 +2937,7 @@ class CtaHourBar(CtaLineBar):
|
||||
self.__rt_countSkd()
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def __drawLineBar(self, tick):
|
||||
def drawLineBar(self, tick):
|
||||
"""
|
||||
生成 line Bar
|
||||
:param tick:
|
||||
@ -2924,33 +2948,34 @@ class CtaHourBar(CtaLineBar):
|
||||
|
||||
# 保存第一个K线数据
|
||||
if l1 == 0:
|
||||
self.__firstTick(tick)
|
||||
self.firstTick(tick)
|
||||
return
|
||||
|
||||
# 清除480周期前的数据,
|
||||
if l1 > 60 * 8:
|
||||
del self.lineBar[0]
|
||||
|
||||
# 处理日内的间隔时段最后一个tick,如10:15分,11:30分,15:00 和 2:30分
|
||||
endtick = False
|
||||
if (tick.datetime.hour == 10 and tick.datetime.minute == 15) \
|
||||
or (tick.datetime.hour == 11 and tick.datetime.minute == 30) \
|
||||
or (tick.datetime.hour == 15 and tick.datetime.minute == 00) \
|
||||
or (tick.datetime.hour == 2 and tick.datetime.minute == 30):
|
||||
endtick = True
|
||||
if not self.is_7x24:
|
||||
# 处理日内的间隔时段最后一个tick,如10:15分,11:30分,15:00 和 2:30分
|
||||
|
||||
# 夜盘1:30收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_SQ2 and tick.datetime.hour == 1 and tick.datetime.minute == 00:
|
||||
endtick = True
|
||||
|
||||
# 夜盘23:00收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_SQ3 and tick.datetime.hour == 23 and tick.datetime.minute == 00:
|
||||
endtick = True
|
||||
# 夜盘23:30收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_ZZ or self.shortSymbol in NIGHT_MARKET_DL:
|
||||
if tick.datetime.hour == 23 and tick.datetime.minute == 30:
|
||||
if (tick.datetime.hour == 10 and tick.datetime.minute == 15) \
|
||||
or (tick.datetime.hour == 11 and tick.datetime.minute == 30) \
|
||||
or (tick.datetime.hour == 15 and tick.datetime.minute == 00) \
|
||||
or (tick.datetime.hour == 2 and tick.datetime.minute == 30):
|
||||
endtick = True
|
||||
|
||||
# 夜盘1:30收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_SQ2 and tick.datetime.hour == 1 and tick.datetime.minute == 00:
|
||||
endtick = True
|
||||
|
||||
# 夜盘23:00收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_SQ3 and tick.datetime.hour == 23 and tick.datetime.minute == 00:
|
||||
endtick = True
|
||||
# 夜盘23:30收盘
|
||||
if self.shortSymbol in NIGHT_MARKET_ZZ or self.shortSymbol in NIGHT_MARKET_DL:
|
||||
if tick.datetime.hour == 23 and tick.datetime.minute == 30:
|
||||
endtick = True
|
||||
|
||||
# 与最后一个BAR的时间比对,判断是否超过K线周期
|
||||
lastBar = self.lineBar[-1]
|
||||
is_new_bar = False
|
||||
@ -2970,7 +2995,7 @@ class CtaHourBar(CtaLineBar):
|
||||
|
||||
if is_new_bar:
|
||||
# 创建并推入新的Bar
|
||||
self.__firstTick(tick)
|
||||
self.firstTick(tick)
|
||||
self.m1_bars_count = 1
|
||||
# 触发OnBar事件
|
||||
self.onBar(lastBar)
|
||||
@ -3148,7 +3173,7 @@ class CtaDayBar(CtaLineBar):
|
||||
self.__rt_countSkd()
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def __drawLineBar(self, tick):
|
||||
def drawLineBar(self, tick):
|
||||
"""
|
||||
生成 line Bar
|
||||
:param tick:
|
||||
@ -3159,7 +3184,7 @@ class CtaDayBar(CtaLineBar):
|
||||
|
||||
# 保存第一个K线数据
|
||||
if l1 == 0:
|
||||
self.__firstTick(tick)
|
||||
self.firstTick(tick)
|
||||
return
|
||||
|
||||
# 清除480周期前的数据,
|
||||
@ -3176,7 +3201,7 @@ class CtaDayBar(CtaLineBar):
|
||||
|
||||
if is_new_bar:
|
||||
# 创建并推入新的Bar
|
||||
self.__firstTick(tick)
|
||||
self.firstTick(tick)
|
||||
# 触发OnBar事件
|
||||
self.onBar(lastBar)
|
||||
|
||||
@ -3217,6 +3242,7 @@ class CtaDayBar(CtaLineBar):
|
||||
if self.inputSkd:
|
||||
self.skd_is_high_dead_cross(runtime=True, high_skd=0)
|
||||
self.skd_is_low_golden_cross(runtime=True,low_skd=100)
|
||||
|
||||
class TestStrategy(object):
|
||||
|
||||
def __init__(self):
|
||||
@ -3224,9 +3250,53 @@ class TestStrategy(object):
|
||||
self.minDiff = 1
|
||||
self.shortSymbol = 'I'
|
||||
self.vtSymbol = 'I99'
|
||||
|
||||
self.lineM30 = None
|
||||
self.lineH1 = None
|
||||
self.lineH2 = None
|
||||
self.lineD = None
|
||||
|
||||
self.TMinuteInterval = 1
|
||||
|
||||
self.save_m30_bars = []
|
||||
self.save_h1_bars = []
|
||||
self.save_h2_bars = []
|
||||
self.save_d_bars = []
|
||||
|
||||
def createLineM30(self):
|
||||
# 创建M30 K线
|
||||
lineM30Setting = {}
|
||||
lineM30Setting['name'] = u'M30'
|
||||
lineM30Setting['period'] = PERIOD_MINUTE
|
||||
lineM30Setting['barTimeInterval'] = 30
|
||||
lineM30Setting['inputPreLen'] = 5
|
||||
lineM30Setting['inputMa1Len'] = 5
|
||||
lineM30Setting['inputMa2Len'] = 10
|
||||
lineM30Setting['inputMa3Len'] = 18
|
||||
lineM30Setting['inputYb'] = True
|
||||
lineM30Setting['inputSkd'] = True
|
||||
lineM30Setting['mode'] = CtaLineBar.TICK_MODE
|
||||
lineM30Setting['minDiff'] = self.minDiff
|
||||
lineM30Setting['shortSymbol'] = self.shortSymbol
|
||||
self.lineM30 = CtaLineBar(self, self.onBarM30, lineM30Setting)
|
||||
|
||||
def createLineH1(self):
|
||||
# 创建2小时K线
|
||||
lineH2Setting = {}
|
||||
lineH2Setting['name'] = u'H1'
|
||||
lineH2Setting['period'] = PERIOD_HOUR
|
||||
lineH2Setting['barTimeInterval'] = 1
|
||||
lineH2Setting['inputPreLen'] = 5
|
||||
lineH2Setting['inputMa1Len'] = 5
|
||||
lineH2Setting['inputMa2Len'] = 10
|
||||
lineH2Setting['inputMa3Len'] = 18
|
||||
lineH2Setting['inputYb'] = True
|
||||
lineH2Setting['inputSkd'] = True
|
||||
lineH2Setting['mode'] = CtaLineBar.TICK_MODE
|
||||
lineH2Setting['minDiff'] = self.minDiff
|
||||
lineH2Setting['shortSymbol'] = self.shortSymbol
|
||||
self.lineH1 = CtaHourBar(self, self.onBarH1, lineH2Setting)
|
||||
|
||||
def createLineH2(self):
|
||||
# 创建2小时K线
|
||||
lineH2Setting = {}
|
||||
@ -3234,10 +3304,9 @@ class TestStrategy(object):
|
||||
lineH2Setting['period'] = PERIOD_HOUR
|
||||
lineH2Setting['barTimeInterval'] = 2
|
||||
lineH2Setting['inputPreLen'] = 5
|
||||
lineH2Setting['inputMa1Len'] = 10
|
||||
lineH2Setting['inputRsi1Len'] = 7
|
||||
lineH2Setting['inputRsi2Len'] = 14
|
||||
|
||||
lineH2Setting['inputMa1Len'] = 5
|
||||
lineH2Setting['inputMa2Len'] = 10
|
||||
lineH2Setting['inputMa3Len'] = 18
|
||||
lineH2Setting['inputYb'] = True
|
||||
lineH2Setting['inputSkd'] = True
|
||||
lineH2Setting['mode'] = CtaLineBar.TICK_MODE
|
||||
@ -3256,6 +3325,7 @@ class TestStrategy(object):
|
||||
lineDaySetting['inputMa2Len'] = 10
|
||||
lineDaySetting['inputMa3Len'] = 18
|
||||
lineDaySetting['inputYb'] = True
|
||||
lineDaySetting['inputSkd'] = True
|
||||
lineDaySetting['mode'] = CtaDayBar.TICK_MODE
|
||||
lineDaySetting['minDiff'] = self.minDiff
|
||||
lineDaySetting['shortSymbol'] = self.shortSymbol
|
||||
@ -3264,31 +3334,152 @@ class TestStrategy(object):
|
||||
def onBar(self, bar):
|
||||
#print(u'tradingDay:{},dt:{},o:{},h:{},l:{},c:{},v:{}'.format(bar.tradingDay,bar.datetime, bar.open, bar.high, bar.low, bar.close, bar.volume))
|
||||
if self.lineD:
|
||||
self.lineD.addBar(bar)
|
||||
self.lineD.addBar(bar, bar_freq=self.TMinuteInterval)
|
||||
if self.lineH2:
|
||||
self.lineH2.addBar(bar)
|
||||
self.lineH2.addBar(bar, bar_freq=self.TMinuteInterval)
|
||||
|
||||
if self.lineH1:
|
||||
self.lineH1.addBar(bar, bar_freq=self.TMinuteInterval)
|
||||
|
||||
if self.lineM30:
|
||||
self.lineM30.addBar(bar, bar_freq=self.TMinuteInterval)
|
||||
|
||||
def onBarM30(self, bar):
|
||||
self.writeCtaLog(self.lineM30.displayLastBar())
|
||||
|
||||
self.save_m30_bars.append({
|
||||
'datetime': bar.datetime,
|
||||
'open': bar.open,
|
||||
'high': bar.high,
|
||||
'low': bar.low,
|
||||
'close': bar.close,
|
||||
'turnover':0,
|
||||
'volume': bar.volume,
|
||||
'openInterest': 0,
|
||||
'ma5': self.lineM30.lineMa1[-1] if len(self.lineM30.lineMa1) > 0 else bar.close,
|
||||
'ma10': self.lineM30.lineMa2[-1] if len(self.lineM30.lineMa2) > 0 else bar.close,
|
||||
'ma18': self.lineM30.lineMa3[-1] if len(self.lineM30.lineMa3) > 0 else bar.close,
|
||||
'sk': self.lineM30.lineSK[-1] if len(self.lineM30.lineSK) > 0 else 0,
|
||||
'sd': self.lineM30.lineSD[-1] if len(self.lineM30.lineSD) > 0 else 0
|
||||
})
|
||||
|
||||
def onBarH1(self, bar):
|
||||
self.writeCtaLog(self.lineH1.displayLastBar())
|
||||
|
||||
self.save_h1_bars.append({
|
||||
'datetime': bar.datetime,
|
||||
'open': bar.open,
|
||||
'high': bar.high,
|
||||
'low': bar.low,
|
||||
'close': bar.close,
|
||||
'turnover':0,
|
||||
'volume': bar.volume,
|
||||
'openInterest': 0,
|
||||
'ma5': self.lineH1.lineMa1[-1] if len(self.lineH1.lineMa1) > 0 else bar.close,
|
||||
'ma10': self.lineH1.lineMa2[-1] if len(self.lineH1.lineMa2) > 0 else bar.close,
|
||||
'ma18': self.lineH1.lineMa3[-1] if len(self.lineH1.lineMa3) > 0 else bar.close,
|
||||
'sk': self.lineH1.lineSK[-1] if len(self.lineH1.lineSK) > 0 else 0,
|
||||
'sd': self.lineH1.lineSD[-1] if len(self.lineH1.lineSD) > 0 else 0
|
||||
})
|
||||
|
||||
def onBarH2(self, bar):
|
||||
self.writeCtaLog(self.lineH2.displayLastBar())
|
||||
|
||||
self.save_h2_bars.append({
|
||||
'datetime': bar.datetime,
|
||||
'open': bar.open,
|
||||
'high': bar.high,
|
||||
'low': bar.low,
|
||||
'close': bar.close,
|
||||
'turnover':0,
|
||||
'volume': bar.volume,
|
||||
'openInterest': 0,
|
||||
'ma5': self.lineH2.lineMa1[-1] if len(self.lineH2.lineMa1) > 0 else bar.close,
|
||||
'ma10': self.lineH2.lineMa2[-1] if len(self.lineH2.lineMa2) > 0 else bar.close,
|
||||
'ma18': self.lineH2.lineMa3[-1] if len(self.lineH2.lineMa3) > 0 else bar.close,
|
||||
'sk': self.lineH2.lineSK[-1] if len(self.lineH2.lineSK) > 0 else 0,
|
||||
'sd': self.lineH2.lineSD[-1] if len(self.lineH2.lineSD) > 0 else 0
|
||||
})
|
||||
|
||||
def onBarD(self, bar):
|
||||
self.writeCtaLog(self.lineD.displayLastBar())
|
||||
|
||||
self.save_d_bars.append({
|
||||
'datetime': bar.datetime,
|
||||
'open': bar.open,
|
||||
'high': bar.high,
|
||||
'low': bar.low,
|
||||
'close': bar.close,
|
||||
'turnover': 0,
|
||||
'volume': bar.volume,
|
||||
'openInterest': 0,
|
||||
'ma5': self.lineD.lineMa1[-1] if len(self.lineD.lineMa1) > 0 else bar.close,
|
||||
'ma10': self.lineD.lineMa2[-1] if len(self.lineD.lineMa2) > 0 else bar.close,
|
||||
'ma18': self.lineD.lineMa3[-1] if len(self.lineD.lineMa3) > 0 else bar.close,
|
||||
'sk': self.lineD.lineSK[-1] if len(self.lineD.lineSK) > 0 else 0,
|
||||
'sd': self.lineD.lineSD[-1] if len(self.lineD.lineSD) > 0 else 0
|
||||
})
|
||||
def onTick(self, tick):
|
||||
print(u'{0},{1},ap:{2},av:{3},bp:{4},bv:{5}'.format(tick.datetime, tick.lastPrice, tick.askPrice1, tick.askVolume1, tick.bidPrice1, tick.bidVolume1))
|
||||
|
||||
def writeCtaLog(self, content):
|
||||
print(content)
|
||||
|
||||
def saveData(self):
|
||||
|
||||
if len(self.save_m30_bars) > 0:
|
||||
outputFile = '{}_m30.csv'.format(self.vtSymbol)
|
||||
with open(outputFile, 'w', encoding='utf8', newline='') as f:
|
||||
fieldnames = ['datetime', 'open', 'price', 'high','low','close','turnover','volume','openInterest','ma5','ma10','ma18','sk','sd']
|
||||
writer = csv.DictWriter(f=f, fieldnames=fieldnames, dialect='excel')
|
||||
writer.writeheader()
|
||||
for row in self.save_m30_bars:
|
||||
writer.writerow(row)
|
||||
|
||||
if len(self.save_h1_bars) > 0:
|
||||
outputFile = '{}_h1.csv'.format(self.vtSymbol)
|
||||
with open(outputFile, 'w', encoding='utf8', newline='') as f:
|
||||
fieldnames = ['datetime', 'open', 'price', 'high','low','close','turnover','volume','openInterest','ma5','ma10','ma18','sk','sd']
|
||||
writer = csv.DictWriter(f=f, fieldnames=fieldnames, dialect='excel')
|
||||
writer.writeheader()
|
||||
for row in self.save_h1_bars:
|
||||
writer.writerow(row)
|
||||
|
||||
if len(self.save_h2_bars) > 0:
|
||||
outputFile = '{}_h2.csv'.format(self.vtSymbol)
|
||||
with open(outputFile, 'w', encoding='utf8', newline='') as f:
|
||||
fieldnames = ['datetime', 'open', 'price', 'high','low','close','turnover','volume','openInterest','ma5','ma10','ma18','sk','sd']
|
||||
writer = csv.DictWriter(f=f, fieldnames=fieldnames, dialect='excel')
|
||||
writer.writeheader()
|
||||
for row in self.save_h2_bars:
|
||||
writer.writerow(row)
|
||||
|
||||
if len(self.save_d_bars) > 0:
|
||||
outputFile = '{}_d.csv'.format(self.vtSymbol)
|
||||
with open(outputFile, 'w', encoding='utf8', newline='') as f:
|
||||
fieldnames = ['datetime', 'open', 'price', 'high','low','close','turnover','volume','openInterest','ma5','ma10','ma18','sk','sd']
|
||||
writer = csv.DictWriter(f=f, fieldnames=fieldnames, dialect='excel')
|
||||
writer.writeheader()
|
||||
for row in self.save_d_bars:
|
||||
writer.writerow(row)
|
||||
|
||||
if __name__ == '__main__':
|
||||
t = TestStrategy()
|
||||
t.minDiff = 1
|
||||
t.shortSymbol = 'J'
|
||||
t.vtSymbol = 'J99'
|
||||
# 创建M30线
|
||||
t.createLineM30()
|
||||
|
||||
# 回测1小时线
|
||||
t.createLineH1()
|
||||
|
||||
# 回测2小时线
|
||||
t.createLineH2()
|
||||
|
||||
# 回测日线
|
||||
#t.createLineD()
|
||||
t.createLineD()
|
||||
|
||||
filename = 'cache/I99_20141201_20171231_1m.csv'
|
||||
filename = 'cache/{}_20141201_20171231_1m.csv'.format(t.vtSymbol)
|
||||
|
||||
barTimeInterval = 60 # 60秒
|
||||
minDiff = 0.5 #回测数据的最小跳动
|
||||
@ -3350,4 +3541,6 @@ if __name__ == '__main__':
|
||||
except Exception as ex:
|
||||
t.writeCtaLog(u'{0}:{1}'.format(Exception, ex))
|
||||
traceback.print_exc()
|
||||
break
|
||||
break
|
||||
|
||||
t.saveData()
|
@ -1,13 +1,174 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from datetime import datetime
|
||||
from collections import OrderedDict
|
||||
|
||||
from vnpy.trader.app.ctaStrategy.ctaBase import *
|
||||
from vnpy.trader.vtConstant import *
|
||||
|
||||
|
||||
DEBUGCTALOG = True
|
||||
|
||||
class CtaPolicy(object):
|
||||
"""CTA的策略规则类
|
||||
"""
|
||||
策略的持久化Policy
|
||||
"""
|
||||
|
||||
def __init__(self, strategy=None):
|
||||
"""
|
||||
构造
|
||||
:param strategy:
|
||||
"""
|
||||
self.strategy = strategy
|
||||
|
||||
self.create_time = None
|
||||
self.save_time = None
|
||||
|
||||
def writeCtaLog(self, log):
|
||||
"""
|
||||
写入日志
|
||||
:param log:
|
||||
:return:
|
||||
"""
|
||||
if self.strategy:
|
||||
self.strategy.writeCtaLog(log)
|
||||
|
||||
def writeCtaError(self, log):
|
||||
"""
|
||||
写入错误日志
|
||||
:param log:
|
||||
:return:
|
||||
"""
|
||||
if self.strategy:
|
||||
self.strategy.writeCtaError(log)
|
||||
|
||||
def toJson(self):
|
||||
"""
|
||||
将数据转换成dict
|
||||
datetime =》 string
|
||||
object =》 string
|
||||
:return:
|
||||
"""
|
||||
j = OrderedDict()
|
||||
j['create_time'] = self.create_time.strftime('%Y-%m-%d %H:%M:%S') if self.create_time is not None else EMPTY_STRING
|
||||
j['save_time'] = self.save_time.strftime('%Y-%m-%d %H:%M:%S') if self.save_time is not None else EMPTY_STRING
|
||||
|
||||
return j
|
||||
|
||||
def fromJson(self, json_data):
|
||||
"""
|
||||
将数据从json_data中恢复
|
||||
:param json_data:
|
||||
:return:
|
||||
"""
|
||||
self.writeCtaLog(u'将数据从json_data中恢复')
|
||||
|
||||
if 'create_time' in json_data:
|
||||
try:
|
||||
self.create_time = datetime.strptime(json_data['create_time'], '%Y-%m-%d %H:%M:%S')
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释create_time异常:{}'.format(str(ex)))
|
||||
self.create_time = datetime.now()
|
||||
|
||||
if 'save_time' in json_data:
|
||||
try:
|
||||
self.save_time = datetime.strptime(json_data['save_time'], '%Y-%m-%d %H:%M:%S')
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释save_time异常:{}'.format(str(ex)))
|
||||
self.save_time = datetime.now()
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
从持久化文件中获取
|
||||
:return:
|
||||
"""
|
||||
json_file = os.path.abspath(os.path.join(self.get_data_folder(), u'{}_Policy.json'.format(self.strategy.name)))
|
||||
|
||||
json_data = {}
|
||||
if os.path.exists(json_file):
|
||||
try:
|
||||
with open(json_file, 'r', encoding='utf8') as f:
|
||||
# 解析json文件
|
||||
json_data = json.load(f)
|
||||
except IOError as ex:
|
||||
self.writeCtaError(u'读取Policy文件{}出错,ex:{}'.format(json_file,str(ex)))
|
||||
json_data = {}
|
||||
|
||||
# 从持久化文件恢复数据
|
||||
self.fromJson(json_data)
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
保存至持久化文件
|
||||
:return:
|
||||
"""
|
||||
json_file = os.path.abspath(
|
||||
os.path.join(self.get_data_folder(), u'{}_Policy.json'.format(self.strategy.name)))
|
||||
|
||||
try:
|
||||
json_data = self.toJson()
|
||||
|
||||
if self.strategy and self.strategy.backtesting:
|
||||
json_data['save_time'] = self.strategy.curDateTime.strftime('%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
json_data['save_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
with open(json_file, 'w') as f:
|
||||
data = json.dumps(json_data, indent=4)
|
||||
f.write(data)
|
||||
#self.writeCtaLog(u'写入Policy文件:{}成功'.format(json_file))
|
||||
except IOError as ex:
|
||||
self.writeCtaError(u'写入Policy文件{}出错,ex:{}'.format(json_file, str(ex)))
|
||||
|
||||
def export_history(self):
|
||||
"""
|
||||
导出历史
|
||||
:return:
|
||||
"""
|
||||
export_dir = os.path.abspath(os.path.join(
|
||||
self.get_data_folder(),
|
||||
'export_csv',
|
||||
'{}'.format(self.strategy.curDateTime.strftime('%Y%m%d'))))
|
||||
if not os.path.exists(export_dir):
|
||||
try:
|
||||
os.mkdir(export_dir)
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'创建Policy切片目录{}出错,ex:{}'.format(export_dir, str(ex)))
|
||||
return
|
||||
|
||||
json_file = os.path.abspath(os.path.join(
|
||||
export_dir,
|
||||
u'{}_Policy_{}.json'.format(self.strategy.name, self.strategy.curDateTime.strftime('%Y%m%d_%H%M'))))
|
||||
|
||||
try:
|
||||
json_data = self.toJson()
|
||||
|
||||
if self.strategy and self.strategy.backtesting:
|
||||
json_data['save_time'] = self.strategy.curDateTime.strftime('%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
json_data['save_time'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
with open(json_file, 'w') as f:
|
||||
data = json.dumps(json_data, indent=4)
|
||||
f.write(data)
|
||||
# self.writeCtaLog(u'写入Policy文件:{}成功'.format(json_file))
|
||||
except IOError as ex:
|
||||
self.writeCtaError(u'写入Policy文件{}出错,ex:{}'.format(json_file, str(ex)))
|
||||
|
||||
def get_data_folder(self):
|
||||
"""获取数据目录"""
|
||||
# 工作目录
|
||||
currentFolder = os.path.abspath(os.path.join(os.getcwd(), u'data'))
|
||||
if os.path.isdir(currentFolder):
|
||||
# 如果工作目录下,存在data子目录,就使用data子目录
|
||||
return currentFolder
|
||||
else:
|
||||
# 否则,使用缺省保存目录 vnpy/trader/app/ctaStrategy/data
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), u'data'))
|
||||
|
||||
class RenkoPolicy(CtaPolicy):
|
||||
"""Renko CTA的策略规则类
|
||||
包括:
|
||||
1、风险评估
|
||||
2、最低止损位置
|
||||
@ -19,10 +180,12 @@ class CtaPolicy(object):
|
||||
R2,Small Range/Renko
|
||||
"""
|
||||
|
||||
def __init__(self, r1Period=EMPTY_STRING, r2Period=EMPTY_STRING):
|
||||
def __init__(self, strategy):
|
||||
|
||||
self.openR1Period = r1Period # 开仓周期Mode
|
||||
self.openR2Period = r2Period # 开仓周期Mode
|
||||
super(RenkoPolicy, self).__init__(strategy)
|
||||
|
||||
self.openR1Period = None # 开仓周期Mode
|
||||
self.openR2Period = None # 开仓周期Mode
|
||||
|
||||
self.entryPrice = EMPTY_FLOAT # 开仓价格
|
||||
self.lastPeriodBasePrice = EMPTY_FLOAT # 上一个周期内的最近高价/低价(看R2 Rsi的高点和低点)
|
||||
@ -62,5 +225,514 @@ class CtaPolicy(object):
|
||||
|
||||
self.reducePosOnEmaRtn = False
|
||||
|
||||
def set_r1Period(self,r1Period):
|
||||
self.openR1Period = r1Period
|
||||
|
||||
def set_r2Period(self,r2Period):
|
||||
self.openR2Period = r2Period
|
||||
|
||||
class TurtlePolicy(CtaPolicy):
|
||||
"""海龟策略事务"""
|
||||
|
||||
def __init__(self, strategy):
|
||||
super(TurtlePolicy, self).__init__(strategy)
|
||||
|
||||
self.tns_open_price = 0 # 首次开仓价格
|
||||
self.last_open_price = 0 # 最后一次加仓价格
|
||||
self.stop_price = 0 # 止损价
|
||||
self.high_price_in_long = 0 # 多趋势时,最高价
|
||||
self.low_price_in_short = 0 # 空趋势时,最低价
|
||||
self.last_under_open_price = 0 # 低于首次开仓价的补仓价格
|
||||
self.add_pos_count_under_first_price = 0 # 低于首次开仓价的补仓次数
|
||||
self.tns_direction = None # 事务方向 DIRECTION_LONG 向上/ DIRECTION_SHORT向下
|
||||
self.tns_count = 0 # 当前事务开启后多少个bar,>0,做多方向,<0,做空方向
|
||||
self.max_pos = 0 # 事务中最大仓位
|
||||
self.tns_has_opened = False
|
||||
self.tns_open_date = '' # 事务开启的交易日
|
||||
self.last_risk_level = 0 # 上一次风控评估的级别
|
||||
|
||||
def toJson(self):
|
||||
"""
|
||||
将数据转换成dict
|
||||
:return:
|
||||
"""
|
||||
j = OrderedDict()
|
||||
j['create_time'] = self.create_time.strftime(
|
||||
'%Y-%m-%d %H:%M:%S') if self.create_time is not None else EMPTY_STRING
|
||||
j['save_time'] = self.save_time.strftime('%Y-%m-%d %H:%M:%S') if self.save_time is not None else EMPTY_STRING
|
||||
j['tns_open_date'] = self.tns_open_date
|
||||
j['tns_open_price'] = self.tns_open_price if self.tns_open_price is not None else 0
|
||||
|
||||
j['last_open_price'] = self.last_open_price if self.last_open_price is not None else 0
|
||||
j['stop_price'] = self.stop_price if self.stop_price is not None else 0
|
||||
j['high_price_in_long'] = self.high_price_in_long if self.high_price_in_long is not None else 0
|
||||
j['low_price_in_short'] = self.low_price_in_short if self.low_price_in_short is not None else 0
|
||||
j[
|
||||
'add_pos_count_under_first_price'] = self.add_pos_count_under_first_price if self.add_pos_count_under_first_price is not None else 0
|
||||
j['last_under_open_price'] = self.last_under_open_price if self.last_under_open_price is not None else 0
|
||||
|
||||
j['max_pos'] = self.max_pos if self.max_pos is not None else 0
|
||||
j['tns_direction'] = self.tns_direction if self.tns_direction is not None else EMPTY_STRING
|
||||
j['tns_count'] = self.tns_count if self.tns_count is not None else 0
|
||||
|
||||
j['tns_has_opened'] = self.tns_has_opened
|
||||
j['last_risk_level'] = self.last_risk_level
|
||||
|
||||
return j
|
||||
|
||||
def fromJson(self, json_data):
|
||||
"""
|
||||
将dict转化为属性
|
||||
:param json_data:
|
||||
:return:
|
||||
"""
|
||||
if 'create_time' in json_data:
|
||||
try:
|
||||
self.create_time = datetime.strptime(json_data['create_time'], '%Y-%m-%d %H:%M:%S')
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释create_time异常:{}'.format(str(ex)))
|
||||
self.create_time = datetime.now()
|
||||
|
||||
if 'save_time' in json_data:
|
||||
try:
|
||||
self.save_time = datetime.strptime(json_data['save_time'], '%Y-%m-%d %H:%M:%S')
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释save_time异常:{}'.format(str(ex)))
|
||||
self.save_time = datetime.now()
|
||||
|
||||
if 'tns_open_price' in json_data:
|
||||
try:
|
||||
self.tns_open_price = json_data['tns_open_price']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释tns_open_price异常:{}'.format(str(ex)))
|
||||
self.tns_open_price = 0
|
||||
|
||||
if 'tns_open_date' in json_data:
|
||||
try:
|
||||
self.tns_open_date = json_data['tns_open_date']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释tns_open_date异常:{}'.format(str(ex)))
|
||||
self.tns_open_date = ''
|
||||
|
||||
if 'last_under_open_price' in json_data:
|
||||
try:
|
||||
self.last_under_open_price = json_data['last_under_open_price']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释last_under_open_price异常:{}'.format(str(ex)))
|
||||
self.last_under_open_price = 0
|
||||
|
||||
if 'last_open_price' in json_data:
|
||||
try:
|
||||
self.last_open_price = json_data['last_open_price']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释last_open_price异常:{}'.format(str(ex)))
|
||||
self.last_open_price = 0
|
||||
|
||||
if 'stop_price' in json_data:
|
||||
try:
|
||||
self.stop_price = json_data['stop_price']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释stop_price异常:{}'.format(str(ex)))
|
||||
self.stop_price = 0
|
||||
|
||||
if 'high_price_in_long' in json_data:
|
||||
try:
|
||||
self.high_price_in_long = json_data['high_price_in_long']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释high_price_in_long异常:{}'.format(str(ex)))
|
||||
self.high_price_in_long = 0
|
||||
|
||||
if 'low_price_in_short' in json_data:
|
||||
try:
|
||||
self.low_price_in_short = json_data['low_price_in_short']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释low_price_in_short异常:{}'.format(str(ex)))
|
||||
self.low_price_in_short = 0
|
||||
|
||||
if 'max_pos' in json_data:
|
||||
try:
|
||||
self.max_pos = json_data['max_pos']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释max_pos异常:{}'.format(str(ex)))
|
||||
self.max_pos = 0
|
||||
|
||||
if 'add_pos_count_under_first_price' in json_data:
|
||||
try:
|
||||
self.add_pos_count_under_first_price = json_data['add_pos_count_under_first_price']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释add_pos_count_under_first_price异常:{}'.format(str(ex)))
|
||||
self.add_pos_count_under_first_price = 0
|
||||
|
||||
if 'tns_direction' in json_data:
|
||||
try:
|
||||
self.tns_direction = json_data['tns_direction']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释tns_direction异常:{}'.format(str(ex)))
|
||||
self.tns_direction = EMPTY_STRING
|
||||
|
||||
if 'tns_count' in json_data:
|
||||
try:
|
||||
self.tns_count = json_data['tns_count']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释tns_count异常:{}'.format(str(ex)))
|
||||
self.tns_count = EMPTY_STRING
|
||||
|
||||
if 'tns_has_opened' in json_data:
|
||||
try:
|
||||
self.tns_has_opened = json_data['tns_has_opened']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释tns_has_opened异常:{}'.format(str(ex)))
|
||||
self.tns_has_opened = False
|
||||
|
||||
if 'last_risk_level' in json_data:
|
||||
try:
|
||||
self.last_risk_level = json_data['last_risk_level']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释last_risk_level异常:{}'.format(str(ex)))
|
||||
self.last_risk_level = 0
|
||||
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
清空数据
|
||||
:return:
|
||||
"""
|
||||
self.writeCtaLog(u'清空policy数据')
|
||||
self.tns_open_price = 0
|
||||
self.tns_open_date = ''
|
||||
self.last_open_price = 0
|
||||
self.last_under_open_price = 0
|
||||
self.stop_price = 0
|
||||
self.high_price_in_long = 0
|
||||
self.low_price_in_short = 0
|
||||
self.max_pos = 0
|
||||
self.add_pos_count_under_first_price = 0
|
||||
self.tns_direction = None
|
||||
self.tns_has_opened = False
|
||||
self.last_risk_level = 0
|
||||
self.tns_count = 0
|
||||
|
||||
class TrendPolicy(CtaPolicy):
|
||||
"""
|
||||
趋势策略规则
|
||||
每次趋势交易,看作一个事务
|
||||
"""
|
||||
|
||||
def __init__(self, strategy):
|
||||
super(TrendPolicy, self).__init__(strategy)
|
||||
|
||||
self.tns_open_price = 0 # 首次开仓价格
|
||||
self.last_open_price = 0 # 最后一次加仓价格
|
||||
self.stop_price = 0 # 止损价
|
||||
self.high_price_in_long = 0 # 多趋势时,最高价
|
||||
self.low_price_in_short = 0 # 空趋势时,最低价
|
||||
self.last_under_open_price = 0 # 低于首次开仓价的补仓价格
|
||||
self.add_pos_count_under_first_price = 0 # 低于首次开仓价的补仓次数
|
||||
self.tns_direction = None # 日线方向 DIRECTION_LONG 向上/ DIRECTION_SHORT向下
|
||||
self.tns_count = 0 # 日线事务,>0,做多方向,<0,做空方向
|
||||
self.max_pos = 0 # 事务中最大仓位
|
||||
self.pos_to_add = [] # 开仓/加仓得仓位数量分布列表
|
||||
self.pos_reduced = {} # 减仓记录
|
||||
self.last_reduce_price = 0 # 最近一次减仓记录
|
||||
|
||||
self.tns_has_opened = False
|
||||
self.tns_open_date = '' # 事务开启的交易日
|
||||
self.last_balance_level = 0 # 上一次平衡的级别
|
||||
self.last_risk = EMPTY_STRING # 上一次risk评估类别
|
||||
self.cur_risk = EMPTY_STRING # 当前risk评估类别
|
||||
|
||||
# 加仓记录
|
||||
self.dtosc_add_pos = {} # DTOSC 加仓
|
||||
|
||||
def toJson(self):
|
||||
"""
|
||||
将数据转换成dict
|
||||
:return:
|
||||
"""
|
||||
j = OrderedDict()
|
||||
j['create_time'] = self.create_time.strftime('%Y-%m-%d %H:%M:%S') if self.create_time is not None else EMPTY_STRING
|
||||
j['save_time'] = self.save_time.strftime('%Y-%m-%d %H:%M:%S') if self.save_time is not None else EMPTY_STRING
|
||||
j['tns_open_date'] = self.tns_open_date
|
||||
j['tns_open_price'] = self.tns_open_price if self.tns_open_price is not None else 0
|
||||
|
||||
j['last_open_price'] = self.last_open_price if self.last_open_price is not None else 0
|
||||
j['stop_price'] = self.stop_price if self.stop_price is not None else 0
|
||||
j['high_price_in_long'] = self.high_price_in_long if self.high_price_in_long is not None else 0
|
||||
j['low_price_in_short'] = self.low_price_in_short if self.low_price_in_short is not None else 0
|
||||
j['add_pos_count_under_first_price'] = self.add_pos_count_under_first_price if self.add_pos_count_under_first_price is not None else 0
|
||||
j['last_under_open_price'] = self.last_under_open_price if self.last_under_open_price is not None else 0
|
||||
|
||||
j['max_pos'] = self.max_pos if self.max_pos is not None else 0
|
||||
j['tns_direction'] = self.tns_direction if self.tns_direction is not None else EMPTY_STRING
|
||||
j['tns_count'] = self.tns_count if self.tns_count is not None else 0
|
||||
j['pos_to_add'] = self.pos_to_add
|
||||
j['pos_reduced'] = self.pos_reduced
|
||||
j['tns_has_opened'] = self.tns_has_opened
|
||||
j['last_balance_level'] = self.last_balance_level
|
||||
j['last_reduce_price'] = self.last_reduce_price
|
||||
j['dtosc_add_pos'] = self.dtosc_add_pos
|
||||
return j
|
||||
|
||||
def fromJson(self, json_data):
|
||||
"""
|
||||
将dict转化为属性
|
||||
:param json_data:
|
||||
:return:
|
||||
"""
|
||||
if 'create_time' in json_data:
|
||||
try:
|
||||
self.create_time = datetime.strptime(json_data['create_time'], '%Y-%m-%d %H:%M:%S')
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释create_time异常:{}'.format(str(ex)))
|
||||
self.create_time = datetime.now()
|
||||
|
||||
if 'save_time' in json_data:
|
||||
try:
|
||||
self.save_time = datetime.strptime(json_data['save_time'], '%Y-%m-%d %H:%M:%S')
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释save_time异常:{}'.format(str(ex)))
|
||||
self.save_time = datetime.now()
|
||||
|
||||
if 'tns_open_price' in json_data:
|
||||
try:
|
||||
self.tns_open_price = json_data['tns_open_price']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释tns_open_price异常:{}'.format(str(ex)))
|
||||
self.tns_open_price = 0
|
||||
|
||||
if 'tns_open_date' in json_data:
|
||||
try:
|
||||
self.tns_open_date = json_data['tns_open_date']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释tns_open_date异常:{}'.format(str(ex)))
|
||||
self.tns_open_date = ''
|
||||
|
||||
if 'last_under_open_price' in json_data:
|
||||
try:
|
||||
self.last_under_open_price = json_data['last_under_open_price']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释last_under_open_price异常:{}'.format(str(ex)))
|
||||
self.last_under_open_price = 0
|
||||
|
||||
if 'last_open_price' in json_data:
|
||||
try:
|
||||
self.last_open_price = json_data['last_open_price']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释last_open_price异常:{}'.format(str(ex)))
|
||||
self.last_open_price = 0
|
||||
|
||||
if 'last_reduce_price' in json_data:
|
||||
try:
|
||||
self.last_reduce_price = json_data['last_reduce_price']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释last_reduce_price异常:{}'.format(str(ex)))
|
||||
self.last_reduce_price = 0
|
||||
|
||||
if 'stop_price' in json_data:
|
||||
try:
|
||||
self.stop_price = json_data['stop_price']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释stop_price异常:{}'.format(str(ex)))
|
||||
self.stop_price = 0
|
||||
|
||||
if 'high_price_in_long' in json_data:
|
||||
try:
|
||||
self.high_price_in_long = json_data['high_price_in_long']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释high_price_in_long异常:{}'.format(str(ex)))
|
||||
self.high_price_in_long = 0
|
||||
|
||||
if 'low_price_in_short' in json_data:
|
||||
try:
|
||||
self.low_price_in_short = json_data['low_price_in_short']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释low_price_in_short异常:{}'.format(str(ex)))
|
||||
self.low_price_in_short = 0
|
||||
|
||||
if 'max_pos' in json_data:
|
||||
try:
|
||||
self.max_pos = json_data['max_pos']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释max_pos异常:{}'.format(str(ex)))
|
||||
self.max_pos = 0
|
||||
|
||||
if 'add_pos_count_under_first_price' in json_data:
|
||||
try:
|
||||
self.add_pos_count_under_first_price = json_data['add_pos_count_under_first_price']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释add_pos_count_under_first_price异常:{}'.format(str(ex)))
|
||||
self.add_pos_count_under_first_price = 0
|
||||
|
||||
if 'tns_direction' in json_data:
|
||||
try:
|
||||
self.tns_direction = json_data['tns_direction']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释tns_direction异常:{}'.format(str(ex)))
|
||||
self.tns_direction = EMPTY_STRING
|
||||
|
||||
if 'tns_count' in json_data:
|
||||
try:
|
||||
self.tns_count = json_data['tns_count']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释tns_count异常:{}'.format(str(ex)))
|
||||
self.tns_count = EMPTY_STRING
|
||||
|
||||
if 'pos_to_add' in json_data:
|
||||
try:
|
||||
self.pos_to_add = json_data['pos_to_add']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释pos_to_add异常:{}'.format(str(ex)))
|
||||
self.pos_to_add = []
|
||||
|
||||
if 'pos_reduced' in json_data:
|
||||
try:
|
||||
self.pos_reduced = json_data['pos_reduced']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释pos_reduced异常:{}'.format(str(ex)))
|
||||
self.pos_reduced = {}
|
||||
|
||||
if 'tns_has_opened' in json_data:
|
||||
try:
|
||||
self.tns_has_opened = json_data['tns_has_opened']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释tns_has_opened异常:{}'.format(str(ex)))
|
||||
self.tns_has_opened = False
|
||||
|
||||
if 'last_balance_level' in json_data:
|
||||
try:
|
||||
self.last_balance_level = json_data['last_balance_level']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释last_balance_level异常:{}'.format(str(ex)))
|
||||
self.last_balance_level = 0
|
||||
|
||||
if 'dtosc_add_pos' in json_data:
|
||||
try:
|
||||
self.dtosc_add_pos = json_data['dtosc_add_pos']
|
||||
except Exception as ex:
|
||||
self.writeCtaError(u'解释dtosc_add_pos异常:{}'.format(str(ex)))
|
||||
self.dtosc_add_pos = {}
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
清空数据
|
||||
:return:
|
||||
"""
|
||||
self.writeCtaLog(u'清空policy数据')
|
||||
self.tns_open_price = 0
|
||||
self.tns_open_date = ''
|
||||
self.last_open_price = 0
|
||||
self.last_under_open_price = 0
|
||||
self.stop_price = 0
|
||||
self.high_price_in_long = 0
|
||||
self.low_price_in_short = 0
|
||||
self.max_pos = 0
|
||||
self.add_pos_count_under_first_price = 0
|
||||
self.tns_direction = None
|
||||
self.pos_to_add = []
|
||||
self.pos_reduced = {}
|
||||
self.last_reduce_price = 0
|
||||
self.tns_has_opened = False
|
||||
self.last_balance_level = 0
|
||||
self.dtosc_add_pos = {}
|
||||
|
||||
def get_last_reduced_pos(self, reduce_type):
|
||||
"""
|
||||
获取最后得减仓数量
|
||||
:param reduce_type:
|
||||
:return:
|
||||
"""
|
||||
reduce_list = self.pos_reduced.get(reduce_type,[])
|
||||
if len(reduce_list):
|
||||
last_pos = reduce_list.pop(-1)
|
||||
return last_pos
|
||||
return 0
|
||||
|
||||
def get_all_reduced_pos(self, reduce_type):
|
||||
"""
|
||||
获取该类型得所有减仓数量
|
||||
:param reduce_type:
|
||||
:return:
|
||||
"""
|
||||
reduce_list = self.pos_reduced.get(reduce_type, [])
|
||||
return sum(reduce_list)
|
||||
|
||||
def add_reduced_pos(self, reduce_type, reduce_volume):
|
||||
"""
|
||||
添加减仓数量
|
||||
:param reduce_type:
|
||||
:param reduce_volume:
|
||||
:return:
|
||||
"""
|
||||
reduce_list = self.pos_reduced.get(reduce_type, [])
|
||||
reduce_list.append(reduce_volume)
|
||||
self.pos_reduced[reduce_type] = reduce_list
|
||||
|
||||
def calculatePosToAdd(self, total_pos, add_count):
|
||||
"""
|
||||
计算可供加仓得仓位
|
||||
仓位 M%
|
||||
加仓次数限定为:N次(N不超过16次)
|
||||
则平均每次加仓手数为M%/N
|
||||
那么第一次加仓手数为M%/N*(1+(N/2)/10)
|
||||
第二次加仓手数为M%/N*(1+(N/2-1)/10)
|
||||
……
|
||||
第N/2次加仓手数为M%/N*1.1
|
||||
第N/2+1次加仓手数为M%/N*0.9
|
||||
……
|
||||
第N次加仓手数为M%/N*(1-(N/2)/10)
|
||||
:param direction:
|
||||
:return:
|
||||
"""
|
||||
max_pos = total_pos
|
||||
|
||||
if len(self.pos_to_add) > 0:
|
||||
self.writeCtaLog(u'加仓参考清单已存在,不重新分配:{}'.format(self.pos_to_add))
|
||||
return
|
||||
|
||||
avg_pos = max_pos / add_count
|
||||
|
||||
for i in range(0, add_count):
|
||||
if max_pos <= 0:
|
||||
self.writeCtaLog(u'分配完毕')
|
||||
break
|
||||
add_pos = avg_pos * (1+(add_count/2-i)/10)
|
||||
add_pos = int(add_pos) if add_pos >= 1 else 1
|
||||
if max_pos <= add_pos:
|
||||
add_pos = max_pos
|
||||
max_pos = 0
|
||||
else:
|
||||
max_pos -= add_pos
|
||||
|
||||
self.pos_to_add.append(add_pos)
|
||||
|
||||
if max_pos > 0:
|
||||
self.pos_to_add.append(max_pos)
|
||||
|
||||
self.writeCtaLog(u'总数量{},计划:{}'.format(total_pos,self.pos_to_add))
|
||||
|
||||
def getPosToAdd(self, max_pos):
|
||||
if len(self.pos_to_add) == 0:
|
||||
if max_pos > self.max_pos:
|
||||
self.writeCtaLog(u'最大允许仓位{}>现最大加仓{}'.format(max_pos,self.max_pos))
|
||||
self.pos_to_add.append(max_pos - self.max_pos)
|
||||
return max_pos - self.max_pos
|
||||
else:
|
||||
self.writeCtaError(u'没有仓位可加')
|
||||
return 0
|
||||
else:
|
||||
return self.pos_to_add[0]
|
||||
|
||||
def removePosToAdd(self, remove_pos):
|
||||
|
||||
if len(self.pos_to_add) == 0:
|
||||
self.writeCtaError(u'可加仓清单列表为空,不能删除')
|
||||
return
|
||||
|
||||
pos = self.pos_to_add.pop(0)
|
||||
if pos > remove_pos:
|
||||
self.writeCtaLog(u'不全移除,剩余:{}补充到最后'.format(pos-remove_pos))
|
||||
self.pos_to_add.append(pos - remove_pos)
|
||||
else:
|
||||
self.writeCtaLog(u'移除:{},剩余:{}'.format(pos,self.pos_to_add))
|
||||
|
||||
|
||||
|
@ -259,20 +259,29 @@ class CtaTemplate(object):
|
||||
# ----------------------------------------------------------------------
|
||||
def writeCtaLog(self, content):
|
||||
"""记录CTA日志"""
|
||||
content = self.name + ':' + content
|
||||
self.ctaEngine.writeCtaLog(content)
|
||||
try:
|
||||
self.ctaEngine.writeCtaLog(content, strategy_name=self.name)
|
||||
except Exception as ex:
|
||||
content = self.name + ':' + content
|
||||
self.ctaEngine.writeCtaLog(content)
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def writeCtaError(self, content):
|
||||
"""记录CTA出错日志"""
|
||||
content = self.name + ':' + content
|
||||
self.ctaEngine.writeCtaError(content)
|
||||
try:
|
||||
self.ctaEngine.writeCtaError(content, strategy_name=self.name)
|
||||
except Exception as ex:
|
||||
content = self.name + ':' + content
|
||||
self.ctaEngine.writeCtaError(content)
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def writeCtaWarning(self, content):
|
||||
"""记录CTA告警日志"""
|
||||
content = self.name + ':' + content
|
||||
self.ctaEngine.writeCtaWarning(content)
|
||||
try:
|
||||
self.ctaEngine.writeCtaWarning(content, strategy_name=self.name)
|
||||
except Exception as ex:
|
||||
content = self.name + ':' + content
|
||||
self.ctaEngine.writeCtaWarning(content)
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def writeCtaNotification(self, content):
|
||||
@ -287,10 +296,15 @@ class CtaTemplate(object):
|
||||
# ----------------------------------------------------------------------
|
||||
def writeCtaCritical(self, content):
|
||||
"""记录CTA系统异常日志"""
|
||||
content = self.name + ':' + content
|
||||
|
||||
if not self.backtesting:
|
||||
self.ctaEngine.writeCtaCritical(content)
|
||||
try:
|
||||
self.ctaEngine.writeCtaCritical(content,strategy_name=self.name)
|
||||
except Exception as ex:
|
||||
content = self.name + ':' + content
|
||||
self.ctaEngine.writeCtaCritical(content)
|
||||
else:
|
||||
content = self.name + ':' + content
|
||||
self.ctaEngine.writeCtaError(content)
|
||||
|
||||
def sendSignal(self,direction,price, level):
|
||||
|
@ -12,6 +12,7 @@ from vnpy.trader.vtFunction import *
|
||||
from vnpy.trader.vtGateway import *
|
||||
from vnpy.trader.vtText import text as vtText
|
||||
from vnpy.trader.uiQt import QtWidgets, QtGui, QtCore, BASIC_FONT
|
||||
from vnpy.trader.vtConstant import EXCHANGE_BINANCE,EXCHANGE_OKEX
|
||||
|
||||
if str(platform.system()) == 'Windows':
|
||||
import winsound
|
||||
@ -36,8 +37,9 @@ class BasicCell(QtWidgets.QTableWidgetItem):
|
||||
"""设置内容"""
|
||||
if text == '0' or text == '0.0' or type(text) == type(None):
|
||||
self.setText('')
|
||||
#elif type(text) == type(float):
|
||||
# self.setText(str(text))
|
||||
elif isinstance(text, float):
|
||||
f_str = str("%.8f" % text)
|
||||
self.setText(floatToStr(f_str))
|
||||
else:
|
||||
self.setText(str(text))
|
||||
|
||||
@ -295,73 +297,76 @@ class BasicMonitor(QtWidgets.QTableWidget):
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def updateData(self, data):
|
||||
"""将数据更新到表格中"""
|
||||
# 如果允许了排序功能,则插入数据前必须关闭,否则插入新的数据会变乱
|
||||
if self.sorting:
|
||||
self.setSortingEnabled(False)
|
||||
|
||||
# 如果设置了dataKey,则采用存量更新模式
|
||||
if self.dataKey:
|
||||
if isinstance(self.dataKey, list):
|
||||
# 多个key,逐一组合
|
||||
key = '_'.join([getattr(data, item, '') for item in self.dataKey])
|
||||
try:
|
||||
"""将数据更新到表格中"""
|
||||
# 如果允许了排序功能,则插入数据前必须关闭,否则插入新的数据会变乱
|
||||
if self.sorting:
|
||||
self.setSortingEnabled(False)
|
||||
|
||||
# 如果设置了dataKey,则采用存量更新模式
|
||||
if self.dataKey:
|
||||
if isinstance(self.dataKey, list):
|
||||
# 多个key,逐一组合
|
||||
key = '_'.join([getattr(data, item, '') for item in self.dataKey])
|
||||
else:
|
||||
# 单个key
|
||||
key = getattr(data, self.dataKey, None)
|
||||
if key is None:
|
||||
print('uiBaseWidget.updateData() error: data had not attribute {} '.format(self.dataKey))
|
||||
return
|
||||
# 如果键在数据字典中不存在,则先插入新的一行,并创建对应单元格
|
||||
if key not in self.dataDict:
|
||||
self.insertRow(0)
|
||||
d = {}
|
||||
for n, header in enumerate(self.headerList):
|
||||
content = safeUnicode(data.__getattribute__(header))
|
||||
cellType = self.headerDict[header]['cellType']
|
||||
cell = cellType(content, self.mainEngine)
|
||||
|
||||
if self.font:
|
||||
cell.setFont(self.font) # 如果设置了特殊字体,则进行单元格设置
|
||||
|
||||
if self.saveData: # 如果设置了保存数据对象,则进行对象保存
|
||||
cell.data = data
|
||||
|
||||
self.setItem(0, n, cell)
|
||||
d[header] = cell
|
||||
self.dataDict[key] = d
|
||||
# 否则如果已经存在,则直接更新相关单元格
|
||||
else:
|
||||
d = self.dataDict[key]
|
||||
for header in self.headerList:
|
||||
content = safeUnicode(data.__getattribute__(header))
|
||||
cell = d[header]
|
||||
cell.setContent(content)
|
||||
|
||||
if self.saveData: # 如果设置了保存数据对象,则进行对象保存
|
||||
cell.data = data
|
||||
# 否则采用增量更新模式
|
||||
else:
|
||||
# 单个key
|
||||
key = getattr(data, self.dataKey, None)
|
||||
if key is None:
|
||||
print('uiBaseWidget.updateData() error: data had not attribute {} '.format(self.dataKey))
|
||||
return
|
||||
# 如果键在数据字典中不存在,则先插入新的一行,并创建对应单元格
|
||||
if key not in self.dataDict:
|
||||
self.insertRow(0)
|
||||
d = {}
|
||||
self.insertRow(0)
|
||||
for n, header in enumerate(self.headerList):
|
||||
content = safeUnicode(data.__getattribute__(header))
|
||||
cellType = self.headerDict[header]['cellType']
|
||||
cell = cellType(content, self.mainEngine)
|
||||
|
||||
|
||||
if self.font:
|
||||
cell.setFont(self.font) # 如果设置了特殊字体,则进行单元格设置
|
||||
|
||||
if self.saveData: # 如果设置了保存数据对象,则进行对象保存
|
||||
cell.setFont(self.font)
|
||||
|
||||
if self.saveData:
|
||||
cell.data = data
|
||||
|
||||
|
||||
self.setItem(0, n, cell)
|
||||
d[header] = cell
|
||||
self.dataDict[key] = d
|
||||
# 否则如果已经存在,则直接更新相关单元格
|
||||
else:
|
||||
d = self.dataDict[key]
|
||||
for header in self.headerList:
|
||||
content = safeUnicode(data.__getattribute__(header))
|
||||
cell = d[header]
|
||||
cell.setContent(content)
|
||||
|
||||
if self.saveData: # 如果设置了保存数据对象,则进行对象保存
|
||||
cell.data = data
|
||||
# 否则采用增量更新模式
|
||||
else:
|
||||
self.insertRow(0)
|
||||
for n, header in enumerate(self.headerList):
|
||||
content = safeUnicode(data.__getattribute__(header))
|
||||
cellType = self.headerDict[header]['cellType']
|
||||
cell = cellType(content, self.mainEngine)
|
||||
|
||||
if self.font:
|
||||
cell.setFont(self.font)
|
||||
|
||||
if self.saveData:
|
||||
cell.data = data
|
||||
# 调整列宽
|
||||
self.resizeColumns()
|
||||
|
||||
# 重新打开排序
|
||||
if self.sorting:
|
||||
self.setSortingEnabled(True)
|
||||
except Exception as ex:
|
||||
print('update data exception:{},{}'.format(str(ex),traceback.format_exc()),file=sys.stderr)
|
||||
|
||||
self.setItem(0, n, cell)
|
||||
|
||||
# 调整列宽
|
||||
self.resizeColumns()
|
||||
|
||||
# 重新打开排序
|
||||
if self.sorting:
|
||||
self.setSortingEnabled(True)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def resizeColumns(self):
|
||||
"""调整各列的大小"""
|
||||
@ -745,7 +750,8 @@ class TradingWidget(QtWidgets.QFrame):
|
||||
EXCHANGE_NYMEX,
|
||||
EXCHANGE_GLOBEX,
|
||||
EXCHANGE_IDEALPRO,
|
||||
EXCHANGE_OKEX]
|
||||
EXCHANGE_OKEX,
|
||||
EXCHANGE_BINANCE]
|
||||
|
||||
currencyList = [CURRENCY_NONE,
|
||||
CURRENCY_CNY,
|
||||
@ -815,7 +821,7 @@ class TradingWidget(QtWidgets.QFrame):
|
||||
|
||||
self.spinVolume = QtWidgets.QDoubleSpinBox()
|
||||
self.spinVolume.setMinimum(0)
|
||||
self.spinVolume.setDecimals(4)
|
||||
#self.spinVolume.setDecimals(8)
|
||||
self.spinVolume.setMaximum(sys.maxsize)
|
||||
|
||||
self.comboPriceType = QtWidgets.QComboBox()
|
||||
@ -991,7 +997,7 @@ class TradingWidget(QtWidgets.QFrame):
|
||||
|
||||
# 清空价格数量
|
||||
self.spinPrice.setValue(0)
|
||||
self.spinVolume.setValue(0)
|
||||
#self.spinVolume.setValue(0)
|
||||
|
||||
# 清空行情显示
|
||||
self.labelBidPrice1.setText('')
|
||||
@ -1045,7 +1051,13 @@ class TradingWidget(QtWidgets.QFrame):
|
||||
|
||||
if tick.vtSymbol == self.symbol:
|
||||
if not self.checkFixed.isChecked():
|
||||
if isinstance(tick.lastPrice, float):
|
||||
p = decimal.Decimal(str(tick.lastPrice))
|
||||
decimal_len = abs(p.as_tuple().exponent)
|
||||
if decimal_len != self.spinPrice.decimals():
|
||||
self.spinPrice.setDecimals(decimal_len)
|
||||
self.spinPrice.setValue(tick.lastPrice)
|
||||
|
||||
self.labelBidPrice1.setText('{}'.format(tick.bidPrice1))
|
||||
self.labelAskPrice1.setText('{}'.format(tick.askPrice1))
|
||||
self.labelBidVolume1.setText('{}'.format(tick.bidVolume1))
|
||||
@ -1089,6 +1101,7 @@ class TradingWidget(QtWidgets.QFrame):
|
||||
def sendOrder(self):
|
||||
"""发单"""
|
||||
symbol = str(self.lineSymbol.text())
|
||||
vtSymbol = symbol
|
||||
exchange = self.comboExchange.currentText()
|
||||
currency = self.comboCurrency.currentText()
|
||||
productClass = self.comboProductClass.currentText()
|
||||
@ -1109,6 +1122,7 @@ class TradingWidget(QtWidgets.QFrame):
|
||||
|
||||
req = VtOrderReq()
|
||||
req.symbol = symbol
|
||||
req.vtSymbol = vtSymbol
|
||||
req.exchange = exchange
|
||||
req.price = self.spinPrice.value()
|
||||
req.volume = self.spinVolume.value()
|
||||
@ -1157,6 +1171,17 @@ class TradingWidget(QtWidgets.QFrame):
|
||||
pos = cell.data
|
||||
symbol = pos.symbol
|
||||
|
||||
symbol_split_list = symbol.split('.')
|
||||
if len(symbol_split_list)==2:
|
||||
exchange_name = symbol_split_list[-1]
|
||||
if exchange_name in [EXCHANGE_OKEX,EXCHANGE_BINANCE]:
|
||||
symbol = symbol_split_list[0]
|
||||
|
||||
symbol_pair_list = symbol.split('_')
|
||||
if len(symbol_pair_list) ==1:
|
||||
if symbol.lower() == 'usdt':
|
||||
return
|
||||
symbol = symbol_pair_list[0] + '_' + 'usdt'+'.'+symbol_split_list[-1]
|
||||
# 更新交易组件的显示合约
|
||||
self.lineSymbol.setText(symbol)
|
||||
self.updateSymbol()
|
||||
@ -1164,6 +1189,14 @@ class TradingWidget(QtWidgets.QFrame):
|
||||
# 自动填写信息
|
||||
self.comboPriceType.setCurrentIndex(self.priceTypeList.index(PRICETYPE_LIMITPRICE))
|
||||
self.comboOffset.setCurrentIndex(self.offsetList.index(OFFSET_CLOSE))
|
||||
if isinstance(pos.position, float):
|
||||
p = decimal.Decimal(str(pos.position))
|
||||
decimal_len = abs(p.as_tuple().exponent)
|
||||
if decimal_len > self.spinVolume.decimals():
|
||||
self.spinVolume.setDecimals(decimal_len)
|
||||
elif isinstance(pos.position, int):
|
||||
self.spinVolume.setDecimals(0)
|
||||
|
||||
self.spinVolume.setValue(pos.position)
|
||||
|
||||
if pos.direction == DIRECTION_LONG or pos.direction == DIRECTION_NET:
|
||||
|
101
vnpy/trader/uiKLine/.gitignore
vendored
Normal file
101
vnpy/trader/uiKLine/.gitignore
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# dotenv
|
||||
.env
|
||||
|
||||
# virtualenv
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
1342
vnpy/trader/uiKLine/css.qss
Normal file
1342
vnpy/trader/uiKLine/css.qss
Normal file
File diff suppressed because it is too large
Load Diff
BIN
vnpy/trader/uiKLine/cta.ico
Normal file
BIN
vnpy/trader/uiKLine/cta.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 182 KiB |
298
vnpy/trader/uiKLine/uiCrosshair.py
Normal file
298
vnpy/trader/uiKLine/uiCrosshair.py
Normal file
@ -0,0 +1,298 @@
|
||||
# encoding: UTF-8
|
||||
import sys,os
|
||||
|
||||
import pyqtgraph as pg
|
||||
import datetime as dt
|
||||
import numpy as np
|
||||
import traceback
|
||||
|
||||
from qtpy import QtWidgets, QtGui, QtCore
|
||||
from pyqtgraph.Point import Point
|
||||
import pandas as pd
|
||||
|
||||
########################################################################
|
||||
# 十字光标支持
|
||||
########################################################################
|
||||
class Crosshair(QtCore.QObject):
|
||||
"""
|
||||
此类给pg.PlotWidget()添加crossHair功能,PlotWidget实例需要初始化时传入
|
||||
"""
|
||||
signal = QtCore.Signal(type(tuple([])))
|
||||
signalInfo = QtCore.Signal(float,float)
|
||||
#----------------------------------------------------------------------
|
||||
def __init__(self, parent, master):
|
||||
"""Constructor"""
|
||||
self.__view = parent # PlatWidget
|
||||
self.master = master # KLineWidget
|
||||
super(Crosshair, self).__init__()
|
||||
|
||||
self.xAxis = 0
|
||||
self.yAxis = 0
|
||||
|
||||
self.datas = None
|
||||
|
||||
self.yAxises = [0 for i in range(3)]
|
||||
self.leftX = [0 for i in range(3)]
|
||||
self.showHLine = [False for i in range(3)]
|
||||
self.textPrices = [pg.TextItem('', anchor=(1, 1)) for i in range(3)]
|
||||
# 取得 widget上的:主图/成交量/副图指标
|
||||
self.views = [parent.centralWidget.getItem(i+1, 0) for i in range(3)]
|
||||
self.rects = [self.views[i].sceneBoundingRect() for i in range(3)]
|
||||
self.vLines = [pg.InfiniteLine(angle=90, movable=False) for i in range(3)]
|
||||
self.hLines = [pg.InfiniteLine(angle=0, movable=False) for i in range(3)]
|
||||
|
||||
#mid 在y轴动态跟随最新价显示最新价和最新时间
|
||||
self.__textDate = pg.TextItem('date', anchor=(1, 1)) # 文本:日期/时间
|
||||
self.__textInfo = pg.TextItem('lastBarInfo') # 文本:bar信息
|
||||
self.__text_main_indicators = pg.TextItem('lastIndicatorsInfo', anchor=(1, 0)) # 文本:主图指标
|
||||
self.__text_sub_indicators = pg.TextItem('lastSubIndicatorsInfo', anchor=(1, 0)) # 文本: 副图指标
|
||||
self.__textVolume = pg.TextItem('lastBarVolume', anchor=(1,0)) # 文本: 成交量
|
||||
|
||||
self.__textDate.setZValue(2)
|
||||
self.__textInfo.setZValue(2)
|
||||
self.__text_main_indicators.setZValue(2)
|
||||
self.__text_sub_indicators.setZValue(2)
|
||||
self.__textVolume.setZValue(2)
|
||||
self.__textInfo.border = pg.mkPen(color=(230, 255, 0, 255), width=1.2)
|
||||
|
||||
for i in range(3):
|
||||
self.textPrices[i].setZValue(2)
|
||||
self.vLines[i].setPos(0)
|
||||
self.hLines[i].setPos(0)
|
||||
self.vLines[i].setZValue(0)
|
||||
self.hLines[i].setZValue(0)
|
||||
self.views[i].addItem(self.vLines[i])
|
||||
self.views[i].addItem(self.hLines[i])
|
||||
self.views[i].addItem(self.textPrices[i])
|
||||
|
||||
self.views[0].addItem(self.__textInfo, ignoreBounds=True)
|
||||
self.views[0].addItem(self.__text_main_indicators, ignoreBounds=True)
|
||||
|
||||
# 添加成交量的提示信息到 volume view
|
||||
if self.master.display_vol:
|
||||
self.views[1].addItem(self.__textVolume, ignoreBounds=True)
|
||||
|
||||
# 添加 副图指标/日期信息到 副图
|
||||
if self.master.display_sub:
|
||||
self.views[2].addItem(self.__textDate, ignoreBounds=True)
|
||||
self.views[2].addItem(self.__text_sub_indicators, ignoreBounds=True)
|
||||
else:
|
||||
# 没有启用副图,日期信息放在主图
|
||||
self.views[0].addItem(self.__textDate, ignoreBounds=True)
|
||||
|
||||
self.proxy = pg.SignalProxy(self.__view.scene().sigMouseMoved, rateLimit=360, slot=self.__mouseMoved)
|
||||
# 跨线程刷新界面支持
|
||||
self.signal.connect(self.update)
|
||||
self.signalInfo.connect(self.plotInfo)
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def update(self, pos):
|
||||
"""刷新界面显示"""
|
||||
try:
|
||||
xAxis, yAxis = pos
|
||||
xAxis, yAxis = (self.xAxis, self.yAxis) if xAxis is None else (xAxis, yAxis)
|
||||
self.moveTo(xAxis, yAxis)
|
||||
except Exception as ex:
|
||||
print(u'Crosshair.update() exception:{},trace:{}'.format(str(ex), traceback.format_exc()))
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def __mouseMoved(self,evt):
|
||||
"""鼠标移动回调"""
|
||||
try:
|
||||
pos = evt[0]
|
||||
self.rects = [self.views[i].sceneBoundingRect() for i in range(3)]
|
||||
for i in range(3):
|
||||
self.showHLine[i] = False
|
||||
if i == 1 and not self.master.display_vol:
|
||||
continue
|
||||
|
||||
if i == 2 and not self.master.display_sub:
|
||||
continue
|
||||
|
||||
if self.rects[i].contains(pos):
|
||||
mousePoint = self.views[i].vb.mapSceneToView(pos)
|
||||
xAxis = mousePoint.x()
|
||||
yAxis = mousePoint.y()
|
||||
self.yAxises[i] = yAxis
|
||||
self.showHLine[i] = True
|
||||
self.moveTo(xAxis, yAxis)
|
||||
except Exception as ex:
|
||||
print(u'_mouseMove() exception:{},trace:{}'.format(str(ex), traceback.format_exc()))
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def moveTo(self, xAxis, yAxis):
|
||||
"""
|
||||
移动
|
||||
:param xAxis:
|
||||
:param yAxis:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
xAxis,yAxis = (self.xAxis,self.yAxis) if xAxis is None else (int(xAxis),yAxis)
|
||||
self.rects = [self.views[i].sceneBoundingRect() for i in range(3)]
|
||||
if not xAxis or not yAxis:
|
||||
return
|
||||
self.xAxis = xAxis
|
||||
self.yAxis = yAxis
|
||||
self.vhLinesSetXY(xAxis,yAxis)
|
||||
self.plotInfo(xAxis,yAxis)
|
||||
self.master.pi_volume.update()
|
||||
except Exception as ex:
|
||||
print(u'_mouseMove() exception:{},trace:{}'.format(str(ex), traceback.format_exc()))
|
||||
#----------------------------------------------------------------------
|
||||
def vhLinesSetXY(self,xAxis, yAxis):
|
||||
"""水平和竖线位置设置"""
|
||||
for i in range(3):
|
||||
self.vLines[i].setPos(xAxis)
|
||||
if self.showHLine[i]:
|
||||
self.hLines[i].setPos(yAxis if i==0 else self.yAxises[i])
|
||||
self.hLines[i].show()
|
||||
else:
|
||||
self.hLines[i].hide()
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
def plotInfo(self, xAxis, yAxis):
|
||||
"""
|
||||
被嵌入的plotWidget在需要的时候通过调用此方法显示K线信息
|
||||
"""
|
||||
if self.datas is None or xAxis >= len(self.datas):
|
||||
return
|
||||
|
||||
tickDatetime = None
|
||||
openPrice = 0
|
||||
closePrice = 0
|
||||
highPrice = 0
|
||||
lowPrice = 0
|
||||
preClosePrice = 0
|
||||
volume = 0
|
||||
openInterest = 0
|
||||
|
||||
try:
|
||||
# 获取K线数据
|
||||
data = self.datas[xAxis]
|
||||
lastdata = self.datas[xAxis-1]
|
||||
tickDatetime = pd.to_datetime(data['datetime'])
|
||||
openPrice = data['open']
|
||||
closePrice = data['close']
|
||||
lowPrice = data['low']
|
||||
highPrice = data['high']
|
||||
volume = int(data['volume'])
|
||||
openInterest = int(data['openInterest'])
|
||||
preClosePrice = lastdata['close']
|
||||
|
||||
except Exception as ex:
|
||||
print(u'exception:{},trace:{}'.format(str(ex), traceback.format_exc()))
|
||||
return
|
||||
|
||||
|
||||
if( isinstance(tickDatetime, dt.datetime)):
|
||||
datetimeText = dt.datetime.strftime(tickDatetime, '%Y-%m-%d %H:%M:%S')
|
||||
dateText = dt.datetime.strftime(tickDatetime, '%Y-%m-%d')
|
||||
timeText = dt.datetime.strftime(tickDatetime, '%H:%M:%S')
|
||||
else:
|
||||
datetimeText = ""
|
||||
dateText = ""
|
||||
timeText = ""
|
||||
|
||||
# 显示所有的主图技术指标
|
||||
html = u'<div style="text-align: right">'
|
||||
for indicator in self.master.main_indicator_data:
|
||||
val = self.master.main_indicator_data[indicator][xAxis]
|
||||
col = self.master.main_indicator_colors[indicator]
|
||||
html += u'<span style="color: %s; font-size: 18px;"> %s:%.2f</span>' %(col,indicator,val)
|
||||
html += u'</div>'
|
||||
self.__text_main_indicators.setHtml(html)
|
||||
|
||||
# 显示所有的主图技术指标
|
||||
html = u'<div style="text-align: right">'
|
||||
for indicator in self.master.sub_indicator_data:
|
||||
val = self.master.sub_indicator_data[indicator][xAxis]
|
||||
col = self.master.sub_indicator_colors[indicator]
|
||||
html += u'<span style="color: %s; font-size: 18px;"> %s:%.2f</span>' %(col,indicator,val)
|
||||
html += u'</div>'
|
||||
self.__text_sub_indicators.setHtml(html)
|
||||
|
||||
# 和上一个收盘价比较,决定K线信息的字符颜色
|
||||
cOpen = 'red' if openPrice > preClosePrice else 'green'
|
||||
cClose = 'red' if closePrice > preClosePrice else 'green'
|
||||
cHigh = 'red' if highPrice > preClosePrice else 'green'
|
||||
cLow = 'red' if lowPrice > preClosePrice else 'green'
|
||||
|
||||
self.__textInfo.setHtml(
|
||||
u'<div style="text-align: center; background-color:#000">\
|
||||
<span style="color: white; font-size: 16px;">日期</span><br>\
|
||||
<span style="color: yellow; font-size: 16px;">%s</span><br>\
|
||||
<span style="color: white; font-size: 16px;">时间</span><br>\
|
||||
<span style="color: yellow; font-size: 16px;">%s</span><br>\
|
||||
<span style="color: white; font-size: 16px;">价格</span><br>\
|
||||
<span style="color: %s; font-size: 16px;">(开) %.3f</span><br>\
|
||||
<span style="color: %s; font-size: 16px;">(高) %.3f</span><br>\
|
||||
<span style="color: %s; font-size: 16px;">(低) %.3f</span><br>\
|
||||
<span style="color: %s; font-size: 16px;">(收) %.3f</span><br>\
|
||||
<span style="color: white; font-size: 16px;">成交量</span><br>\
|
||||
<span style="color: yellow; font-size: 16px;">(量) %d</span><br>\
|
||||
</div>'\
|
||||
% (dateText,timeText,cOpen,openPrice,cHigh,highPrice,\
|
||||
cLow,lowPrice,cClose,closePrice,volume))
|
||||
self.__textDate.setHtml(
|
||||
'<div style="text-align: center">\
|
||||
<span style="color: yellow; font-size: 12px;">%s</span>\
|
||||
</div>'\
|
||||
% (datetimeText))
|
||||
|
||||
self.__textVolume.setHtml(
|
||||
'<div style="text-align: right">\
|
||||
<span style="color: white; font-size: 12px;">VOL : %.3f</span>\
|
||||
</div>'\
|
||||
% (volume))
|
||||
# 坐标轴宽度
|
||||
rightAxisWidth = self.views[0].getAxis('right').width()
|
||||
bottomAxisHeight = self.views[2].getAxis('bottom').height()
|
||||
offset = QtCore.QPointF(rightAxisWidth, bottomAxisHeight)
|
||||
|
||||
# 各个顶点
|
||||
t1 = [None, None, None]
|
||||
br = [None, None, None]
|
||||
t1[0] = self.views[0].vb.mapSceneToView(self.rects[0].topLeft())
|
||||
br[0] = self.views[0].vb.mapSceneToView(self.rects[0].bottomRight() - offset)
|
||||
|
||||
if self.master.display_vol:
|
||||
t1[1] = self.views[1].vb.mapSceneToView(self.rects[1].topLeft())
|
||||
br[1] = self.views[1].vb.mapSceneToView(self.rects[1].bottomRight() - offset)
|
||||
if self.master.display_sub:
|
||||
t1[2] = self.views[2].vb.mapSceneToView(self.rects[2].topLeft())
|
||||
br[2] = self.views[2].vb.mapSceneToView(self.rects[2].bottomRight() - offset)
|
||||
|
||||
|
||||
# 显示价格
|
||||
for i in range(3):
|
||||
if self.showHLine[i] and t1[i] is not None and br[i] is not None:
|
||||
self.textPrices[i].setHtml(
|
||||
'<div style="text-align: right">\
|
||||
<span style="color: yellow; font-size: 18px;">\
|
||||
%0.3f\
|
||||
</span>\
|
||||
</div>'\
|
||||
% (yAxis if i==0 else self.yAxises[i]))
|
||||
self.textPrices[i].setPos(br[i].x(),yAxis if i==0 else self.yAxises[i])
|
||||
self.textPrices[i].show()
|
||||
else:
|
||||
self.textPrices[i].hide()
|
||||
|
||||
|
||||
# 设置坐标
|
||||
self.__textInfo.setPos(t1[0])
|
||||
self.__text_main_indicators.setPos(br[0].x(), t1[0].y())
|
||||
if self.master.display_sub:
|
||||
self.__text_sub_indicators.setPos(br[2].x(), t1[2].y())
|
||||
if self.master.display_vol:
|
||||
self.__textVolume.setPos(br[1].x(),t1[1].y())
|
||||
|
||||
# 修改对称方式防止遮挡
|
||||
self.__textDate.anchor = Point((1,1)) if xAxis > self.master.index else Point((0,1))
|
||||
if br[2] is not None:
|
||||
self.__textDate.setPos(xAxis, br[2].y())
|
||||
elif br[1] is not None:
|
||||
self.__textDate.setPos(xAxis, br[1].y())
|
||||
else:
|
||||
self.__textDate.setPos(xAxis, br[0].y())
|
1402
vnpy/trader/uiKLine/uiKLine.py
Normal file
1402
vnpy/trader/uiKLine/uiKLine.py
Normal file
File diff suppressed because it is too large
Load Diff
157
vnpy/trader/uiKLine/uiMulti4KLine.py
Normal file
157
vnpy/trader/uiKLine/uiMulti4KLine.py
Normal file
@ -0,0 +1,157 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
多周期显示K线,
|
||||
时间点同步
|
||||
华富资产/李来佳
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import ctypes
|
||||
import platform
|
||||
system = platform.system()
|
||||
|
||||
# 将repostory的目录,作为根目录,添加到系统环境中。
|
||||
ROOT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..' , '..'))
|
||||
sys.path.append(ROOT_PATH)
|
||||
|
||||
|
||||
from vnpy.trader.uiKLine.uiCrosshair import Crosshair
|
||||
from vnpy.trader.uiKLine.uiKLine import *
|
||||
|
||||
|
||||
class GridKline(QtWidgets.QWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.parent = parent
|
||||
super(GridKline, self).__init__(parent)
|
||||
|
||||
self.periods = ['m30', 'h1', 'h2', 'd']
|
||||
self.kline_dict = {}
|
||||
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
gridLayout = QtWidgets.QGridLayout()
|
||||
|
||||
for period_name in self.periods:
|
||||
canvas = KLineWidget(display_vol=False, display_sub=True)
|
||||
canvas.show()
|
||||
canvas.KLtitle.setText('{}'.format(period_name), size='18pt')
|
||||
canvas.title = '{}'.format(period_name)
|
||||
canvas.add_indicator(indicator='ma5', is_main=True)
|
||||
canvas.add_indicator(indicator='ma10', is_main=True)
|
||||
canvas.add_indicator(indicator='ma18', is_main=True)
|
||||
canvas.add_indicator(indicator='sk', is_main=False)
|
||||
canvas.add_indicator(indicator='sd', is_main=False)
|
||||
self.kline_dict[period_name] = canvas
|
||||
# 注册重定向事件
|
||||
canvas.relocate_notify_func = self.onRelocate
|
||||
|
||||
gridLayout.addWidget(self.kline_dict['m30'], 0, 1)
|
||||
gridLayout.addWidget(self.kline_dict['h1'], 0, 2)
|
||||
gridLayout.addWidget(self.kline_dict['h2'], 1, 1)
|
||||
gridLayout.addWidget(self.kline_dict['d'], 1, 2)
|
||||
|
||||
self.setLayout(gridLayout)
|
||||
|
||||
self.show()
|
||||
|
||||
self.load_multi_kline()
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def load_multi_kline(self):
|
||||
"""加载多周期窗口"""
|
||||
|
||||
try:
|
||||
for period_name in self.periods:
|
||||
canvas = self.kline_dict.get(period_name,None)
|
||||
if canvas is not None:
|
||||
df = pd.read_csv('I99_{}.csv'.format(period_name))
|
||||
df = df.set_index(pd.DatetimeIndex(df['datetime']))
|
||||
canvas.loadData(df, main_indicators=['ma5', 'ma10', 'ma18'], sub_indicators=['sk', 'sd'])
|
||||
|
||||
# 载入 回测引擎生成的成交记录
|
||||
trade_list_file = 'TradeList.csv'
|
||||
if os.path.exists(trade_list_file):
|
||||
df_trade = pd.read_csv(trade_list_file)
|
||||
self.kline_dict['h1'].add_signals(df_trade)
|
||||
|
||||
# 载入策略生成的交易事务过程
|
||||
tns_file = 'tns.csv'
|
||||
if os.path.exists(tns_file):
|
||||
df_tns = pd.read_csv(tns_file)
|
||||
self.kline_dict['h2'].add_trans_df(df_tns)
|
||||
self.kline_dict['d'].add_trans_df(df_tns)
|
||||
|
||||
|
||||
except Exception as ex:
|
||||
traceback.print_exc()
|
||||
QtWidgets.QMessageBox.warning(self, 'Exception', u'Load data Exception',
|
||||
QtWidgets.QMessageBox.Cancel,
|
||||
QtWidgets.QMessageBox.NoButton)
|
||||
|
||||
return
|
||||
|
||||
def onRelocate(self,window_id, t_value, count_k):
|
||||
"""
|
||||
重定位所有周期的时间
|
||||
:param window_id:
|
||||
:param t_value:
|
||||
:return:
|
||||
"""
|
||||
for period_name in self.periods:
|
||||
try:
|
||||
canvas = self.kline_dict.get(period_name, None)
|
||||
if canvas is not None:
|
||||
canvas.relocate(window_id,t_value, count_k)
|
||||
except Exception as ex:
|
||||
traceback.print_exc()
|
||||
########################################################################
|
||||
# 功能测试
|
||||
########################################################################
|
||||
|
||||
from vnpy.trader.uiQt import createQApp
|
||||
from vnpy.trader.vtFunction import loadIconPath
|
||||
|
||||
|
||||
def display_multi_grid():
|
||||
|
||||
qApp = createQApp()
|
||||
qApp.setWindowIcon(QtGui.QIcon(loadIconPath('dashboard.ico')))
|
||||
w = GridKline()
|
||||
w.showMaximized()
|
||||
sys.exit(qApp.exec_())
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
#
|
||||
## 界面设置
|
||||
#cfgfile = QtCore.QFile('css.qss')
|
||||
#cfgfile.open(QtCore.QFile.ReadOnly)
|
||||
#styleSheet = cfgfile.readAll()
|
||||
#styleSheet = str(styleSheet)
|
||||
#qApp.setStyleSheet(styleSheet)
|
||||
#
|
||||
# K线界面
|
||||
try:
|
||||
#ui = KLineWidget(display_vol=False,display_sub=True)
|
||||
#ui.show()
|
||||
#ui.KLtitle.setText('btc(H2)',size='20pt')
|
||||
#ui.add_indicator(indicator='ma5', is_main=True)
|
||||
#ui.add_indicator(indicator='ma10', is_main=True)
|
||||
#ui.add_indicator(indicator='ma18', is_main=True)
|
||||
#ui.add_indicator(indicator='sk',is_main=False)
|
||||
#ui.add_indicator(indicator='sd', is_main=False)
|
||||
#ui.loadData(pd.DataFrame.from_csv('data/btc_h2.csv'), main_indicators=['ma5','ma10','ma18'], sub_indicators=['sk','sd'])
|
||||
|
||||
#ui = MultiKline(parent=app)
|
||||
#ui.show()
|
||||
|
||||
#app.exec_()
|
||||
#
|
||||
|
||||
display_multi_grid()
|
||||
|
||||
except Exception as ex:
|
||||
print(u'exception:{},trace:{}'.format(str(ex), traceback.format_exc()))
|
@ -11,7 +11,23 @@ from datetime import datetime
|
||||
import importlib
|
||||
|
||||
MAX_NUMBER = 10000000000000
|
||||
MAX_DECIMAL = 4
|
||||
MAX_DECIMAL = 8
|
||||
|
||||
|
||||
def floatToStr(float_str):
|
||||
"""格式化显示浮点字符串,去除后面的0"""
|
||||
if '.' in float_str:
|
||||
llen = len(float_str)
|
||||
if llen > 0:
|
||||
for i in range(llen):
|
||||
vv = llen - i - 1
|
||||
if float_str[vv] not in ['.', '0']:
|
||||
return float_str[:vv + 1]
|
||||
elif float_str[vv] in ['.']:
|
||||
return float_str[:vv]
|
||||
return float_str
|
||||
else:
|
||||
return float_str
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
def safeUnicode(value):
|
||||
@ -25,8 +41,8 @@ def safeUnicode(value):
|
||||
if type(value) is float:
|
||||
d = decimal.Decimal(str(value))
|
||||
if abs(d.as_tuple().exponent) > MAX_DECIMAL:
|
||||
value = round(value, ndigits=MAX_DECIMAL)
|
||||
|
||||
value = round(float(value), ndigits=MAX_DECIMAL)
|
||||
|
||||
return value
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
@ -312,6 +312,7 @@ class VtOrderReq(object):
|
||||
"""Constructor"""
|
||||
self.symbol = EMPTY_STRING # 代码
|
||||
self.exchange = EMPTY_STRING # 交易所
|
||||
self.vtSymbol = EMPTY_STRING # VT合约代码
|
||||
self.price = EMPTY_FLOAT # 价格
|
||||
self.volume = EMPTY_FLOAT # 数量
|
||||
|
||||
@ -338,6 +339,7 @@ class VtCancelOrderReq(object):
|
||||
"""Constructor"""
|
||||
self.symbol = EMPTY_STRING # 代码
|
||||
self.exchange = EMPTY_STRING # 交易所
|
||||
self.vtSymbol = EMPTY_STRING # VT合约代码
|
||||
|
||||
# 以下字段主要和CTP、LTS类接口相关
|
||||
self.orderID = EMPTY_STRING # 报单号
|
||||
|
Loading…
Reference in New Issue
Block a user