[增强功能] 开源资金曲线,renko_bar
This commit is contained in:
parent
732b6606ac
commit
e327f49de6
@ -36,8 +36,8 @@ gitee 链接: https://gitee.com/vnpy2/vnpy
|
||||
|
||||
|
||||
- 提供cta_line_bar k线组件,支持国内文华/交易师/TB等分钟/小时的计算模式,支持任意秒/分钟/小时/天/周等周期,支持k线数据实时生成。
|
||||
- 提供cta_renko_bar k线组件,支持x跳动/千分比跳动 【特定开源对象】
|
||||
- 提供cta_fund_kline 资金曲线组件,策略实例/账号基本的实时资金曲线 【特定开源对象】
|
||||
- 提供cta_renko_bar k线组件,支持x跳动/千分比跳动
|
||||
- 提供cta_fund_kline 资金曲线组件,策略实例/账号基本的实时资金曲线
|
||||
- 提供cta_position 组件,支持多/空/净仓位记录,支持套利
|
||||
- 提供cta_policy 组件,持久化复杂的策略执行逻辑
|
||||
- 提供cta_period 组件,支持策略中‘周期’的逻辑
|
||||
|
572
vnpy/component/cta_fund_kline.py
Normal file
572
vnpy/component/cta_fund_kline.py
Normal file
@ -0,0 +1,572 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
# 资金曲线 - 华富资产
|
||||
# 账号级别资金曲线( 根据账号balance更新)
|
||||
# 策略实例级别资金曲线(根据pnl计算)
|
||||
# 使用小时级别KLine计算
|
||||
# 针对策略实例,bar.open_interest 为平仓权益
|
||||
# 2019/5/30
|
||||
# 1. add load trade list function on init for strategy
|
||||
# 2. add trade record function for strategy
|
||||
# 2019/6/9
|
||||
# 1. 增加renko bar的资金曲线
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
import pandas as pd
|
||||
import traceback
|
||||
|
||||
from vnpy.component.cta_line_bar import CtaHourBar
|
||||
from vnpy.component.cta_renko_bar import CtaRenkoBar
|
||||
from vnpy.trader.object import BarData, TickData
|
||||
from vnpy.trader.utility import get_folder_path, get_trading_date
|
||||
from vnpy.trader.constant import Direction, Offset, Exchange
|
||||
|
||||
|
||||
class FundKline(object):
|
||||
def __init__(self, cta_engine, setting, use_cache=False, load_trade=False):
|
||||
"""
|
||||
初始化
|
||||
:param cta_engine:
|
||||
:param setting:
|
||||
"""
|
||||
self.cta_engine = cta_engine
|
||||
self.kline = None
|
||||
self.setting = setting
|
||||
self.kline_name = setting.get('name')
|
||||
self.kline_file = None
|
||||
self.closed_profit = 0
|
||||
self.holding_profit = 0
|
||||
self.profit_list = []
|
||||
self.holding_list = []
|
||||
self.price_tick = self.setting.get('price_tick', 0.01)
|
||||
self.symbol = self.setting.get('symbol', None)
|
||||
if self.symbol is None:
|
||||
vt_symbol = self.setting.get('vt_symbol', 'fund')
|
||||
if '.' in vt_symbol:
|
||||
self.symbol, _ = vt_symbol.split('.')
|
||||
else:
|
||||
self.symbol = vt_symbol
|
||||
|
||||
if self.kline_name is None:
|
||||
self.write_error(u'setting没有配置资金曲线name变量')
|
||||
return
|
||||
|
||||
# onbar 回调函数
|
||||
self.onbar_callback = self.setting.pop('onbar_callback', None)
|
||||
|
||||
self.use_renko = self.setting.pop('use_renko', False)
|
||||
if self.use_renko:
|
||||
self.write_log(u'使用CtaRenkoBar')
|
||||
self.kline = CtaRenkoBar(strategy=self, cb_on_bar=self.on_bar, setting=self.setting)
|
||||
else:
|
||||
self.write_log(u'使用CtaHourBar')
|
||||
self.kline = CtaHourBar(strategy=self, cb_on_bar=self.on_bar, setting=self.setting)
|
||||
# self.kline = CtaDayBar(strategy=self, onBarFunc=self.onBar, setting=self.setting)
|
||||
self.inited = False
|
||||
|
||||
self.long_pos_dict = {}
|
||||
self.short_pos_dict = {}
|
||||
|
||||
# 记载历史k线
|
||||
if use_cache:
|
||||
self.load()
|
||||
else:
|
||||
self.inited = True
|
||||
# 加载历史交易记录,获取剩余持仓记录
|
||||
if (load_trade):
|
||||
self.load_trade_list()
|
||||
|
||||
def write_log(self, content, strategy_name=None):
|
||||
"""
|
||||
记录日志
|
||||
:param content:
|
||||
:return:
|
||||
"""
|
||||
if self.cta_engine:
|
||||
self.cta_engine.write_log(content, strategy_name)
|
||||
|
||||
def write_error(self, content, strategy_name=None):
|
||||
"""
|
||||
记录充值日志
|
||||
:param content:
|
||||
:return:
|
||||
"""
|
||||
if self.cta_engine:
|
||||
self.cta_engine.write_error(content, strategy_name)
|
||||
|
||||
def load(self):
|
||||
"""
|
||||
从本地csv文件恢复k线数据
|
||||
:return:
|
||||
"""
|
||||
self.kline_file = str(get_folder_path('data').joinpath('fund_{}.csv'.format(self.kline_name)))
|
||||
|
||||
# 如果数据文件存在,则加载数据
|
||||
if os.path.exists(self.kline_file):
|
||||
self.write_log(u'加载{}数据'.format(self.kline_name))
|
||||
df = pd.read_csv(self.kline_file)
|
||||
dt_now = datetime.now()
|
||||
df = df.set_index(pd.DatetimeIndex(df['datetime']))
|
||||
for dt, bar_data in df.iterrows():
|
||||
bar = BarData()
|
||||
|
||||
bar.symbol = self.symbol
|
||||
bar.datetime = dt
|
||||
bar.open_price = float(bar_data['open'])
|
||||
bar.close_price = float(bar_data['close'])
|
||||
bar.high_price = float(bar_data['high'])
|
||||
bar.low_price = float(bar_data['low'])
|
||||
bar.date = dt.strftime('%Y-%m-%d')
|
||||
str_td = str(bar_data.get('trading_date', ''))
|
||||
if len(str_td) == 8:
|
||||
bar.trading_day = str_td[0:4] + '-' + str_td[4:6] + '-' + str_td[6:8]
|
||||
elif len(str_td) == 0:
|
||||
bar.trading_day = bar.date
|
||||
else:
|
||||
bar.trading_day = get_trading_date(dt)
|
||||
bar.time = dt.strftime('%H:%M:%S')
|
||||
bar.open_interest = float(bar_data.get('open_interest', 0))
|
||||
|
||||
if self.use_renko:
|
||||
self.kline.add_bar(bar)
|
||||
else:
|
||||
# bar得时间,与当前时间相隔超过一个小时,加入完整得bar
|
||||
if (dt_now - dt).total_seconds() > 60 * 60:
|
||||
self.kline.add_bar(bar, bar_is_completed=True, bar_freq=60)
|
||||
# 可能是最后一根bar
|
||||
else:
|
||||
self.write_log(u'更新最后一根Bar:{},now:{}'.format(dt, dt_now))
|
||||
self.kline.add_bar(bar, bar_is_completed=False, bar_freq=dt.minute)
|
||||
else:
|
||||
self.write_log(u'当前没有资金历史K线文件:{}'.format(self.kline_file))
|
||||
|
||||
self.inited = True
|
||||
|
||||
# 设置 kline的输出文件
|
||||
self.kline.export_filename = self.kline_file
|
||||
self.kline.export_fields = [
|
||||
{'name': 'datetime', 'source': 'bar', 'attr': 'datetime', 'type_': 'datetime'},
|
||||
{'name': 'open', 'source': 'bar', 'attr': 'open_price', 'type_': 'float'},
|
||||
{'name': 'high', 'source': 'bar', 'attr': 'high_price', 'type_': 'float'},
|
||||
{'name': 'low', 'source': 'bar', 'attr': 'low_price', 'type_': 'float'},
|
||||
{'name': 'close', 'source': 'bar', 'attr': 'close_price', 'type_': 'float'},
|
||||
{'name': 'turnover', 'source': 'bar', 'attr': 'turnover', 'type_': 'float'},
|
||||
{'name': 'volume', 'source': 'bar', 'attr': 'volume', 'type_': 'float'},
|
||||
{'name': 'open_interest', 'source': 'bar', 'attr': 'open_interest', 'type_': 'float'}
|
||||
]
|
||||
|
||||
def save(self):
|
||||
"""保存数据"""
|
||||
for bar in self.kline.line_bar:
|
||||
self.kline.export_to_csv(bar)
|
||||
|
||||
def load_trade_list(self):
|
||||
"""
|
||||
加载策略的交易记录 data/xxxx_trade.csv
|
||||
:return:
|
||||
"""
|
||||
trade_csv_file = str(get_folder_path('data').joinpath('{}_trade.csv'.format(self.kline_name)))
|
||||
|
||||
if not os.path.exists(trade_csv_file):
|
||||
self.write_log('交易文件{} 不存在,不需要加载交易记录')
|
||||
return
|
||||
|
||||
trade_df = pd.read_csv(trade_csv_file)
|
||||
# 对交易记录进行处理,计算平仓盈亏
|
||||
for _, row in trade_df.iterrows():
|
||||
try:
|
||||
direction = row.get('direction', None)
|
||||
offset = row.get('offset', None)
|
||||
price = row.get('price', None)
|
||||
vt_symbol = row.get('vt_symbol', None)
|
||||
volume = row.get('volume', None)
|
||||
trade_time = row.get('time', None)
|
||||
|
||||
if 'SPD' in vt_symbol:
|
||||
continue
|
||||
|
||||
# 如果开仓类型,放入队列
|
||||
if direction == u'多' and offset == u'开仓':
|
||||
exist_buy_list = self.long_pos_dict.get(vt_symbol, [])
|
||||
exist_buy_list.append({'volume': volume, 'price': price, 'open_time': trade_time})
|
||||
self.long_pos_dict.update({vt_symbol: exist_buy_list})
|
||||
continue
|
||||
|
||||
if direction == u'空' and offset == u'开仓':
|
||||
exist_short_list = self.short_pos_dict.get(vt_symbol, [])
|
||||
exist_short_list.append({'volume': volume, 'price': price, 'open_time': trade_time})
|
||||
self.short_pos_dict.update({vt_symbol: exist_short_list})
|
||||
continue
|
||||
|
||||
if direction == u'空' and offset in [u'平仓', u'平今', u'平昨']:
|
||||
sell_volume = volume
|
||||
exist_buy_list = self.long_pos_dict.get(vt_symbol, [])
|
||||
|
||||
# 循环,一直到sell单被满足
|
||||
while (sell_volume > 0):
|
||||
if len(exist_buy_list) == 0:
|
||||
self.write_log(u'{}没有足够的{}多单,数据不齐全,需要补全.{}'
|
||||
.format(self.kline_name, vt_symbol, row), strategy_name=self.kline_name)
|
||||
break
|
||||
buy_trade = exist_buy_list.pop(0)
|
||||
buy_volume = buy_trade.get('volume', 0)
|
||||
open_time = buy_trade.get('open_time', None)
|
||||
trade_volume = 0
|
||||
|
||||
if sell_volume <= buy_volume:
|
||||
trade_volume = sell_volume
|
||||
buy_trade.update({'volume': buy_volume - sell_volume})
|
||||
sell_volume = 0
|
||||
|
||||
else:
|
||||
sell_volume -= buy_volume
|
||||
trade_volume = buy_volume
|
||||
buy_trade.update({'volume': 0})
|
||||
|
||||
self.write_log(f'{open_time} {trade_volume} => selled')
|
||||
|
||||
# 仍然有剩余的多单
|
||||
if buy_trade.get('volume', 0) > 0:
|
||||
exist_buy_list.insert(0, buy_trade)
|
||||
self.long_pos_dict.update({vt_symbol: exist_buy_list})
|
||||
|
||||
if direction == u'多' and offset in [u'平仓', u'平今', u'平昨']:
|
||||
cover_volume = volume
|
||||
exist_short_list = self.short_pos_dict.get(vt_symbol, [])
|
||||
|
||||
# 循环,一直到cover单被满足
|
||||
while cover_volume > 0:
|
||||
if len(exist_short_list) == 0:
|
||||
self.write_error(u'{}没有足够的{}空单,数据不齐全,需要补全.{}'
|
||||
.format(self.kline_name, vt_symbol, row), strategy_name=self.kline_name)
|
||||
break
|
||||
short_trade = exist_short_list.pop(0)
|
||||
short_volume = short_trade.get('volume', 0)
|
||||
open_time = short_trade.get('open_time', None)
|
||||
trade_volume = 0
|
||||
|
||||
if cover_volume <= short_volume:
|
||||
trade_volume = cover_volume
|
||||
short_trade.update({'volume': short_volume - cover_volume})
|
||||
cover_volume = 0
|
||||
|
||||
else:
|
||||
cover_volume -= short_volume
|
||||
trade_volume = short_volume
|
||||
short_trade.update({'volume': 0})
|
||||
|
||||
self.write_log(f'{open_time} {trade_volume} => covered')
|
||||
# 仍然有剩余的空单
|
||||
if short_trade.get('volume', 0) > 0:
|
||||
exist_short_list.insert(0, short_trade)
|
||||
self.short_pos_dict.update({vt_symbol: exist_short_list})
|
||||
|
||||
except Exception as ex:
|
||||
self.write_error(u'{}发生异常:{}'.format(self.kline_name, str(ex)))
|
||||
pass
|
||||
|
||||
self.write_log(u'{}加载历史交易数据完毕'.format(self.kline_name))
|
||||
if len(self.long_pos_dict) > 0:
|
||||
self.write_log(u'记录得持仓多单:{}'.format(self.long_pos_dict))
|
||||
if len(self.short_pos_dict) > 0:
|
||||
self.write_log(u'记录得持仓空单:{}'.format(self.short_pos_dict))
|
||||
|
||||
def get_hold_pnl(self, log=False, update_list=False):
|
||||
"""
|
||||
获取持仓收益
|
||||
:param: log 输出日志
|
||||
:param: update_list: 更新self.holding_list
|
||||
:return:
|
||||
"""
|
||||
all_holding_profit = 0.0
|
||||
holded = False
|
||||
|
||||
# 计算所有多单的持仓盈亏
|
||||
for vt_symbol, long_trade_list in self.long_pos_dict.items():
|
||||
cur_price = self.cta_engine.get_price(vt_symbol)
|
||||
if cur_price is None:
|
||||
continue
|
||||
cur_size = self.cta_engine.get_size(vt_symbol)
|
||||
long_holding_profit = 0
|
||||
traded = False
|
||||
for buy_trade in long_trade_list:
|
||||
open_price = buy_trade.get('price', 0)
|
||||
cur_profit = (cur_price - open_price) * cur_size * buy_trade.get('volume')
|
||||
long_holding_profit += cur_profit
|
||||
traded = True
|
||||
holded = True
|
||||
if update_list:
|
||||
holding_record = {'open_time': buy_trade.get('open_time'),
|
||||
'vt_symbol': vt_symbol,
|
||||
'open_action': 'Buy',
|
||||
'volume': int(buy_trade.get('volume', 0)),
|
||||
'open_price': float(buy_trade.get('price', 0.0)),
|
||||
'cur_price': cur_price,
|
||||
'cur_profit': cur_profit,
|
||||
'holding_profit': long_holding_profit
|
||||
}
|
||||
self.holding_list.append(holding_record)
|
||||
all_holding_profit += long_holding_profit
|
||||
if log and traded:
|
||||
self.write_log(u'{}多单持仓收益:{}'.format(vt_symbol, long_holding_profit), strategy_name=self.kline_name)
|
||||
|
||||
# 计算所有空单的持仓盈亏
|
||||
for vt_symbol, short_trade_list in self.short_pos_dict.items():
|
||||
cur_price = self.cta_engine.get_price(vt_symbol)
|
||||
if cur_price is None:
|
||||
continue
|
||||
cur_size = self.cta_engine.get_size(vt_symbol)
|
||||
short_holding_profit = 0
|
||||
traded = False
|
||||
for short_trade in short_trade_list:
|
||||
open_price = short_trade.get('price', 0)
|
||||
cur_profit = (open_price - cur_price) * cur_size * short_trade.get('volume')
|
||||
short_holding_profit += cur_profit
|
||||
traded = True
|
||||
holded = True
|
||||
if update_list:
|
||||
holding_record = {'open_time': short_trade.get('open_time'),
|
||||
'vt_symbol': vt_symbol,
|
||||
'open_action': 'Buy',
|
||||
'volume': int(short_trade.get('volume', 0)),
|
||||
'open_price': float(short_trade.get('price', 0.0)),
|
||||
'cur_price': cur_price,
|
||||
'cur_profit': cur_profit,
|
||||
'holding_profit': short_holding_profit
|
||||
}
|
||||
self.holding_list.append(holding_record)
|
||||
all_holding_profit += short_holding_profit
|
||||
if log and traded:
|
||||
self.write_log(u'{}空单单持仓收益:{}'.format(vt_symbol, short_holding_profit), strategy_name=self.kline_name)
|
||||
return all_holding_profit, holded
|
||||
|
||||
def on_bar(self, *args, **kwargs):
|
||||
if self.onbar_callback and len(args) > 0:
|
||||
try:
|
||||
self.onbar_callback(*args, **kwargs)
|
||||
except Exception as ex:
|
||||
self.write_error(u'执行onbar回调函数异常:{},tb:{}'.format(str(ex), traceback.format_exc()))
|
||||
|
||||
def update_account(self, dt, balance):
|
||||
"""
|
||||
更新资金曲线
|
||||
:param dt:
|
||||
:param balance: 账号级别,直接使用账号得的balance;
|
||||
:return:
|
||||
"""
|
||||
tick = TickData(
|
||||
gateway_name='Fund',
|
||||
symbol=self.symbol,
|
||||
exchange=Exchange.LOCAL,
|
||||
datetime=dt
|
||||
)
|
||||
tick.last_price = balance
|
||||
tick.volume = 1
|
||||
tick.ask_price_1 = balance
|
||||
tick.ask_volume_1 = 1
|
||||
tick.bid_price_1 = balance
|
||||
tick.bid_volume_1 = 1
|
||||
tick.date = tick.datetime.strftime('%Y-%m-%d')
|
||||
tick.time = tick.datetime.strftime('%H:%M:%S')
|
||||
tick.trading_day = get_trading_date(dt)
|
||||
tick.open_interest = balance
|
||||
|
||||
if self.inited:
|
||||
self.kline.on_tick(tick)
|
||||
|
||||
# 如果是从账号更新,无法更新持仓盈亏
|
||||
self.closed_profit = balance
|
||||
self.holding_profit = 0
|
||||
|
||||
def update_trade(self, trade):
|
||||
"""
|
||||
更新策略的交易记录
|
||||
:param trade:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
|
||||
# 如果开仓类型,放入队列
|
||||
if trade.direction == Direction.LONG and trade.offset == Offset.OPEN:
|
||||
exist_buy_list = self.long_pos_dict.get(trade.vt_symbol, [])
|
||||
exist_buy_list.append({'volume': trade.volume, 'price': trade.price, 'open_time': trade.time})
|
||||
self.long_pos_dict.update({trade.vt_symbol: exist_buy_list})
|
||||
self.write_log(u'更新{}的持仓记录:{}'.format(trade.vt_symbol, exist_buy_list))
|
||||
return
|
||||
|
||||
if trade.direction == Direction.SHORT and trade.offset == Offset.OPEN:
|
||||
exist_short_list = self.short_pos_dict.get(trade.vt_symbol, [])
|
||||
exist_short_list.append({'volume': trade.volume, 'price': trade.price, 'open_time': trade.time})
|
||||
self.short_pos_dict.update({trade.vt_symbol: exist_short_list})
|
||||
self.write_log(u'更新{}的持仓记录:{}'.format(trade.vt_symbol, exist_short_list))
|
||||
return
|
||||
|
||||
if trade.direction == Direction.SHORT and trade.offset in [Offset.CLOSE, Offset.CLOSETODAY,
|
||||
Offset.CLOSEYESTERDAY]:
|
||||
sell_volume = trade.volume
|
||||
exist_buy_list = self.long_pos_dict.get(trade.vt_symbol, [])
|
||||
close_profit = 0
|
||||
# 循环,一直到sell单被满足
|
||||
while (sell_volume > 0):
|
||||
if len(exist_buy_list) == 0:
|
||||
self.write_error(
|
||||
u'{}没有足够的{}多单记录,数据不齐全.{}'.format(self.kline_name, trade.vt_symbol, trade.__dict__))
|
||||
return
|
||||
buy_trade = exist_buy_list.pop(0)
|
||||
buy_volume = buy_trade.get('volume', 0)
|
||||
trade_volume = 0
|
||||
|
||||
if sell_volume <= buy_volume:
|
||||
trade_volume = sell_volume
|
||||
buy_trade.update({'volume': buy_volume - sell_volume})
|
||||
sell_volume = 0
|
||||
|
||||
else:
|
||||
sell_volume -= buy_volume
|
||||
trade_volume = buy_volume
|
||||
buy_trade.update({'volume': 0})
|
||||
|
||||
# 仍然有剩余的多单
|
||||
if buy_trade.get('volume', 0) > 0:
|
||||
exist_buy_list.insert(0, buy_trade)
|
||||
self.long_pos_dict.update({trade.vt_symbol: exist_buy_list})
|
||||
|
||||
symbol_size = self.cta_engine.get_size(trade.vt_symbol)
|
||||
cur_profit = (trade.price - float(buy_trade.get('price', 0.0))) * symbol_size * trade_volume
|
||||
close_profit += cur_profit
|
||||
self.closed_profit += cur_profit
|
||||
profit_record = {'open_time': buy_trade.get('time'),
|
||||
'vt_symbol': trade.vt_symbol,
|
||||
'open_action': 'Buy',
|
||||
'volume': trade_volume,
|
||||
'open_price': buy_trade.get('price', 0.0),
|
||||
'close_time': trade.time,
|
||||
'close_price': trade.price,
|
||||
'close_action': 'Sell',
|
||||
'cur_profit': cur_profit,
|
||||
'closed_profit': self.closed_profit
|
||||
}
|
||||
self.profit_list.append(profit_record)
|
||||
|
||||
trade_dt = datetime.now()
|
||||
if len(trade.time) > 8 and ' ' in trade.time:
|
||||
try:
|
||||
trade_dt = datetime.strptime(trade.time, '%Y-%m-%d %H:%M:%S')
|
||||
except Exception:
|
||||
trade_dt = datetime.now()
|
||||
|
||||
hold_pnl, _ = self.get_hold_pnl(log=True)
|
||||
|
||||
self.update_strategy(dt=trade_dt, closed_pnl=close_profit, hold_pnl=hold_pnl)
|
||||
|
||||
cur_openIntesting = self.kline.line_bar[-1].open_interest if len(
|
||||
self.kline.line_bar) > 0 else close_profit
|
||||
self.write_log(u'{} 多单 {} => {}平仓,当前平仓收益:{},策略收益:{}'
|
||||
.format(self.kline_name, trade.vt_symbol, trade.time,
|
||||
close_profit, cur_openIntesting))
|
||||
|
||||
if trade.direction == Direction.LONG and trade.offset in [Offset.CLOSE, Offset.CLOSETODAY,
|
||||
Offset.CLOSEYESTERDAY]:
|
||||
cover_volume = trade.volume
|
||||
exist_short_list = self.short_pos_dict.get(trade.vt_symbol, [])
|
||||
close_profit = 0
|
||||
# 循环,一直到cover单被满足
|
||||
while (cover_volume > 0):
|
||||
if len(exist_short_list) == 0:
|
||||
self.write_error(u'{}没有足够的{}空单,数据不齐全,数据需要补全.{}'
|
||||
.format(self.kline_name, trade.vt_symbol, trade.__dict__))
|
||||
return
|
||||
short_trade = exist_short_list.pop(0)
|
||||
short_volume = short_trade.get('volume', 0)
|
||||
open_time = short_trade.get('open_time', None)
|
||||
trade_volume = 0
|
||||
|
||||
if cover_volume <= short_volume:
|
||||
trade_volume = cover_volume
|
||||
short_trade.update({'volume': short_volume - cover_volume})
|
||||
cover_volume = 0
|
||||
|
||||
else:
|
||||
cover_volume -= short_volume
|
||||
trade_volume = short_volume
|
||||
short_trade.update({'volume': 0})
|
||||
|
||||
# 仍然有剩余的空单
|
||||
if short_trade.get('volume', 0) > 0:
|
||||
exist_short_list.insert(0, short_trade)
|
||||
self.short_pos_dict.update({trade.vt_symbol: exist_short_list})
|
||||
symbol_size = self.cta_engine.get_size(trade.vt_symbol)
|
||||
cur_profit = (float(short_trade.get('price', 0.0)) - trade.price) * symbol_size * trade_volume
|
||||
close_profit += cur_profit
|
||||
self.closed_profit += cur_profit
|
||||
profit_record = {'open_time': short_trade.get('time'),
|
||||
'vt_symbol': trade.vt_symbol,
|
||||
'open_action': 'Short',
|
||||
'volume': trade_volume,
|
||||
'open_price': short_trade.get('price', 0.0),
|
||||
'close_time': trade.time,
|
||||
'close_price': trade.price,
|
||||
'close_action': 'Cover',
|
||||
'cur_profit': cur_profit,
|
||||
'closed_profit': self.closed_profit
|
||||
}
|
||||
self.profit_list.append(profit_record)
|
||||
|
||||
cur_openIntesting = self.kline.line_bar[-1].open_interest if len(
|
||||
self.kline.line_bar) > 0 else close_profit
|
||||
self.write_log(f'{self.kline_name} {open_time} {trade.vt_symbol}空单 => '
|
||||
f'{trade.time} 平仓,累计平仓收益:{close_profit},策略收益:{cur_openIntesting}')
|
||||
trade_dt = datetime.now()
|
||||
if len(trade.time) > 8 and ' ' in trade.time:
|
||||
try:
|
||||
trade_dt = datetime.strptime(trade.time, '%Y-%m-%d %H:%M:%S')
|
||||
except Exception:
|
||||
trade_dt = datetime.now()
|
||||
|
||||
hold_pnl, _ = self.get_hold_pnl(log=True)
|
||||
self.update_strategy(dt=trade_dt, closed_pnl=close_profit, hold_pnl=hold_pnl)
|
||||
|
||||
except Exception as ex:
|
||||
self.write_error(u'更新策略{}交易记录异常:{}'.format(self.kline_name, str(ex)))
|
||||
self.write_error(traceback.format_exc())
|
||||
|
||||
def update_strategy(self, dt, closed_pnl=0, hold_pnl=0):
|
||||
"""
|
||||
更新资金曲线
|
||||
:param dt:
|
||||
:param closed_pnl: 策略提供的平仓盈亏
|
||||
:param hold_pnl: 策略提供的持仓盈亏
|
||||
:return:
|
||||
"""
|
||||
# 获取当前bar的平仓权益
|
||||
open_interest = 0
|
||||
if len(self.kline.line_bar) > 0:
|
||||
open_interest = self.kline.line_bar[-1].open_interest
|
||||
|
||||
if closed_pnl != 0:
|
||||
self.write_log(u'策略平仓收益:{}->{}'.format(open_interest, open_interest + closed_pnl))
|
||||
open_interest += closed_pnl
|
||||
|
||||
tick = TickData(gateway_name='Fund',
|
||||
symbol=self.symbol,
|
||||
exchange=Exchange.LOCAL,
|
||||
datetime=dt)
|
||||
|
||||
tick.last_price = open_interest + hold_pnl
|
||||
tick.volume = 1
|
||||
tick.ask_price1 = open_interest + hold_pnl
|
||||
tick.ask_volume1 = 1
|
||||
tick.bid_price1 = open_interest + hold_pnl
|
||||
tick.bid_volume1 = 1
|
||||
tick.datetime = dt
|
||||
tick.open_interest = open_interest
|
||||
tick.date = tick.datetime.strftime('%Y-%m-%d')
|
||||
tick.time = tick.datetime.strftime('%H:%M:%S')
|
||||
tick.trading_day = get_trading_date(dt)
|
||||
|
||||
if self.inited:
|
||||
self.kline.on_tick(tick)
|
||||
|
||||
self.closed_profit = open_interest
|
||||
self.holding_profit = hold_pnl
|
@ -1294,7 +1294,7 @@ class CtaLineBar(object):
|
||||
|
||||
if self.para_pre_len <= 0: # 不计算
|
||||
return
|
||||
count_len = min(self.para_pre_len, self.bar_len)
|
||||
count_len = min(self.para_pre_len, self.bar_len - 1)
|
||||
|
||||
# 2.计算前self.para_pre_len周期内的Bar高点和低点(不包含当前周期,因为当前正在合成的bar
|
||||
# 还未触发on_bar,不会存入开高低收序列)
|
||||
@ -1487,7 +1487,7 @@ class CtaLineBar(object):
|
||||
|
||||
# 计算第一条MA均线
|
||||
if self.para_ma1_len > 0:
|
||||
count_len = min(self.para_ma1_len, self.bar_len)
|
||||
count_len = min(self.para_ma1_len, self.bar_len - 1)
|
||||
|
||||
barMa1 = ta.MA(self.close_array[-count_len:], count_len)[-1]
|
||||
if np.isnan(barMa1):
|
||||
@ -1508,7 +1508,7 @@ class CtaLineBar(object):
|
||||
|
||||
# 计算第二条MA均线
|
||||
if self.para_ma2_len > 0:
|
||||
count_len = min(self.para_ma2_len, self.bar_len)
|
||||
count_len = min(self.para_ma2_len, self.bar_len - 1)
|
||||
barMa2 = ta.MA(self.close_array[-count_len:], count_len)[-1]
|
||||
if np.isnan(barMa2):
|
||||
return
|
||||
@ -1528,7 +1528,7 @@ class CtaLineBar(object):
|
||||
|
||||
# 计算第三条MA均线
|
||||
if self.para_ma3_len > 0:
|
||||
count_len = min(self.para_ma3_len, self.bar_len)
|
||||
count_len = min(self.para_ma3_len, self.bar_len - 1)
|
||||
barMa3 = ta.MA(self.close_array[-count_len:], count_len)[-1]
|
||||
if np.isnan(barMa3):
|
||||
return
|
||||
@ -1725,7 +1725,7 @@ class CtaLineBar(object):
|
||||
|
||||
# 计算第一条EMA均线
|
||||
if self.para_ema1_len > 0:
|
||||
count_len = min(self.para_ema1_len, self.bar_len)
|
||||
count_len = min(self.para_ema1_len, self.bar_len - 1)
|
||||
|
||||
# 3、获取前InputN周期(不包含当前周期)的K线
|
||||
barEma1 = ta.EMA(self.close_array[-ema1_data_len:], count_len)[-1]
|
||||
@ -1739,7 +1739,7 @@ class CtaLineBar(object):
|
||||
|
||||
# 计算第二条EMA均线
|
||||
if self.para_ema2_len > 0:
|
||||
count_len = min(self.bar_len, self.para_ema2_len)
|
||||
count_len = min(self.bar_len - 1, self.para_ema2_len)
|
||||
|
||||
# 3、获取前InputN周期(不包含当前周期)的自适应均线
|
||||
|
||||
@ -1754,7 +1754,7 @@ class CtaLineBar(object):
|
||||
|
||||
# 计算第三条EMA均线
|
||||
if self.para_ema3_len > 0:
|
||||
count_len = min(self.bar_len, self.para_ema3_len)
|
||||
count_len = min(self.bar_len - 1, self.para_ema3_len)
|
||||
|
||||
# 3、获取前InputN周期(不包含当前周期)的自适应均线
|
||||
barEma3 = ta.EMA(self.close_array[-ema3_data_len:], count_len)[-1]
|
||||
@ -1982,7 +1982,7 @@ class CtaLineBar(object):
|
||||
|
||||
# 计算 ATR
|
||||
if self.para_atr1_len > 0:
|
||||
count_len = min(self.bar_len, self.para_atr1_len)
|
||||
count_len = min(self.bar_len - 1, self.para_atr1_len)
|
||||
cur_atr1 = ta.ATR(self.high_array[-count_len * 2:], self.low_array[-count_len * 2:],
|
||||
self.close_array[-count_len * 2:], count_len)
|
||||
self.cur_atr1 = round(cur_atr1[-1], self.round_n)
|
||||
@ -1991,7 +1991,7 @@ class CtaLineBar(object):
|
||||
self.line_atr1.append(self.cur_atr1)
|
||||
|
||||
if self.para_atr2_len > 0:
|
||||
count_len = min(self.bar_len, self.para_atr2_len)
|
||||
count_len = min(self.bar_len - 1, self.para_atr2_len)
|
||||
cur_atr2 = ta.ATR(self.high_array[-count_len * 2:], self.low_array[-count_len * 2:],
|
||||
self.close_array[-count_len * 2:], count_len)
|
||||
self.cur_atr2 = round(cur_atr2[-1], self.round_n)
|
||||
@ -2000,7 +2000,7 @@ class CtaLineBar(object):
|
||||
self.line_atr2.append(self.cur_atr2)
|
||||
|
||||
if self.para_atr3_len > 0:
|
||||
count_len = min(self.bar_len, self.para_atr3_len)
|
||||
count_len = min(self.bar_len - 1, self.para_atr3_len)
|
||||
cur_atr3 = ta.ATR(self.high_array[-count_len * 2:], self.low_array[-count_len * 2:],
|
||||
self.close_array[-count_len * 2:], count_len)
|
||||
self.cur_atr3 = round(cur_atr3[-1], self.round_n)
|
||||
@ -2017,8 +2017,8 @@ class CtaLineBar(object):
|
||||
if self.para_vol_len <= 0: # 不计算
|
||||
return
|
||||
|
||||
bar_len = min(self.bar_len, self.para_vol_len)
|
||||
sumVol = sum([x.volume for x in self.line_bar[-bar_len:]])
|
||||
bar_len = min(self.bar_len - 1, self.para_vol_len)
|
||||
sumVol = sum([x.volume for x in self.line_bar[-bar_len - 1:-1]])
|
||||
avgVol = round(sumVol / bar_len, 0)
|
||||
|
||||
if len(self.line_vol_ma) > self.max_hold_bars:
|
||||
@ -2134,7 +2134,7 @@ class CtaLineBar(object):
|
||||
self.write_log(u'数据未充分,当前Bar数据数量:{0},计算Boll需要:{1}'.
|
||||
format(len(self.line_bar), min(14, self.para_boll_len) + 1))
|
||||
else:
|
||||
bollLen = min(self.bar_len, self.para_boll_len)
|
||||
bollLen = min(self.bar_len - 1, self.para_boll_len)
|
||||
|
||||
# 不包含当前最新的Bar
|
||||
upper_list, middle_list, lower_list = ta.BBANDS(self.close_array,
|
||||
@ -2195,7 +2195,7 @@ class CtaLineBar(object):
|
||||
self.write_log(u'数据未充分,当前Bar数据数量:{0},计算Boll2需要:{1}'.
|
||||
format(len(self.line_bar), min(14, self.para_boll2_len) + 1))
|
||||
else:
|
||||
boll2Len = min(self.bar_len, self.para_boll2_len)
|
||||
boll2Len = min(self.bar_len - 1, self.para_boll2_len)
|
||||
|
||||
# 不包含当前最新的Bar
|
||||
upper_list, middle_list, lower_list = ta.BBANDS(self.close_array,
|
||||
@ -2256,7 +2256,7 @@ class CtaLineBar(object):
|
||||
self.write_log(u'数据未充分,当前Bar数据数量:{0},计算Boll需要:{1}'.
|
||||
format(len(self.line_bar), min(14, self.para_boll_tb_len) + 1))
|
||||
else:
|
||||
bollLen = min(self.bar_len, self.para_boll_tb_len)
|
||||
bollLen = min(self.bar_len - 1, self.para_boll_tb_len)
|
||||
|
||||
# 不包含当前最新的Bar
|
||||
|
||||
@ -2312,7 +2312,7 @@ class CtaLineBar(object):
|
||||
self.write_log(u'数据未充分,当前Bar数据数量:{0},计算Boll2需要:{1}'.
|
||||
format(len(self.line_bar), min(14, self.para_boll2_tb_len) + 1))
|
||||
else:
|
||||
boll2Len = min(self.bar_len, self.para_boll2_tb_len)
|
||||
boll2Len = min(self.bar_len - 1, self.para_boll2_tb_len)
|
||||
|
||||
if len(self.line_boll2_upper) > self.max_hold_bars:
|
||||
del self.line_boll2_upper[0]
|
||||
@ -2565,7 +2565,7 @@ class CtaLineBar(object):
|
||||
if self.para_kdj_smooth_len == 0:
|
||||
self.para_kdj_smooth_len = 3
|
||||
|
||||
inputKdjLen = min(self.para_kdj_len, self.bar_len)
|
||||
inputKdjLen = min(self.para_kdj_len, self.bar_len - 1)
|
||||
|
||||
hhv = max(self.high_array[-inputKdjLen:])
|
||||
llv = min(self.low_array[-inputKdjLen:])
|
||||
@ -2680,7 +2680,7 @@ class CtaLineBar(object):
|
||||
if self.bar_len < 3:
|
||||
return
|
||||
|
||||
data_len = min(self.bar_len, self.para_kdj_tb_len)
|
||||
data_len = min(self.bar_len - 1, self.para_kdj_tb_len)
|
||||
|
||||
hhv = max(self.high_array[-data_len:])
|
||||
llv = min(self.low_array[-data_len:])
|
||||
@ -2808,13 +2808,13 @@ class CtaLineBar(object):
|
||||
|
||||
maxLen = max(self.para_macd_fast_len, self.para_macd_slow_len) + self.para_macd_signal_len
|
||||
|
||||
maxLen = maxLen * 3 # 注:数据长度需要足够,才能准确。测试过,3倍长度才可以与国内的文华等软件一致
|
||||
# maxLen = maxLen * 3 # 注:数据长度需要足够,才能准确。测试过,3倍长度才可以与国内的文华等软件一致
|
||||
|
||||
if self.bar_len < maxLen:
|
||||
self.write_log(u'数据未充分,当前Bar数据数量:{0},计算MACD需要:{1}'.format(len(self.line_bar), maxLen))
|
||||
if self.bar_len - 1 < maxLen:
|
||||
self.write_log(u'数据未充分,当前Bar数据数量:{0},计算MACD需要:{1}'.format(self.bar_len - 1, maxLen))
|
||||
return
|
||||
|
||||
dif_list, dea_list, macd_list = ta.MACD(self.close_array[-2 * maxLen:], fastperiod=self.para_macd_fast_len,
|
||||
dif_list, dea_list, macd_list = ta.MACD(self.close_array, fastperiod=self.para_macd_fast_len,
|
||||
slowperiod=self.para_macd_slow_len,
|
||||
signalperiod=self.para_macd_signal_len)
|
||||
if np.isnan(dif_list[-1]) or np.isnan(dea_list[-1]) or np.isnan(macd_list[-1]):
|
||||
@ -3223,7 +3223,7 @@ class CtaLineBar(object):
|
||||
observation=self.close_array[-1])
|
||||
m = state_means[-1].item()
|
||||
c = state_covariances[-1].item()
|
||||
std_len = 26 if self.bar_len > 26 else self.bar_len
|
||||
std_len = 26 if self.bar_len - 1 > 26 else self.bar_len - 1
|
||||
std = np.std(self.close_array[-std_len:], ddof=1)
|
||||
self.cur_state_std = std
|
||||
if len(self.line_state_mean) > self.max_hold_bars:
|
||||
@ -3503,7 +3503,7 @@ class CtaLineBar(object):
|
||||
return
|
||||
|
||||
data_len = max(self.para_skd_fast_len * 2, self.para_skd_fast_len + 20)
|
||||
if self.bar_len < data_len:
|
||||
if self.bar_len - 1 < data_len:
|
||||
return
|
||||
|
||||
# 计算最后一根Bar的RSI指标
|
||||
@ -3966,7 +3966,7 @@ class CtaLineBar(object):
|
||||
# format(len(self.lineBar), 4 * self.inputYbLen))
|
||||
# return
|
||||
|
||||
ema_len = min(self.bar_len, self.para_yb_len)
|
||||
ema_len = min(self.bar_len - 1, self.para_yb_len)
|
||||
if ema_len < 3:
|
||||
self.write_log(u'数据未充分,当前Bar数据数量:{0}'.
|
||||
format(len(self.line_bar)))
|
||||
@ -4027,7 +4027,7 @@ class CtaLineBar(object):
|
||||
return
|
||||
if self.bar_len < 2:
|
||||
return
|
||||
bar_len = min(self.para_golden_n, self.bar_len)
|
||||
bar_len = min(self.para_golden_n, self.bar_len - 1)
|
||||
|
||||
hhv = max(self.high_array[-bar_len:])
|
||||
llv = min(self.low_array[-bar_len:])
|
||||
@ -4117,7 +4117,7 @@ class CtaLineBar(object):
|
||||
format(len(self.line_bar), min(14, self.para_bias_len) + 1))
|
||||
else:
|
||||
|
||||
BiasLen = min(self.para_bias_len, self.bar_len)
|
||||
BiasLen = min(self.para_bias_len, self.bar_len - 1)
|
||||
|
||||
# 计算BIAS
|
||||
m = np.mean(self.close_array[-BiasLen:])
|
||||
@ -4133,7 +4133,7 @@ class CtaLineBar(object):
|
||||
self.write_log(u'数据未充分,当前Bar数据数量:{0},计算Bias2需要:{1}'.
|
||||
format(len(self.line_bar), min(14, self.para_bias2_len) + 1))
|
||||
else:
|
||||
Bias2Len = min(self.bar_len, self.para_bias2_len)
|
||||
Bias2Len = min(self.bar_len - 1, self.para_bias2_len)
|
||||
# 计算BIAS2
|
||||
m = np.mean(self.close_array[-Bias2Len:])
|
||||
bias2 = (self.close_array[-1] - m) / m * 100
|
||||
@ -4147,7 +4147,7 @@ class CtaLineBar(object):
|
||||
self.write_log(u'数据未充分,当前Bar数据数量:{0},计算Bias3需要:{1}'.
|
||||
format(len(self.line_bar), min(14, self.para_bias3_len) + 1))
|
||||
else:
|
||||
Bias3Len = min(self.bar_len, self.para_bias3_len)
|
||||
Bias3Len = min(self.bar_len - 1, self.para_bias3_len)
|
||||
# 计算BIAS3
|
||||
m = np.mean(self.close_array[-Bias3Len:])
|
||||
bias3 = (self.close_array[-1] - m) / m * 100
|
||||
|
3998
vnpy/component/cta_renko_bar.py
Normal file
3998
vnpy/component/cta_renko_bar.py
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user