Merge pull request #2181 from vnpy/dev-spread-crypto

Dev spread crypto
This commit is contained in:
vn.py 2019-11-07 16:08:00 +09:00 committed by GitHub
commit 2e01bef96f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 453 additions and 232 deletions

View File

@ -8,7 +8,7 @@ from vnpy.trader.ui import MainWindow, create_qapp
from vnpy.gateway.bitmex import BitmexGateway
# from vnpy.gateway.futu import FutuGateway
# from vnpy.gateway.ib import IbGateway
from vnpy.gateway.ctp import CtpGateway
# from vnpy.gateway.ctp import CtpGateway
# from vnpy.gateway.ctptest import CtptestGateway
# from vnpy.gateway.mini import MiniGateway
# from vnpy.gateway.sopt import SoptGateway
@ -18,20 +18,20 @@ from vnpy.gateway.ctp import CtpGateway
# from vnpy.gateway.oes import OesGateway
# from vnpy.gateway.okex import OkexGateway
# from vnpy.gateway.huobi import HuobiGateway
from vnpy.gateway.bitfinex import BitfinexGateway
# from vnpy.gateway.bitfinex import BitfinexGateway
# from vnpy.gateway.onetoken import OnetokenGateway
from vnpy.gateway.okexf import OkexfGateway
from vnpy.gateway.okexs import OkexsGateway
# from vnpy.gateway.okexf import OkexfGateway
# from vnpy.gateway.okexs import OkexsGateway
# from vnpy.gateway.xtp import XtpGateway
# from vnpy.gateway.hbdm import HbdmGateway
# from vnpy.gateway.tap import TapGateway
# from vnpy.gateway.tora import ToraGateway
# from vnpy.gateway.alpaca import AlpacaGateway
from vnpy.gateway.da import DaGateway
from vnpy.gateway.coinbase import CoinbaseGateway
from vnpy.gateway.bitstamp import BitstampGateway
from vnpy.gateway.gateios import GateiosGateway
from vnpy.gateway.bybit import BybitGateway
# from vnpy.gateway.da import DaGateway
# from vnpy.gateway.coinbase import CoinbaseGateway
# from vnpy.gateway.bitstamp import BitstampGateway
# from vnpy.gateway.gateios import GateiosGateway
# from vnpy.gateway.bybit import BybitGateway
from vnpy.app.cta_strategy import CtaStrategyApp
# from vnpy.app.csv_loader import CsvLoaderApp
@ -79,7 +79,7 @@ def main():
# main_engine.add_gateway(CoinbaseGateway)
# main_engine.add_gateway(BitstampGateway)
# main_engine.add_gateway(GateiosGateway)
main_engine.add_gateway(BybitGateway)
# main_engine.add_gateway(BybitGateway)
main_engine.add_app(CtaStrategyApp)
main_engine.add_app(CtaBacktesterApp)

View File

@ -48,6 +48,7 @@ public:
///客户端认证响应
virtual void OnRspAuthenticate(CThostFtdcRspAuthenticateField *pRspAuthenticateField, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) {};
///登录请求响应
virtual void OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast) {};

View File

@ -1976,6 +1976,11 @@ typedef char TThostFtdcCommentType[31];
/////////////////////////////////////////////////////////////////////////
typedef char TThostFtdcVersionType[4];
/////////////////////////////////////////////////////////////////////////
/////UtpCipherVersionType是一个版本信息类型
///////////////////////////////////////////////////////////////////////////
typedef char UtpCipherVersionType[65];
/////////////////////////////////////////////////////////////////////////
///TFtdcTradeCodeType是一个交易代码类型
/////////////////////////////////////////////////////////////////////////
@ -6410,11 +6415,11 @@ typedef char TThostFtdcClientSystemInfoType[273];
/////////////////////////////////////////////////////////////////////////
///TThostFtdcAppIDType是App代码类型
/////////////////////////////////////////////////////////////////////////
typedef char TThostFtdcClientAppIDType[21];
typedef char TThostFtdcClientAppIDType[33];
/////////////////////////////////////////////////////////////////////////
///TThostFtdcAutoCodeType是AutoCode代码类型
/////////////////////////////////////////////////////////////////////////
typedef char TThostFtdcAutoCodeType[17];
typedef char TThostFtdcAutoCodeType[17];
#endif

View File

@ -112,8 +112,8 @@ struct CThostFtdcReqAuthenticateField
TThostFtdcProductInfoType UserProductInfo;
///认证码
TThostFtdcAuthCodeType AuthCode;
///App´úÂë
TThostFtdcClientAppIDType AppID;
// App代码
TThostFtdcClientAppIDType AppID;
};
///客户端认证响应
@ -125,8 +125,8 @@ struct CThostFtdcRspAuthenticateField
TThostFtdcUserIDType UserID;
///用户端产品信息
TThostFtdcProductInfoType UserProductInfo;
///App´úÂë
TThostFtdcClientAppIDType AppID;
// App代码
TThostFtdcClientAppIDType AppID;
};
///客户端认证信息
@ -139,13 +139,15 @@ struct CThostFtdcAuthenticationInfoField
///用户端产品信息
TThostFtdcProductInfoType UserProductInfo;
///时间戳
TThostFtdcAuthInfoType TimeStamp;
TThostFtdcAuthInfoType TimeStamp;
///认证信息
TThostFtdcAuthInfoType AuthInfo;
///是否为认证结果
TThostFtdcBoolType IsResult;
///App´úÂë
TThostFtdcClientAppIDType AppID;
// App代码
TThostFtdcClientAppIDType AppID;
// 版本信息
UtpCipherVersionType VerInfo;
};
///银期转帐报文头
@ -1926,9 +1928,9 @@ struct CThostFtdcTransFundField
///投资者帐号
TThostFtdcAccountIDType AccountID;
///出金的核心地址
TThostFtdcAddressAndPortType DepositKernel;
TThostFtdcAddressAndPortType DepositKernel;
///入金的核心地址
TThostFtdcAddressAndPortType IncomingKernel;
TThostFtdcAddressAndPortType IncomingKernel;
///币种代码
TThostFtdcCurrencyIDType CurrencyID;
///转账金额

