支持数字货币,增加多周期数据展示

This commit is contained in:
msincenselee 2018-05-29 11:27:41 +08:00
parent fd8ff33731
commit 7f76e50501
18 changed files with 4798 additions and 237 deletions

View File

View 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

View File

View 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

View File

@ -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()

View File

@ -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]
@ -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)
if strategy_name is None:
# 写入本地log日志
if self.logger:
self.logger.info(content)
else:
self.createLogger()
else:
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'))
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)
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)
# 自动初始化
@ -1951,22 +1992,42 @@ class PositionBuffer(object):
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
else:
if pos.direction == DIRECTION_SHORT:
self.shortPosition = pos.position # >=0
self.shortYd = pos.ydPosition # >=0
self.shortToday = self.shortPosition - self.shortYd # >=0
else:
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
@ -1986,21 +2047,3 @@ class PositionBuffer(object):
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

View File

@ -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的Highlow
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,6 +683,7 @@ class CtaLineBar(object):
# 处理日内的间隔时段最后一个tick如10:15分11:30分15:00 和 2:30分
endtick = False
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) \
@ -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,15 +2948,16 @@ 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 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) \
@ -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 #回测数据的最小跳动
@ -3351,3 +3542,5 @@ if __name__ == '__main__':
t.writeCtaLog(u'{0}:{1}'.format(Exception, ex))
traceback.print_exc()
break
t.saveData()

View File

@ -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))

View File

@ -259,18 +259,27 @@ class CtaTemplate(object):
# ----------------------------------------------------------------------
def writeCtaLog(self, content):
"""记录CTA日志"""
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出错日志"""
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告警日志"""
try:
self.ctaEngine.writeCtaWarning(content, strategy_name=self.name)
except Exception as ex:
content = self.name + ':' + content
self.ctaEngine.writeCtaWarning(content)
@ -287,10 +296,15 @@ class CtaTemplate(object):
# ----------------------------------------------------------------------
def writeCtaCritical(self, content):
"""记录CTA系统异常日志"""
content = self.name + ':' + content
if not self.backtesting:
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):

View File

@ -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,6 +297,7 @@ class BasicMonitor(QtWidgets.QTableWidget):
# ----------------------------------------------------------------------
def updateData(self, data):
try:
"""将数据更新到表格中"""
# 如果允许了排序功能,则插入数据前必须关闭,否则插入新的数据会变乱
if self.sorting:
@ -361,6 +364,8 @@ class BasicMonitor(QtWidgets.QTableWidget):
# 重新打开排序
if self.sorting:
self.setSortingEnabled(True)
except Exception as ex:
print('update data exception:{},{}'.format(str(ex),traceback.format_exc()),file=sys.stderr)
#----------------------------------------------------------------------
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
View 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

File diff suppressed because it is too large Load Diff

BIN
vnpy/trader/uiKLine/cta.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

View 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;">&nbsp;&nbsp;%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;">&nbsp;&nbsp;%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())

File diff suppressed because it is too large Load Diff

View 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()))

View File

@ -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,7 +41,7 @@ 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

View File

@ -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 # 报单号