[增强功能] 增加接口获取某一合约当前保证金(包括自定义套利合约)
This commit is contained in:
parent
8c6b926b82
commit
aafedafca3
@ -65,6 +65,7 @@ from vnpy.trader.util_logger import setup_logger
|
||||
from vnpy.data.mongo.mongo_data import MongoData
|
||||
from uuid import uuid1
|
||||
|
||||
|
||||
class BackTestingEngine(object):
|
||||
"""
|
||||
CTA回测引擎
|
||||
@ -302,6 +303,77 @@ class BackTestingEngine(object):
|
||||
def get_margin_rate(self, vt_symbol: str):
|
||||
return self.margin_rate.get(vt_symbol, 0.1)
|
||||
|
||||
def get_margin(self, vt_symbol: str):
|
||||
"""
|
||||
按照当前价格,计算1手合约需要得保证金
|
||||
:param vt_symbol:
|
||||
:return: 普通合约/期权 => 当前价格 * size * margin_rate
|
||||
SP j2101&j2105.DCE => max( 当前价格 * size * margin_rate)
|
||||
j2101-1-i2101-3-BJ.SPD => (主动腿价格*主动腿size * 主动腿margin_rate + 被动腿价格*被动腿size * 被动腿margin_rate
|
||||
rb2101-1-rb2105-1-CJ.SPD => max(主动腿价格*主动腿size * 主动腿margin_rate , 被动腿价格*被动腿size * 被动腿margin_rate
|
||||
"""
|
||||
|
||||
if '.SPD99' in vt_symbol:
|
||||
vt_symbol = vt_symbol.replace('.SPD99', '.SPD')
|
||||
|
||||
if not vt_symbol.endswith('.SPD') and '&' not in vt_symbol:
|
||||
cur_price = self.get_price(vt_symbol)
|
||||
cur_size = self.get_size(vt_symbol)
|
||||
cur_margin_rate = self.get_margin_rate(vt_symbol)
|
||||
if cur_price and cur_size and cur_margin_rate:
|
||||
return abs(cur_price * cur_size * cur_margin_rate)
|
||||
else:
|
||||
# 取不到价格,取不到size,或者取不到保证金比例
|
||||
self.write_error(f'无法计算{vt_symbol}的保证金,价格:{cur_price}或size:{cur_size}或margin_rate:{cur_margin_rate}')
|
||||
return None
|
||||
|
||||
# j2101-1-i2101-3-BJ.SPD rb2101-1-rb2105-1-CJ.SPD
|
||||
if vt_symbol.endswith('.SPD'):
|
||||
act_symbol, act_ratio, pas_symbol, pas_ratio, spd_type = vt_symbol.replace('.SPD', '').split('-')
|
||||
act_vt_symbol = '{}.{}'.format(act_symbol, self.get_exchange(act_symbol).value)
|
||||
pas_vt_symbol = '{}.{}'.format(pas_symbol, self.get_exchange(pas_symbol).value)
|
||||
act_ratio = int(act_ratio)
|
||||
pas_ratio = int(pas_ratio)
|
||||
# SP j2101&j2105.DCE
|
||||
elif '&' in vt_symbol:
|
||||
symbol, exchange = extract_vt_symbol(vt_symbol)
|
||||
symbol = symbol.split(' ')[-1]
|
||||
act_symbol, pas_symbol = symbol.split('&')
|
||||
act_vt_symbol = f'{act_symbol}.{exchange.value}'
|
||||
pas_vt_symbol = f'{pas_symbol}.{exchange.value}'
|
||||
act_ratio = 1
|
||||
pas_ratio = 1
|
||||
else:
|
||||
self.write_error(f'无法计算{vt_symbol}的保证金:无法分解')
|
||||
return None
|
||||
|
||||
act_cur_price = self.get_price(act_vt_symbol)
|
||||
act_size = self.get_size(act_vt_symbol)
|
||||
act_margin_rate = self.get_margin_rate(act_vt_symbol)
|
||||
pas_cur_price = self.get_price(pas_vt_symbol)
|
||||
pas_size = self.get_size(pas_vt_symbol)
|
||||
pas_margin_rate = self.get_margin_rate(pas_vt_symbol)
|
||||
|
||||
if not all([act_cur_price, act_size, act_margin_rate]):
|
||||
self.write_error(
|
||||
f'无法计算{vt_symbol}的保证金,{act_vt_symbol}价格:{act_cur_price}或size:{act_size}或margin_rate:{act_margin_rate}')
|
||||
return None
|
||||
if not all([pas_cur_price, pas_size, pas_margin_rate]):
|
||||
self.write_error(
|
||||
f'无法计算{vt_symbol}的保证金,{pas_vt_symbol}价格:{pas_cur_price}或size:{pas_size}或margin_rate:{pas_margin_rate}')
|
||||
return None
|
||||
|
||||
# 跨期合约
|
||||
if get_underlying_symbol(act_symbol) == get_underlying_symbol(pas_symbol):
|
||||
spd_margin = max(act_cur_price * act_size * act_margin_rate * act_ratio,
|
||||
pas_cur_price * pas_size * pas_margin_rate * pas_ratio)
|
||||
|
||||
# 跨品种合约,取最大值
|
||||
else:
|
||||
spd_margin = act_cur_price * act_size * act_margin_rate * act_ratio + pas_cur_price * pas_size * pas_margin_rate * pas_ratio
|
||||
|
||||
return spd_margin
|
||||
|
||||
def set_slippage(self, vt_symbol: str, slippage: float):
|
||||
"""设置滑点点数"""
|
||||
self.slippage.update({vt_symbol: slippage})
|
||||
@ -599,7 +671,8 @@ class BackTestingEngine(object):
|
||||
def new_bar(self, bar):
|
||||
"""新的K线"""
|
||||
self.last_bar.update({bar.vt_symbol: bar})
|
||||
if self.last_dt is None or (bar.datetime and bar.datetime > self.last_dt - timedelta(seconds=self.bar_interval_seconds)):
|
||||
if self.last_dt is None or (
|
||||
bar.datetime and bar.datetime > self.last_dt - timedelta(seconds=self.bar_interval_seconds)):
|
||||
self.last_dt = bar.datetime + timedelta(seconds=self.bar_interval_seconds)
|
||||
self.set_price(bar.vt_symbol, bar.close_price)
|
||||
self.cross_stop_order(bar=bar) # 撮合停止单
|
||||
@ -798,7 +871,7 @@ class BackTestingEngine(object):
|
||||
"""保存策略数据"""
|
||||
for strategy in self.strategies.values():
|
||||
self.write_log(u'save strategy data')
|
||||
if hasattr(strategy,'save_data'):
|
||||
if hasattr(strategy, 'save_data'):
|
||||
strategy.save_data()
|
||||
|
||||
def send_order(self,
|
||||
@ -1441,7 +1514,8 @@ class BackTestingEngine(object):
|
||||
# raise Exception(u'异常!没有空单持仓,不能cover')
|
||||
return
|
||||
|
||||
cur_short_pos_list = [s_pos.volume for s_pos in self.short_position_list if s_pos.vt_symbol == trade.vt_symbol]
|
||||
cur_short_pos_list = [s_pos.volume for s_pos in self.short_position_list if
|
||||
s_pos.vt_symbol == trade.vt_symbol]
|
||||
|
||||
self.write_log(u'{}当前空单:{}'.format(trade.vt_symbol, cur_short_pos_list))
|
||||
|
||||
@ -1454,7 +1528,8 @@ class BackTestingEngine(object):
|
||||
self.write_error(f'没有{trade.strategy_name}对应的symbol:{trade.vt_symbol}的空单持仓, 继续')
|
||||
break
|
||||
else:
|
||||
self.write_error(u'异常,{}没有对应symbol:{}的空单持仓, 终止'.format(trade.strategy_name, trade.vt_symbol))
|
||||
self.write_error(
|
||||
u'异常,{}没有对应symbol:{}的空单持仓, 终止'.format(trade.strategy_name, trade.vt_symbol))
|
||||
# raise Exception(u'realtimeCalculate2() Exception,没有对应symbol:{0}的空单持仓'.format(trade.vt_symbol))
|
||||
return
|
||||
|
||||
@ -1587,7 +1662,6 @@ class BackTestingEngine(object):
|
||||
|
||||
cover_volume = 0
|
||||
|
||||
|
||||
if g_result is not None:
|
||||
# 更新组合的数据
|
||||
g_result.turnover = g_result.turnover + result.turnover
|
||||
@ -1629,7 +1703,8 @@ class BackTestingEngine(object):
|
||||
# raise RuntimeError(f'realtimeCalculate2() Exception,没有对应的symbol:{trade.vt_symbol}多单数据,')
|
||||
return
|
||||
|
||||
cur_long_pos_list = [s_pos.volume for s_pos in self.long_position_list if s_pos.vt_symbol == trade.vt_symbol]
|
||||
cur_long_pos_list = [s_pos.volume for s_pos in self.long_position_list if
|
||||
s_pos.vt_symbol == trade.vt_symbol]
|
||||
|
||||
self.write_log(u'{}当前多单:{}'.format(trade.vt_symbol, cur_long_pos_list))
|
||||
|
||||
|
@ -850,6 +850,78 @@ class CtaEngine(BaseEngine):
|
||||
|
||||
return contract.min_volume
|
||||
|
||||
def get_margin(self, vt_symbol: str):
|
||||
"""
|
||||
按照当前价格,计算1手合约需要得保证金
|
||||
:param vt_symbol:
|
||||
:return: 普通合约/期权 => 当前价格 * size * margin_rate
|
||||
SP j2101&j2105.DCE => max( 当前价格 * size * margin_rate)
|
||||
j2101-1-i2101-3-BJ.SPD => (主动腿价格*主动腿size * 主动腿margin_rate + 被动腿价格*被动腿size * 被动腿margin_rate
|
||||
rb2101-1-rb2105-1-CJ.SPD => max(主动腿价格*主动腿size * 主动腿margin_rate , 被动腿价格*被动腿size * 被动腿margin_rate
|
||||
"""
|
||||
if not vt_symbol.endswith('.SPD') and '&' not in vt_symbol:
|
||||
cur_price = self.get_price(vt_symbol)
|
||||
cur_size = self.get_size(vt_symbol)
|
||||
cur_margin_rate = self.get_margin_rate(vt_symbol)
|
||||
if cur_price and cur_size and cur_margin_rate:
|
||||
return abs(cur_price * cur_size * cur_margin_rate)
|
||||
else:
|
||||
# 取不到价格,取不到size,或者取不到保证金比例
|
||||
self.write_error(f'无法计算{vt_symbol}的保证金,价格:{cur_price}或size:{cur_size}或margin_rate:{cur_margin_rate}')
|
||||
return None
|
||||
|
||||
# j2101-1-i2101-3-BJ.SPD rb2101-1-rb2105-1-CJ.SPD
|
||||
if vt_symbol.endswith('.SPD'):
|
||||
contract_conf = self.get_custom_contract(vt_symbol)
|
||||
if contract_conf is None:
|
||||
self.write_error(f'无法计算{vt_symbol}保证金,获取不到自定义合约配置')
|
||||
return None
|
||||
act_symbol = contract_conf.get('leg1_symbol')
|
||||
pas_symbol = contract_conf.get('leg2_symbol')
|
||||
act_vt_symbol = '{}.{}'.format(act_symbol, contract_conf.get('leg1_exchange'))
|
||||
pas_vt_symbol = '{}.{}'.format(pas_symbol, contract_conf.get('leg2_exchange'))
|
||||
act_ratio = int(contract_conf.get('leg1_ratio'))
|
||||
pas_ratio = int(contract_conf.get('leg2_ratio'))
|
||||
# SP j2101&j2105.DCE
|
||||
elif '&' in vt_symbol:
|
||||
symbol, exchange = extract_vt_symbol(vt_symbol)
|
||||
symbol = symbol.split(' ')[-1]
|
||||
act_symbol, pas_symbol = symbol.split('&')
|
||||
act_vt_symbol = f'{act_symbol}.{exchange.value}'
|
||||
pas_vt_symbol = f'{pas_symbol}.{exchange.value}'
|
||||
act_ratio = 1
|
||||
pas_ratio = 1
|
||||
else:
|
||||
self.write_error(f'无法计算{vt_symbol}的保证金:无法分解')
|
||||
return None
|
||||
|
||||
act_cur_price = self.get_price(act_vt_symbol)
|
||||
act_size = self.get_size(act_vt_symbol)
|
||||
act_margin_rate = self.get_margin_rate(act_vt_symbol)
|
||||
pas_cur_price = self.get_price(pas_vt_symbol)
|
||||
pas_size = self.get_size(pas_vt_symbol)
|
||||
pas_margin_rate = self.get_margin_rate(pas_vt_symbol)
|
||||
|
||||
if not all([act_cur_price, act_size, act_margin_rate]):
|
||||
self.write_error(
|
||||
f'无法计算{vt_symbol}的保证金,{act_vt_symbol}价格:{act_cur_price}或size:{act_size}或margin_rate:{act_margin_rate}')
|
||||
return None
|
||||
if not all([pas_cur_price, pas_size, pas_margin_rate]):
|
||||
self.write_error(
|
||||
f'无法计算{vt_symbol}的保证金,{pas_vt_symbol}价格:{pas_cur_price}或size:{pas_size}或margin_rate:{pas_margin_rate}')
|
||||
return None
|
||||
|
||||
# 跨期合约
|
||||
if get_underlying_symbol(act_symbol) == get_underlying_symbol(pas_symbol):
|
||||
spd_margin = max(act_cur_price * act_size * act_margin_rate * act_ratio,
|
||||
pas_cur_price * pas_size * pas_margin_rate * pas_ratio)
|
||||
|
||||
# 跨品种合约,取最大值
|
||||
else:
|
||||
spd_margin = act_cur_price * act_size * act_margin_rate * act_ratio + pas_cur_price * pas_size * pas_margin_rate * pas_ratio
|
||||
|
||||
return spd_margin
|
||||
|
||||
def get_tick(self, vt_symbol: str):
|
||||
"""获取合约得最新tick"""
|
||||
return self.main_engine.get_tick(vt_symbol)
|
||||
@ -873,6 +945,24 @@ class CtaEngine(BaseEngine):
|
||||
return self.main_engine.get_contract(vt_symbol)
|
||||
|
||||
def get_custom_contract(self, vt_symbol):
|
||||
"""
|
||||
获取自定义合约的设置
|
||||
:param symbol: "pb2012-1-pb2101-1-CJ"
|
||||
:return: {
|
||||
"name": "pb跨期价差",
|
||||
"exchange": "SPD",
|
||||
"leg1_symbol": "pb2012",
|
||||
"leg1_exchange": "SHFE",
|
||||
"leg1_ratio": 1,
|
||||
"leg2_symbol": "pb2101",
|
||||
"leg2_exchange": "SHFE",
|
||||
"leg2_ratio": 1,
|
||||
"is_spread": true,
|
||||
"size": 1,
|
||||
"margin_rate": 0.1,
|
||||
"price_tick": 5
|
||||
}
|
||||
"""
|
||||
return self.main_engine.get_custom_contract(vt_symbol.split('.')[0])
|
||||
|
||||
def get_all_contracts(self):
|
||||
|
@ -288,6 +288,9 @@ class SpreadTestingEngine(BackTestingEngine):
|
||||
|
||||
self.write_log(u'开始套利组合回测')
|
||||
|
||||
# 保存回测脚本到数据库
|
||||
self.save_setting_to_mongo()
|
||||
|
||||
for strategy_name, strategy_setting in strategy_settings.items():
|
||||
# 策略得启动日期
|
||||
if 'start_date' in strategy_setting:
|
||||
|
@ -771,8 +771,8 @@ class CtaProTemplate(CtaTemplate):
|
||||
|
||||
short_symbol = sg.snapshot.get('mi_symbol', self.vt_symbol)
|
||||
pos_symbols.add(short_symbol)
|
||||
self.write_log(u'加载持仓空单[{},价格:{}],[指数:{},价格:{}],数量:{}手'
|
||||
.format(short_symbol, sg.snapshot.get('open_price'),
|
||||
self.write_log(u'加载持仓空单[ID:{},vt_symbol:{},价格:{}],[指数:{},价格:{}],数量:{}手'
|
||||
.format(sg.id, short_symbol, sg.snapshot.get('open_price'),
|
||||
self.idx_symbol, sg.open_price, sg.volume))
|
||||
self.position.short_pos -= sg.volume
|
||||
|
||||
@ -797,8 +797,8 @@ class CtaProTemplate(CtaTemplate):
|
||||
long_symbol = lg.snapshot.get('mi_symbol', self.vt_symbol)
|
||||
pos_symbols.add(long_symbol)
|
||||
|
||||
self.write_log(u'加载持仓多单[{},价格:{}],[指数{},价格:{}],数量:{}手'
|
||||
.format(lg.snapshot.get('miSymbol'), lg.snapshot.get('open_price'),
|
||||
self.write_log(u'加载持仓多单[ID:{},vt_symbol:{},价格:{}],[指数{},价格:{}],数量:{}手'
|
||||
.format(lg.id, long_symbol, lg.snapshot.get('open_price'),
|
||||
self.idx_symbol, lg.open_price, lg.volume))
|
||||
self.position.long_pos += lg.volume
|
||||
|
||||
@ -899,24 +899,29 @@ class CtaProTemplate(CtaTemplate):
|
||||
|
||||
none_mi_grid = None
|
||||
none_mi_symbol = None
|
||||
|
||||
self.write_log(f'持仓换月=>启动.')
|
||||
# 找出非主力合约的持仓网格
|
||||
for g in self.gt.get_opened_grids(direction=Direction.LONG):
|
||||
none_mi_symbol = g.snapshot.get('mi_symbol')
|
||||
if none_mi_symbol is None or none_mi_symbol == self.vt_symbol:
|
||||
none_mi_symbol = g.snapshot.get('mi_symbol', g.vt_symbol)
|
||||
# 如果持仓的合约,跟策略配置的vt_symbol一致,则不处理
|
||||
if none_mi_symbol is None or none_mi_symbol == self.vt_symbol:
|
||||
self.write_log(f'none_mi_symbol:{none_mi_symbol}, vt_symbol:{self.vt_symbol} 一致,不处理')
|
||||
continue
|
||||
|
||||
# 如果未开仓,或者处于委托状态,或者已交易完毕,不处理
|
||||
if not g.open_status or g.order_status or g.volume - g.traded_volume <= 0:
|
||||
self.write_log(f'开仓状态:{g.open_status}, 委托状态:{g.order_status},网格持仓:{g.volume} ,已交易数量:{g.traded_volume}, 不处理')
|
||||
continue
|
||||
|
||||
none_mi_grid = g
|
||||
if g.traded_volume > 0 and g.volume - g.traded_volume > 0:
|
||||
|
||||
g.volume -= g.traded_volume
|
||||
g.traded_volume = 0
|
||||
break
|
||||
if none_mi_grid is None:
|
||||
return
|
||||
|
||||
self.write_log(f'持仓换月=>找到多单持仓:{none_mi_symbol},持仓数量:{none_mi_grid.volume}')
|
||||
# 找到行情中非主力合约/主力合约的最新价
|
||||
none_mi_tick = self.tick_dict.get(none_mi_symbol)
|
||||
mi_tick = self.tick_dict.get(self.vt_symbol, None)
|
||||
@ -925,24 +930,28 @@ class CtaProTemplate(CtaTemplate):
|
||||
|
||||
# 如果涨停价,不做卖出
|
||||
if self.is_upper_limit(none_mi_symbol) or self.is_upper_limit(self.vt_symbol):
|
||||
self.write_log(f'{none_mi_symbol} 或 {self.vt_symbol} 为涨停价,不做换仓')
|
||||
return
|
||||
none_mi_price = max(none_mi_tick.last_price, none_mi_tick.bid_price_1)
|
||||
|
||||
grid = deepcopy(none_mi_grid)
|
||||
grid.id = str(uuid.uuid1())
|
||||
grid.open_status = False
|
||||
self.write_log(f'持仓换月=>复制持仓信息{none_mi_symbol},ID:{none_mi_grid.id} => {self.vt_symbol},ID:{grid.id}')
|
||||
|
||||
# 委托卖出非主力合约
|
||||
|
||||
vt_orderids = self.sell(price=none_mi_price,
|
||||
volume=none_mi_grid.volume,
|
||||
vt_symbol=none_mi_symbol,
|
||||
order_type=self.order_type,
|
||||
grid=none_mi_grid)
|
||||
if len(vt_orderids) > 0:
|
||||
self.write_log(f'切换合约,委托卖出非主力合约{none_mi_symbol}持仓:{none_mi_grid.volume}')
|
||||
self.write_log(f'持仓换月=>委托卖出非主力合约{none_mi_symbol}持仓:{none_mi_grid.volume}')
|
||||
|
||||
# 已经发生过换月的,不执行买入新合约
|
||||
if none_mi_grid.snapshot.get("switched", False):
|
||||
self.write_log(f'已经执行过换月,不再创建新的买入操作')
|
||||
self.write_log(f'持仓换月=>已经执行过换月,不再创建新的买入操作')
|
||||
return
|
||||
|
||||
none_mi_grid.snapshot.update({'switched': True})
|
||||
@ -956,13 +965,13 @@ class CtaProTemplate(CtaTemplate):
|
||||
order_type=self.order_type,
|
||||
grid=grid)
|
||||
if len(vt_orderids) > 0:
|
||||
self.write_log(u'切换合约,委托买入主力合约:{},价格:{},数量:{}'
|
||||
self.write_log(u'持仓换月=>委托买入主力合约:{},价格:{},数量:{}'
|
||||
.format(self.vt_symbol, self.cur_mi_price, grid.volume))
|
||||
else:
|
||||
self.write_error(f'委托买入主力合约:{self.vt_symbol}失败')
|
||||
self.write_error(f'持仓换月=>委托买入主力合约:{self.vt_symbol}失败')
|
||||
self.gt.save()
|
||||
else:
|
||||
self.write_error(f'委托卖出非主力合约:{none_mi_symbol}失败')
|
||||
self.write_error(f'持仓换月=>委托卖出非主力合约:{none_mi_symbol}失败')
|
||||
|
||||
def tns_switch_short_pos(self):
|
||||
"""切换合约,从持仓的非主力合约,切换至主力合约"""
|
||||
|
@ -1070,6 +1070,8 @@ class CtpTdApi(TdApi):
|
||||
future_contract.update({'margin_rate': mi_margin_rate})
|
||||
future_contract.update({'symbol_size': idx_contract.size})
|
||||
future_contract.update({'price_tick': idx_contract.pricetick})
|
||||
if 'exchange' not in future_contract:
|
||||
future_contract.update({'exchange': contract.exchange.value})
|
||||
future_contracts.update({underlying_symbol: future_contract})
|
||||
self.future_contract_changed = True
|
||||
index_contracts.update({underlying_symbol: idx_contract})
|
||||
@ -2090,4 +2092,3 @@ class TqMdApi():
|
||||
self.update_thread.join()
|
||||
except Exception as e:
|
||||
self.gateway.write_log('退出天勤行情api异常:{}'.format(str(e)))
|
||||
|
||||
|
@ -452,8 +452,7 @@ class OmsEngine(BaseEngine):
|
||||
import bz2
|
||||
import pickle
|
||||
contract_file_name = 'vn_contract.pkb2'
|
||||
if not os.path.exists(contract_file_name):
|
||||
return
|
||||
if os.path.exists(contract_file_name):
|
||||
try:
|
||||
with bz2.BZ2File(contract_file_name, 'rb') as f:
|
||||
self.contracts = pickle.load(f)
|
||||
|
Loading…
Reference in New Issue
Block a user