Binary file not shown.

Binary file not shown.

View File

@ -552,7 +552,8 @@ class BacktestingEngine:
self.pricetick,
self.capital,
self.end,
self.mode
self.mode,
self.inverse
)))
results.append(result)
@ -612,6 +613,7 @@ class BacktestingEngine:
global ga_capital
global ga_end
global ga_mode
global ga_inverse
ga_target_name = target_name
ga_strategy_class = self.strategy_class
@ -626,6 +628,7 @@ class BacktestingEngine:
ga_capital = self.capital
ga_end = self.end
ga_mode = self.mode
ga_inverse = self.inverse
# Set up genetic algorithem
toolbox = base.Toolbox()
@ -1217,7 +1220,8 @@ def _ga_optimize(parameter_values: tuple):
ga_pricetick,
ga_capital,
ga_end,
ga_mode
ga_mode,
ga_inverse
)
return (result[1],)

View File

@ -1,4 +1,4 @@
from typing import Sequence
from typing import Sequence, Type
from vnpy.event import EventEngine, Event
from vnpy.trader.engine import MainEngine
@ -14,7 +14,7 @@ def process_log_event(event: Event):
print(f"{log.time}\t{log.msg}")
def init_cli_trading(gateways: Sequence[BaseGateway]):
def init_cli_trading(gateways: Sequence[Type[BaseGateway]]):
""""""
event_engine = EventEngine()
event_engine.register(EVENT_LOG, process_log_event)

View File

