commit
2e01bef96f
@ -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)
|
||||
|
@ -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) {};
|
||||
|
||||
|
@ -1976,6 +1976,11 @@ typedef char TThostFtdcCommentType[31];
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
typedef char TThostFtdcVersionType[4];
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
/////UtpCipherVersionType是一个版本信息类型
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
typedef char UtpCipherVersionType[65];
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///TFtdcTradeCodeType是一个交易代码类型
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
@ -6410,7 +6415,7 @@ typedef char TThostFtdcClientSystemInfoType[273];
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///TThostFtdcAppIDType是App代码类型
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
typedef char TThostFtdcClientAppIDType[21];
|
||||
typedef char TThostFtdcClientAppIDType[33];
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
///TThostFtdcAutoCodeType是AutoCode代码类型
|
||||
|
@ -112,7 +112,7 @@ struct CThostFtdcReqAuthenticateField
|
||||
TThostFtdcProductInfoType UserProductInfo;
|
||||
///认证码
|
||||
TThostFtdcAuthCodeType AuthCode;
|
||||
///App代码
|
||||
// App代码
|
||||
TThostFtdcClientAppIDType AppID;
|
||||
};
|
||||
|
||||
@ -125,7 +125,7 @@ struct CThostFtdcRspAuthenticateField
|
||||
TThostFtdcUserIDType UserID;
|
||||
///用户端产品信息
|
||||
TThostFtdcProductInfoType UserProductInfo;
|
||||
///App代码
|
||||
// App代码
|
||||
TThostFtdcClientAppIDType AppID;
|
||||
};
|
||||
|
||||
@ -144,8 +144,10 @@ struct CThostFtdcAuthenticationInfoField
|
||||
TThostFtdcAuthInfoType AuthInfo;
|
||||
///是否为认证结果
|
||||
TThostFtdcBoolType IsResult;
|
||||
///App代码
|
||||
// App代码
|
||||
TThostFtdcClientAppIDType AppID;
|
||||
// 版本信息
|
||||
UtpCipherVersionType VerInfo;
|
||||
};
|
||||
|
||||
///银期转帐报文头
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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],)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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,6 +71,40 @@ class LegData:
|
||||
|
||||
def update_trade(self, trade: TradeData):
|
||||
""""""
|
||||
# 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
|
||||
|
||||
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
|
||||
@ -66,7 +116,7 @@ class LegData:
|
||||
else:
|
||||
self.long_pos -= trade.volume
|
||||
|
||||
self.net_pos = self.long_pos - self.net_pos
|
||||
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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
""""""
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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,7 +632,14 @@ class BitfinexWebsocketApi(WebsocketClient):
|
||||
|
||||
def on_wallet(self, data):
|
||||
""""""
|
||||
if str(data[0]) == "exchange":
|
||||
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
|
||||
|
||||
accountid = str(data[1])
|
||||
account = self.accounts.get(accountid, None)
|
||||
if not account:
|
||||
@ -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]),
|
||||
|
@ -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):
|
||||
""""""
|
||||
symbol = d["symbol"]
|
||||
|
||||
position = self.positions.get(symbol, None)
|
||||
if not position:
|
||||
position = PositionData(
|
||||
symbol=d["symbol"],
|
||||
exchange=Exchange.BITMEX,
|
||||
direction=Direction.NET,
|
||||
volume=d.get("currentQty", 0),
|
||||
gateway_name=self.gateway_name,
|
||||
)
|
||||
self.positions[symbol] = position
|
||||
|
||||
self.gateway.on_position(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):
|
||||
""""""
|
||||
|
@ -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连接断开")
|
||||
|
@ -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"]
|
||||
|
@ -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:
|
||||
|
@ -53,7 +53,7 @@ class RqdataClient:
|
||||
self.username,
|
||||
self.password,
|
||||
('rqdatad-pro.ricequant.com', 16011),
|
||||
use_pool=True
|
||||
use_pool=True,
|
||||
)
|
||||
|
||||
try:
|
||||
|
@ -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},
|
||||
}
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user