@ -1,7 +1,8 @@
from typing import Any
from vnpy.trader.constant import Direction
from vnpy.trader.constant import Direction, Offset
from vnpy.trader.object import (TickData, OrderData, TradeData)
from vnpy.trader.utility import round_to
from .template import SpreadAlgoTemplate
from .base import SpreadData
@ -17,6 +18,7 @@ class SpreadTakerAlgo(SpreadAlgoTemplate):
algoid: str,
spread: SpreadData,
direction: Direction,
offset: Offset,
price: float,
volume: float,
payup: int,
@ -25,8 +27,9 @@ class SpreadTakerAlgo(SpreadAlgoTemplate):
):
""""""
super().__init__(
algo_engine, algoid, spread, direction,
price, volume, payup, interval, lock
algo_engine, algoid, spread,
direction, offset, price, volume,
payup, interval, lock
)
self.cancel_interval: int = 2
@ -109,6 +112,7 @@ class SpreadTakerAlgo(SpreadAlgoTemplate):
# Calcualte spread volume to hedge
active_leg = self.spread.active_leg
active_traded = self.leg_traded[active_leg.vt_symbol]
active_traded = round_to(active_traded, self.spread.min_volume)
hedge_volume = self.spread.calculate_spread_volume(
active_leg.vt_symbol,
@ -118,6 +122,8 @@ class SpreadTakerAlgo(SpreadAlgoTemplate):
# Calculate passive leg target volume and do hedge
for leg in self.spread.passive_legs:
passive_traded = self.leg_traded[leg.vt_symbol]
passive_traded = round_to(passive_traded, self.spread.min_volume)
passive_target = self.spread.calculate_leg_volume(
leg.vt_symbol,
hedge_volume

View File

@ -1,9 +1,9 @@
from typing import Dict, List
from math import floor, ceil
from datetime import datetime
from vnpy.trader.object import TickData, PositionData, TradeData
from vnpy.trader.object import TickData, PositionData, TradeData, ContractData
from vnpy.trader.constant import Direction, Offset, Exchange
from vnpy.trader.utility import floor_to, ceil_to
EVENT_SPREAD_DATA = "eSpreadData"
@ -30,15 +30,30 @@ class LegData:
self.short_pos: float = 0
self.net_pos: float = 0
self.last_price: float = 0
self.net_pos_price: float = 0 # Average entry price of net position
# Tick data buf
self.tick: TickData = None
# Contract data
self.size: float = 0
self.net_position: bool = False
self.min_volume: float = 0
def update_contract(self, contract: ContractData):
""""""
self.size = contract.size
self.net_position = contract.net_position
self.min_volume = contract.min_volume
def update_tick(self, tick: TickData):
""""""
self.bid_price = tick.bid_price_1
self.ask_price = tick.ask_price_1
self.bid_volume = tick.bid_volume_1
self.ask_volume = tick.ask_volume_1
self.last_price = tick.last_price
self.tick = tick
@ -46,6 +61,7 @@ class LegData:
""""""
if position.direction == Direction.NET:
self.net_pos = position.volume
self.net_pos_price = position.price
else:
if position.direction == Direction.LONG:
self.long_pos = position.volume
@ -55,18 +71,52 @@ class LegData:
def update_trade(self, trade: TradeData):
""""""
if trade.direction == Direction.LONG:
if trade.offset == Offset.OPEN:
self.long_pos += trade.volume
else:
self.short_pos -= trade.volume
else:
if trade.offset == Offset.OPEN:
self.short_pos += trade.volume
else:
self.long_pos -= trade.volume
# Only update net pos for contract with net position mode
if self.net_position:
trade_cost = trade.volume * trade.price
old_cost = self.net_pos * self.net_pos_price
self.net_pos = self.long_pos - self.net_pos
if trade.direction == Direction.LONG:
new_pos = self.net_pos + trade.volume
if self.net_pos >= 0:
new_cost = old_cost + trade_cost
self.net_pos_price = new_cost / new_pos
else:
# If all previous short position closed
if not new_pos:
self.net_pos_price = 0
# If only part short position closed
elif new_pos > 0:
self.net_pos_price = trade.price
else:
new_pos = self.net_pos - trade.volume
if self.net_pos <= 0:
new_cost = old_cost - trade_cost
self.net_pos_price = new_cost / new_pos
else:
# If all previous long position closed
if not new_pos:
self.net_pos_price = 0
# If only part long position closed
elif new_pos < 0:
self.net_pos_price = trade.price
self.net_pos = new_pos
else:
if trade.direction == Direction.LONG:
if trade.offset == Offset.OPEN:
self.long_pos += trade.volume
else:
self.short_pos -= trade.volume
else:
if trade.offset == Offset.OPEN:
self.short_pos += trade.volume
else:
self.long_pos -= trade.volume
self.net_pos = self.long_pos - self.short_pos
class SpreadData:
@ -78,7 +128,9 @@ class SpreadData:
legs: List[LegData],
price_multipliers: Dict[str, int],
trading_multipliers: Dict[str, int],
active_symbol: str
active_symbol: str,
inverse_contracts: Dict[str, bool],
min_volume: float
):
""""""
self.name: str = name
@ -87,11 +139,16 @@ class SpreadData:
self.active_leg: LegData = None
self.passive_legs: List[LegData] = []
self.min_volume: float = min_volume
# For calculating spread price
self.price_multipliers: Dict[str: int] = price_multipliers
self.price_multipliers: Dict[str, int] = price_multipliers
# For calculating spread pos and sending orders
self.trading_multipliers: Dict[str: int] = trading_multipliers
self.trading_multipliers: Dict[str, int] = trading_multipliers
# For inverse derivative contracts of crypto market
self.inverse_contracts: Dict[str, bool] = inverse_contracts
self.price_formula: str = ""
self.trading_formula: str = ""
@ -146,17 +203,35 @@ class SpreadData:
# Calculate volume
trading_multiplier = self.trading_multipliers[leg.vt_symbol]
inverse_contract = self.inverse_contracts[leg.vt_symbol]
if not inverse_contract:
leg_bid_volume = leg.bid_volume
leg_ask_volume = leg.ask_volume
else:
leg_bid_volume = calculate_inverse_volume(
leg.bid_volume, leg.bid_price, leg.size)
leg_ask_volume = calculate_inverse_volume(
leg.ask_volume, leg.ask_price, leg.size)
if trading_multiplier > 0:
adjusted_bid_volume = floor(
leg.bid_volume / trading_multiplier)
adjusted_ask_volume = floor(
leg.ask_volume / trading_multiplier)
adjusted_bid_volume = floor_to(
leg_bid_volume / trading_multiplier,
self.min_volume
)
adjusted_ask_volume = floor_to(
leg_ask_volume / trading_multiplier,
self.min_volume
)
else:
adjusted_bid_volume = floor(
leg.ask_volume / abs(trading_multiplier))
adjusted_ask_volume = floor(
leg.bid_volume / abs(trading_multiplier))
adjusted_bid_volume = floor_to(
leg_bid_volume / abs(trading_multiplier),
self.min_volume
)
adjusted_ask_volume = floor_to(
leg_ask_volume / abs(trading_multiplier),
self.min_volume
)
# For the first leg, just initialize
if not n:
@ -180,13 +255,21 @@ class SpreadData:
leg_short_pos = 0
trading_multiplier = self.trading_multipliers[leg.vt_symbol]
adjusted_net_pos = leg.net_pos / trading_multiplier
inverse_contract = self.inverse_contracts[leg.vt_symbol]
if not inverse_contract:
net_pos = leg.net_pos
else:
net_pos = calculate_inverse_volume(
leg.net_pos, leg.net_pos_price, leg.size)
adjusted_net_pos = net_pos / trading_multiplier
if adjusted_net_pos > 0:
adjusted_net_pos = floor(adjusted_net_pos)
adjusted_net_pos = floor_to(adjusted_net_pos, self.min_volume)
leg_long_pos = adjusted_net_pos
else:
adjusted_net_pos = ceil(adjusted_net_pos)
adjusted_net_pos = ceil_to(adjusted_net_pos, self.min_volume)
leg_short_pos = abs(adjusted_net_pos)
if not n:
@ -222,9 +305,9 @@ class SpreadData:
spread_volume = leg_volume / trading_multiplier
if spread_volume > 0:
spread_volume = floor(spread_volume)
spread_volume = floor_to(spread_volume, self.min_volume)
else:
spread_volume = ceil(spread_volume)
spread_volume = ceil_to(spread_volume, self.min_volume)
return spread_volume
@ -243,3 +326,24 @@ class SpreadData:
gateway_name="SPREAD"
)
return tick
def is_inverse(self, vt_symbol: str) -> bool:
""""""
inverse_contract = self.inverse_contracts[vt_symbol]
return inverse_contract
def get_leg_size(self, vt_symbol: str) -> float:
""""""
leg = self.legs[vt_symbol]
return leg.size
def calculate_inverse_volume(
original_volume: float,
price: float,
size: float,
) -> float:
""""""
if not price:
return 0
return original_volume * size / price

View File

@ -116,6 +116,7 @@ class SpreadDataEngine:
spread_setting["name"],
spread_setting["leg_settings"],
spread_setting["active_symbol"],
spread_setting.get("min_volume", 1),
save=False
)
@ -128,18 +129,21 @@ class SpreadDataEngine:
for leg in spread.legs.values():
price_multiplier = spread.price_multipliers[leg.vt_symbol]
trading_multiplier = spread.trading_multipliers[leg.vt_symbol]
inverse_contract = spread.inverse_contracts[leg.vt_symbol]
leg_setting = {
"vt_symbol": leg.vt_symbol,
"price_multiplier": price_multiplier,
"trading_multiplier": trading_multiplier
"trading_multiplier": trading_multiplier,
"inverse_contract": inverse_contract
}
leg_settings.append(leg_setting)
spread_setting = {
"name": spread.name,
"leg_settings": leg_settings,
"active_symbol": spread.active_leg.vt_symbol
"active_symbol": spread.active_leg.vt_symbol,
"min_volume": spread.min_volume
}
setting.append(spread_setting)
@ -194,8 +198,12 @@ class SpreadDataEngine:
def process_contract_event(self, event: Event) -> None:
""""""
contract = event.data
leg = self.legs.get(contract.vt_symbol, None)
if leg:
# Update contract data
leg.update_contract(contract)
if contract.vt_symbol in self.legs:
req = SubscribeRequest(
contract.symbol, contract.exchange
)
@ -222,6 +230,8 @@ class SpreadDataEngine:
# Subscribe market data
contract = self.main_engine.get_contract(vt_symbol)
if contract:
leg.update_contract(contract)
req = SubscribeRequest(
contract.symbol,
contract.exchange
@ -243,6 +253,7 @@ class SpreadDataEngine:
name: str,
leg_settings: List[Dict],
active_symbol: str,
min_volume: float,
save: bool = True
) -> None:
""""""
@ -253,6 +264,7 @@ class SpreadDataEngine:
legs: List[LegData] = []
price_multipliers: Dict[str, int] = {}
trading_multipliers: Dict[str, int] = {}
inverse_contracts: Dict[str, bool] = {}
for leg_setting in leg_settings:
vt_symbol = leg_setting["vt_symbol"]
@ -261,13 +273,17 @@ class SpreadDataEngine:
legs.append(leg)
price_multipliers[vt_symbol] = leg_setting["price_multiplier"]
trading_multipliers[vt_symbol] = leg_setting["trading_multiplier"]
inverse_contracts[vt_symbol] = leg_setting.get(
"inverse_contract", False)
spread = SpreadData(
name,
legs,
price_multipliers,
trading_multipliers,
active_symbol
active_symbol,
inverse_contracts,
min_volume
)
self.spreads[name] = spread
@ -414,6 +430,7 @@ class SpreadAlgoEngine:
self,
spread_name: str,
direction: Direction,
offset: Offset,
price: float,
volume: float,
payup: int,
@ -437,6 +454,7 @@ class SpreadAlgoEngine:
algoid,
spread,
direction,
offset,
price,
volume,
payup,
@ -711,7 +729,8 @@ class SpreadStrategyEngine:
strategy = self.algo_strategy_map.get(algo.algoid, None)
if strategy:
self.call_strategy_func(strategy, strategy.update_spread_algo, algo)
self.call_strategy_func(
strategy, strategy.update_spread_algo, algo)
def process_order_event(self, event: Event):
""""""
@ -894,6 +913,7 @@ class SpreadStrategyEngine:
strategy: SpreadStrategyTemplate,
spread_name: str,
direction: Direction,
offset: Offset,
price: float,
volume: float,
payup: int,
@ -904,6 +924,7 @@ class SpreadStrategyEngine:
algoid = self.spread_engine.start_algo(
spread_name,
direction,
offset,
price,
volume,
payup,

View File

@ -87,6 +87,8 @@ class BasicSpreadStrategy(SpreadStrategyTemplate):
# No position
if not self.spread_pos:
self.stop_close_algos()
# Start open algos
if not self.buy_algoid:
self.buy_algoid = self.start_long_algo(
@ -98,27 +100,20 @@ class BasicSpreadStrategy(SpreadStrategyTemplate):
self.short_price, self.max_pos, self.payup, self.interval
)
# Stop close algos
if self.sell_algoid:
self.stop_algo(self.sell_algoid)
if self.cover_algoid:
self.stop_algo(self.cover_algoid)
# Long position
elif self.spread_pos > 0:
self.stop_open_algos()
# Start sell close algo
if not self.sell_algoid:
self.sell_algoid = self.start_short_algo(
self.sell_price, self.spread_pos, self.payup, self.interval
)
# Stop buy open algo
if self.buy_algoid:
self.stop_algo(self.buy_algoid)
# Short position
elif self.spread_pos < 0:
self.stop_open_algos()
# Start cover close algo
if not self.cover_algoid:
self.cover_algoid = self.start_long_algo(
@ -126,10 +121,6 @@ class BasicSpreadStrategy(SpreadStrategyTemplate):
self.spread_pos), self.payup, self.interval
)
# Stop short open algo
if self.short_algoid:
self.stop_algo(self.short_algoid)
self.put_event()
def on_spread_pos(self):
@ -166,3 +157,19 @@ class BasicSpreadStrategy(SpreadStrategyTemplate):
Callback when new trade data is received.
"""
pass
def stop_open_algos(self):
""""""
if self.buy_algoid:
self.stop_algo(self.buy_algoid)
if self.short_algoid:
self.stop_algo(self.short_algoid)
def stop_close_algos(self):
""""""
if self.sell_algoid:
self.stop_algo(self.sell_algoid)
if self.cover_algoid:
self.stop_algo(self.cover_algoid)

View File

@ -1,14 +1,13 @@
from collections import defaultdict
from typing import Dict, List, Set
from math import floor, ceil
from copy import copy
from vnpy.trader.object import TickData, TradeData, OrderData, ContractData
from vnpy.trader.constant import Direction, Status, Offset
from vnpy.trader.utility import virtual
from vnpy.trader.utility import virtual, floor_to, ceil_to, round_to
from .base import SpreadData
from .base import SpreadData, calculate_inverse_volume
class SpreadAlgoTemplate:
@ -23,11 +22,12 @@ class SpreadAlgoTemplate:
algoid: str,
spread: SpreadData,
direction: Direction,
offset: Offset,
price: float,
volume: float,
payup: int,
interval: int,
lock: bool
lock: bool,
):
""""""
self.algo_engine = algo_engine
@ -36,6 +36,7 @@ class SpreadAlgoTemplate:
self.spread: SpreadData = spread
self.spread_name: str = spread.name
self.offset: Offset = offset
self.direction: Direction = direction
self.price: float = price
self.volume: float = volume
@ -117,10 +118,24 @@ class SpreadAlgoTemplate:
def update_trade(self, trade: TradeData):
""""""
if trade.direction == Direction.LONG:
self.leg_traded[trade.vt_symbol] += trade.volume
# For inverse contract:
# record coin trading volume as leg trading volume,
# not contract volume!
if self.spread.is_inverse(trade.vt_symbol):
size = self.spread.get_leg_size(trade.vt_symbol)
trade_volume = calculate_inverse_volume(
trade.volume,
trade.price,
size
)
else:
self.leg_traded[trade.vt_symbol] -= trade.volume
trade_volume = trade.volume
if trade.direction == Direction.LONG:
self.leg_traded[trade.vt_symbol] += trade_volume
else:
self.leg_traded[trade.vt_symbol] -= trade_volume
msg = "委托成交,{}{}{}@{}".format(
trade.vt_symbol,
@ -177,6 +192,21 @@ class SpreadAlgoTemplate:
direction: Direction,
):
""""""
# For inverse contract:
# calculate contract trading volume from coin trading volume
if self.spread.is_inverse(vt_symbol):
size = self.spread.get_leg_size(vt_symbol)
if self.offset == Offset.CLOSE:
leg = self.spread.legs[vt_symbol]
volume = volume * leg.net_pos_price / size
else:
volume = volume * price / size
# Round order volume to min_volume of contract
leg = self.spread.legs[vt_symbol]
volume = round_to(volume, leg.min_volume)
vt_orderids = self.algo_engine.send_order(
self,
vt_symbol,
@ -214,12 +244,17 @@ class SpreadAlgoTemplate:
leg_traded = self.leg_traded[leg.vt_symbol]
trading_multiplier = self.spread.trading_multipliers[
leg.vt_symbol]
adjusted_leg_traded = leg_traded / trading_multiplier
adjusted_leg_traded = round_to(
adjusted_leg_traded, self.spread.min_volume)
if adjusted_leg_traded > 0:
adjusted_leg_traded = floor(adjusted_leg_traded)
adjusted_leg_traded = floor_to(
adjusted_leg_traded, self.spread.min_volume)
else:
adjusted_leg_traded = ceil(adjusted_leg_traded)
adjusted_leg_traded = ceil_to(
adjusted_leg_traded, self.spread.min_volume)
if not n:
self.traded = adjusted_leg_traded
@ -434,7 +469,8 @@ class SpreadStrategyTemplate:
volume: float,
payup: int,
interval: int,
lock: bool
lock: bool,
offset: Offset
) -> str:
""""""
if not self.trading:
@ -444,6 +480,7 @@ class SpreadStrategyTemplate:
self,
self.spread_name,
direction,
offset,
price,
volume,
payup,
@ -461,10 +498,14 @@ class SpreadStrategyTemplate:
volume: float,
payup: int,
interval: int,
lock: bool = False
lock: bool = False,
offset: Offset = Offset.NONE
) -> str:
""""""
return self.start_algo(Direction.LONG, price, volume, payup, interval, lock)
return self.start_algo(
Direction.LONG, price, volume,
payup, interval, lock, offset
)
def start_short_algo(
self,
@ -472,10 +513,14 @@ class SpreadStrategyTemplate:
volume: float,
payup: int,
interval: int,
lock: bool = False
lock: bool = False,
offset: Offset = Offset.NONE
) -> str:
""""""
return self.start_algo(Direction.SHORT, price, volume, payup, interval, lock)
return self.start_algo(
Direction.SHORT, price, volume,
payup, interval, lock, offset
)
def stop_algo(self, algoid: str):
""""""

View File

@ -4,7 +4,7 @@ Widget for spread trading.
from vnpy.event import EventEngine, Event
from vnpy.trader.engine import MainEngine
from vnpy.trader.constant import Direction
from vnpy.trader.constant import Direction, Offset
from vnpy.trader.ui import QtWidgets, QtCore, QtGui
from vnpy.trader.ui.widget import (
BaseMonitor, BaseCell,
@ -169,6 +169,7 @@ class SpreadAlgoMonitor(BaseMonitor):
"algoid": {"display": "算法", "cell": BaseCell, "update": False},
"spread_name": {"display": "价差", "cell": BaseCell, "update": False},
"direction": {"display": "方向", "cell": DirectionCell, "update": False},
"offset": {"display": "开平", "cell": EnumCell, "update": False},
"price": {"display": "价格", "cell": BaseCell, "update": False},
"payup": {"display": "超价", "cell": BaseCell, "update": False},
"volume": {"display": "数量", "cell": BaseCell, "update": False},
@ -226,6 +227,11 @@ class SpreadAlgoWidget(QtWidgets.QFrame):
[Direction.LONG.value, Direction.SHORT.value]
)
self.offset_combo = QtWidgets.QComboBox()
self.offset_combo.addItems(
[Offset.NONE.value, Offset.OPEN.value, Offset.CLOSE.value]
)
float_validator = QtGui.QDoubleValidator()
self.price_line = QtWidgets.QLineEdit()
@ -273,6 +279,7 @@ class SpreadAlgoWidget(QtWidgets.QFrame):
form = QtWidgets.QFormLayout()
form.addRow("价差", self.name_line)
form.addRow("方向", self.direction_combo)
form.addRow("开平", self.offset_combo)
form.addRow("价格", self.price_line)
form.addRow("数量", self.volume_line)
form.addRow("超价", self.payup_line)
@ -298,6 +305,7 @@ class SpreadAlgoWidget(QtWidgets.QFrame):
""""""
name = self.name_line.text()
direction = Direction(self.direction_combo.currentText())
offset = Offset(self.offset_combo.currentText())
price = float(self.price_line.text())
volume = float(self.volume_line.text())
payup = int(self.payup_line.text())
@ -310,7 +318,7 @@ class SpreadAlgoWidget(QtWidgets.QFrame):
lock = False
self.spread_engine.start_algo(
name, direction, price, volume, payup, interval, lock
name, direction, offset, price, volume, payup, interval, lock
)
def add_spread(self):
@ -375,6 +383,17 @@ class SpreadDataDialog(QtWidgets.QDialog):
self.name_line = QtWidgets.QLineEdit()
self.active_line = QtWidgets.QLineEdit()
self.min_volume_combo = QtWidgets.QComboBox()
self.min_volume_combo.addItems([
"1",
"0.1",
"0.01",
"0.001",
"0.0001",
"0.00001",
"0.000001",
])
self.grid = QtWidgets.QGridLayout()
button_add = QtWidgets.QPushButton("创建价差")
@ -384,14 +403,17 @@ class SpreadDataDialog(QtWidgets.QDialog):
grid = QtWidgets.QGridLayout()
grid.addWidget(Label("价差名称"), 0, 0)
grid.addWidget(self.name_line, 0, 1, 1, 3)
grid.addWidget(self.name_line, 0, 1, 1, 4)
grid.addWidget(Label("主动腿代码"), 1, 0)
grid.addWidget(self.active_line, 1, 1, 1, 3)
grid.addWidget(self.active_line, 1, 1, 1, 4)
grid.addWidget(Label("最小交易量"), 2, 0)
grid.addWidget(self.min_volume_combo, 2, 1, 1, 4)
grid.addWidget(Label(""), 2, 0)
grid.addWidget(Label("本地代码"), 3, 1)
grid.addWidget(Label("价格乘数"), 3, 2)
grid.addWidget(Label("交易乘数"), 3, 3)
grid.addWidget(Label(""), 3, 0)
grid.addWidget(Label("本地代码"), 4, 1)
grid.addWidget(Label("价格乘数"), 4, 2)
grid.addWidget(Label("交易乘数"), 4, 3)
grid.addWidget(Label("合约模式"), 4, 4)
int_validator = QtGui.QIntValidator()
@ -405,20 +427,25 @@ class SpreadDataDialog(QtWidgets.QDialog):
trading_line = QtWidgets.QLineEdit()
trading_line.setValidator(int_validator)
grid.addWidget(Label("{}".format(i + 1)), 4 + i, 0)
grid.addWidget(symbol_line, 4 + i, 1)
grid.addWidget(price_line, 4 + i, 2)
grid.addWidget(trading_line, 4 + i, 3)
inverse_combo = QtWidgets.QComboBox()
inverse_combo.addItems(["正向", "反向"])
grid.addWidget(Label("{}".format(i + 1)), 5 + i, 0)
grid.addWidget(symbol_line, 5 + i, 1)
grid.addWidget(price_line, 5 + i, 2)
grid.addWidget(trading_line, 5 + i, 3)
grid.addWidget(inverse_combo, 5 + i, 4)
d = {
"symbol": symbol_line,
"price": price_line,
"trading": trading_line
"trading": trading_line,
"inverse": inverse_combo
}
self.leg_widgets.append(d)
grid.addWidget(Label(""), 4 + leg_count, 0,)
grid.addWidget(button_add, 5 + leg_count, 0, 1, 4)
grid.addWidget(Label(""), 5 + leg_count, 0,)
grid.addWidget(button_add, 6 + leg_count, 0, 1, 5)
self.setLayout(grid)
@ -435,6 +462,7 @@ class SpreadDataDialog(QtWidgets.QDialog):
return
active_symbol = self.active_line.text()
min_volume = float(self.min_volume_combo.currentText())
leg_settings = {}
for d in self.leg_widgets:
@ -443,10 +471,16 @@ class SpreadDataDialog(QtWidgets.QDialog):
price_multiplier = int(d["price"].text())
trading_multiplier = int(d["trading"].text())
if d["inverse"].currentText() == "正向":
inverse_contract = False
else:
inverse_contract = True
leg_settings[vt_symbol] = {
"vt_symbol": vt_symbol,
"price_multiplier": price_multiplier,
"trading_multiplier": trading_multiplier
"trading_multiplier": trading_multiplier,
"inverse_contract": inverse_contract
}
except ValueError:
pass
@ -472,7 +506,8 @@ class SpreadDataDialog(QtWidgets.QDialog):
self.spread_engine.add_spread(
spread_name,
list(leg_settings.values()),
active_symbol
active_symbol,
min_volume
)
self.accept()

View File

@ -107,8 +107,6 @@ class BinanceGateway(BaseGateway):
self.market_ws_api = BinanceDataWebsocketApi(self)
self.rest_api = BinanceRestApi(self)
self.event_engine.register(EVENT_TIMER, self.process_timer_event)
def connect(self, setting: dict):
""""""
key = setting["key"]
@ -121,6 +119,8 @@ class BinanceGateway(BaseGateway):
proxy_host, proxy_port)
self.market_ws_api.connect(proxy_host, proxy_port)
self.event_engine.register(EVENT_TIMER, self.process_timer_event)
def subscribe(self, req: SubscribeRequest):
""""""
self.market_ws_api.subscribe(req)

View File

@ -53,6 +53,13 @@ ORDERTYPE_VT2BITFINEX = {
OrderType.LIMIT: "EXCHANGE LIMIT",
OrderType.MARKET: "EXCHANGE MARKET",
}
ORDERTYPE_BITFINEX2VT = {
"EXCHANGE LIMIT": OrderType.LIMIT,
"EXCHANGE MARKET": OrderType.MARKET,
"LIMIT": OrderType.LIMIT,
"MARKET": OrderType.MARKET
}
DIRECTION_VT2BITFINEX = {
Direction.LONG: "Buy",
Direction.SHORT: "Sell",
@ -86,6 +93,7 @@ class BitfinexGateway(BaseGateway):
"session": 3,
"proxy_host": "127.0.0.1",
"proxy_port": 1080,
"margin": ["False", "True"]
}
exchanges = [Exchange.BITFINEX]
@ -108,8 +116,13 @@ class BitfinexGateway(BaseGateway):
proxy_host = setting["proxy_host"]
proxy_port = setting["proxy_port"]
if setting["margin"] == "True":
margin = True
else:
margin = False
self.rest_api.connect(key, secret, session, proxy_host, proxy_port)
self.ws_api.connect(key, secret, proxy_host, proxy_port)
self.ws_api.connect(key, secret, proxy_host, proxy_port, margin)
self.event_engine.register(EVENT_TIMER, self.process_timer_event)
@ -212,7 +225,7 @@ class BitfinexRestApi(RestClient):
secret: str,
session: int,
proxy_host: str,
proxy_port: int,
proxy_port: int
):
"""
Initialize connection to REST server.
@ -383,11 +396,17 @@ class BitfinexWebsocketApi(WebsocketClient):
self.subscribed = {}
def connect(
self, key: str, secret: str, proxy_host: str, proxy_port: int
self,
key: str,
secret: str,
proxy_host: str,
proxy_port: int,
margin: bool
):
""""""
self.key = key
self.secret = secret.encode()
self.margin = margin
self.init(WEBSOCKET_HOST, proxy_host, proxy_port)
self.start()
@ -432,9 +451,13 @@ class BitfinexWebsocketApi(WebsocketClient):
else:
amount = -req.volume
order_type = ORDERTYPE_VT2BITFINEX[req.type]
if self.margin:
order_type = order_type.replace("EXCHANGE ", "")
o = {
"cid": orderid,
"type": ORDERTYPE_VT2BITFINEX[req.type],
"type": order_type,
"symbol": "t" + req.symbol,
"amount": str(amount),
"price": str(req.price),
@ -609,19 +632,26 @@ class BitfinexWebsocketApi(WebsocketClient):
def on_wallet(self, data):
""""""
if str(data[0]) == "exchange":
accountid = str(data[1])
account = self.accounts.get(accountid, None)
if not account:
account = AccountData(
accountid=accountid,
gateway_name=self.gateway_name,
)
print(data)
# Exchange Mode
if not self.margin and str(data[0]) != "exchange":
return
# Margin Mode
elif self.margin and str(data[0]) != "margin":
return
account.balance = float(data[2])
account.available = 0.0
account.frozen = 0.0
self.gateway.on_account(copy(account))
accountid = str(data[1])
account = self.accounts.get(accountid, None)
if not account:
account = AccountData(
accountid=accountid,
gateway_name=self.gateway_name,
)
account.balance = float(data[2])
account.available = 0.0
account.frozen = 0.0
self.gateway.on_account(copy(account))
def on_trade_update(self, data):
""""""
@ -776,6 +806,7 @@ class BitfinexWebsocketApi(WebsocketClient):
order = OrderData(
symbol=str(data[3].replace("t", "")),
exchange=Exchange.BITFINEX,
type=ORDERTYPE_BITFINEX2VT[data[8]],
orderid=orderid,
status=Status.REJECTED,
direction=direction,
@ -808,6 +839,7 @@ class BitfinexWebsocketApi(WebsocketClient):
symbol=str(data[3].replace("t", "")),
exchange=Exchange.BITFINEX,
orderid=orderid,
type=ORDERTYPE_BITFINEX2VT[data[8]],
status=STATUS_BITFINEX2VT[order_status],
direction=direction,
price=float(data[16]),

View File

@ -533,6 +533,7 @@ class BitmexWebsocketApi(WebsocketClient):
self.ticks = {}
self.accounts = {}
self.orders = {}
self.positions = {}
self.trades = set()
def connect(
@ -702,9 +703,15 @@ class BitmexWebsocketApi(WebsocketClient):
def on_order(self, d):
""""""
# Filter order data which cannot be processed properly
if "ordStatus" not in d:
return
side = d.get("side", "")
if not side:
return
# Update local order data
sysid = d["orderID"]
order = self.orders.get(sysid, None)
if not order:
@ -713,14 +720,12 @@ class BitmexWebsocketApi(WebsocketClient):
else:
orderid = sysid
# time = d["timestamp"][11:19]
order = OrderData(
symbol=d["symbol"],
exchange=Exchange.BITMEX,
type=ORDERTYPE_BITMEX2VT[d["ordType"]],
orderid=orderid,
direction=DIRECTION_BITMEX2VT[d["side"]],
direction=DIRECTION_BITMEX2VT[side],
price=d["price"],
volume=d["orderQty"],
time=d["timestamp"][11:19],
@ -735,15 +740,27 @@ class BitmexWebsocketApi(WebsocketClient):
def on_position(self, d):
""""""
position = PositionData(
symbol=d["symbol"],
exchange=Exchange.BITMEX,
direction=Direction.NET,
volume=d.get("currentQty", 0),
gateway_name=self.gateway_name,
)
symbol = d["symbol"]
self.gateway.on_position(position)
position = self.positions.get(symbol, None)
if not position:
position = PositionData(
symbol=d["symbol"],
exchange=Exchange.BITMEX,
direction=Direction.NET,
gateway_name=self.gateway_name,
)
self.positions[symbol] = position
volume = d.get("currentQty", None)
if volume is not None:
position.volume = volume
price = d.get("avgEntryPrice", None)
if price is not None:
position.price = price
self.gateway.on_position(copy(position))
def on_account(self, d):
""""""

View File

@ -198,6 +198,7 @@ class CoinbaseWebsocketApi(WebsocketClient):
}
self.orderbooks = {}
self.subscribed = {}
def connect(
self,
@ -223,6 +224,10 @@ class CoinbaseWebsocketApi(WebsocketClient):
def subscribe(self, req: SubscribeRequest):
""""""
self.subscribed[req.symbol] = req
if not self._active:
return
symbol = req.symbol
exchange = req.exchange
@ -259,6 +264,9 @@ class CoinbaseWebsocketApi(WebsocketClient):
"""
self.gateway.write_log("Websocket API连接成功")
for req in self.subscribed.values():
self.subscribe(req)
def on_disconnected(self):
""""""
self.gateway.write_log("Websocket API连接断开")

View File

@ -59,6 +59,8 @@ STATUS_HBDM2VT = {
ORDERTYPE_VT2HBDM = {
OrderType.MARKET: "opponent",
OrderType.LIMIT: "limit",
OrderType.FOK: "fok",
OrderType.FAK: "ioc"
}
ORDERTYPE_HBDM2VT = {v: k for k, v in ORDERTYPE_VT2HBDM.items()}
ORDERTYPE_HBDM2VT[1] = OrderType.LIMIT
@ -284,41 +286,7 @@ class HbdmRestApi(RestClient):
self.add_request(
method="POST",
path="/api/v1/contract_openorders",
callback=self.on_query_active_order,
data=data,
extra=currency
)
# History Orders
data = {
"symbol": currency,
"trade_type": 0,
"type": 2,
"status": 0,
"create_date": 7
}
self.add_request(
method="POST",
path="/api/v1/contract_hisorders",
callback=self.on_query_history_order,
data=data,
extra=currency
)
def query_trade(self):
""""""
for currency in self.currencies:
data = {
"symbol": currency,
"trade_type": 0,
"create_date": 7
}
self.add_request(
method="POST",
path="/api/v1/contract_matchresults",
callback=self.on_query_trade,
callback=self.on_query_order,
data=data,
extra=currency
)
@ -549,7 +517,7 @@ class HbdmRestApi(RestClient):
for position in self.positions.values():
self.gateway.on_position(position)
def on_query_active_order(self, data, request):
def on_query_order(self, data, request):
""""""
if self.check_error(data, "查询活动委托"):
return
@ -582,61 +550,6 @@ class HbdmRestApi(RestClient):
self.gateway.write_log(f"{request.extra}活动委托信息查询成功")
def on_query_history_order(self, data, request):
""""""
if self.check_error(data, "查询历史委托"):
return
for d in data["data"]["orders"]:
timestamp = d["create_date"]
dt = datetime.fromtimestamp(timestamp / 1000)
time = dt.strftime("%H:%M:%S")
orderid = d["order_id"]
order = OrderData(
orderid=orderid,
symbol=d["contract_code"],
exchange=Exchange.HUOBI,
price=d["price"],
volume=d["volume"],
type=ORDERTYPE_HBDM2VT[d["order_price_type"]],
direction=DIRECTION_HBDM2VT[d["direction"]],
offset=OFFSET_HBDM2VT[d["offset"]],
traded=d["trade_volume"],
status=STATUS_HBDM2VT[d["status"]],
time=time,
gateway_name=self.gateway_name,
)
self.gateway.on_order(order)
self.gateway.write_log(f"{request.extra}历史委托信息查询成功")
def on_query_trade(self, data, request):
""""""
if self.check_error(data, "查询成交"):
return
for d in data["data"]["trades"]:
dt = datetime.fromtimestamp(d["create_date"] / 1000)
time = dt.strftime("%H:%M:%S")
trade = TradeData(
tradeid=d["match_id"],
orderid=d["order_id"],
symbol=d["contract_code"],
exchange=Exchange.HUOBI,
price=d["trade_price"],
volume=d["trade_volume"],
direction=DIRECTION_HBDM2VT[d["direction"]],
offset=OFFSET_HBDM2VT[d["offset"]],
time=time,
gateway_name=self.gateway_name,
)
self.gateway.on_trade(trade)
self.gateway.write_log(f"{request.extra}成交信息查询成功")
def on_query_contract(self, data, request): # type: (dict, Request)->None
""""""
if self.check_error(data, "查询合约"):
@ -663,7 +576,6 @@ class HbdmRestApi(RestClient):
self.gateway.write_log("合约信息查询成功")
self.query_order()
self.query_trade()
def on_send_order(self, data, request):
""""""
@ -1041,7 +953,6 @@ class HbdmDataWebsocketApi(HbdmWebsocketApiBase):
tick_data = data["tick"]
if "bids" not in tick_data or "asks" not in tick_data:
print(data)
return
bids = tick_data["bids"]

View File

@ -278,6 +278,7 @@ class PositionHolding:
# If no td_volume, we close opposite yd position first
# then open new position
else:
close_volume = min(req.volume, yd_available)
open_volume = max(0, req.volume - yd_available)
req_list = []
@ -287,6 +288,7 @@ class PositionHolding:
req_yd.offset = Offset.CLOSEYESTERDAY
else:
req_yd.offset = Offset.CLOSE
req_yd.volume = close_volume
req_list.append(req_yd)
if open_volume:

View File

@ -53,7 +53,7 @@ class RqdataClient:
self.username,
self.password,
('rqdatad-pro.ricequant.com', 16011),
use_pool=True
use_pool=True,
)
try:

View File

@ -463,7 +463,7 @@ class PositionMonitor(BaseMonitor):
"volume": {"display": "数量", "cell": BaseCell, "update": True},
"yd_volume": {"display": "昨仓", "cell": BaseCell, "update": True},
"frozen": {"display": "冻结", "cell": BaseCell, "update": True},
"price": {"display": "均价", "cell": BaseCell, "update": False},
"price": {"display": "均价", "cell": BaseCell, "update": True},
"pnl": {"display": "盈亏", "cell": PnlCell, "update": True},
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
}

View File

@ -7,6 +7,7 @@ import logging
from pathlib import Path
from typing import Callable, Dict
from decimal import Decimal
from math import floor, ceil
import numpy as np
import talib
@ -124,6 +125,26 @@ def round_to(value: float, target: float) -> float:
return rounded
def floor_to(value: float, target: float) -> float:
"""
Similar to math.floor function, but to target float number.
"""
value = Decimal(str(value))
target = Decimal(str(target))
result = float(int(floor(value / target)) * target)
return result
def ceil_to(value: float, target: float) -> float:
"""
Similar to math.ceil function, but to target float number.
"""
value = Decimal(str(value))
target = Decimal(str(target))
result = float(int(ceil(value / target)) * target)
return result
class BarGenerator:
"""
For: