jingsiaosing wrote:
这样的话第一个20:59:00的tick接收到后 会初始化一个时间戳为21:00:00的bar,一分钟之后连续竞价交易过来的tick的分钟与其相同,就不会推bar,直到这一分钟走完。
一个交易所可能有多个柜台连接,比如CTP柜台、恒牛柜台、金仕达柜台,不管来自哪个柜台,是不是报单编号和成交编号在一个交易 日内,在交易所内是唯一的吗?
报单编号是唯一的, 但是成交编号是不唯一的,因为撮合的双方,成交编号是一致的。也就是说一个报单可能在撮合过程中被拆分为多个成交单,交易所赋予成交单的相同的成交编号会被推送给多空双方,同时还包含了个不相同的报单编号。
另外,因为自成交情况存在,所以在写程序时需要注意,用成交方向+成交编号才能确定笔成交记录。
naive wrote:
def send_buy_orders(self, price):
""""""
t = self.pos / self.fixed_sizeif t < 1: self.buy(price, self.fixed_size, True) if t < 2: self.buy(price + self.atr_value * 0.5, self.fixed_size, True) if t < 3: self.buy(price + self.atr_value, self.fixed_size, True) if t < 4: self.buy(price + self.atr_value * 1.5, self.fixed_size, True)
在学习海龟交易策略的时候看到源码里的这个代码,如果t < 1的话,是不是会调用四次 self.buy() 函数呀?
那个意思是说:
(每份仓是self.fixed_size手),P=price, ATR= self.atr_value
如果已经持有0份仓,就下4份停止单;价格分别为(P+0ATR,P+1ATR,P+2ATR,P+3ATR),
如果已经持有1份仓,就下3份停止单,价格分别为(P+1ATR,P+2ATR,P+3ATR);,
如果已经持有2份仓,就下2份停止单,价格分别为(P+2ATR,P+3ATR);
如果已经持有3份仓,就下1份停止单,价格为(P+3ATR);
如果已经持有4份仓,就不下停止单了;
修改vnpy\trader\event.py,添加如下内容:
EVENT_ORIGIN_TICK = "eOriginTick." # 原始tick消息
EVENT_AUCTION_TICK = "eAuctionTick." # 集合竞价tick消息
EVENT_STATUS = "eStatus." # 交易状态消息
EVENT_STATUS_END = "eStatusEnd." # 交易状态结束消息
修改vnpy\trader\constant.py,添加如下内容:
class InstrumentStatus(Enum):
"""
合约交易状态类型 hxxjava debug
"""
BEFORE_TRADING = "开盘前"
NO_TRADING = "非交易"
CONTINOUS = "连续交易"
AUCTION_ORDERING = "集合竞价报单"
AUCTION_BALANCE = "集合竞价价格平衡"
AUCTION_MATCH = "集合竞价撮合"
CLOSE = "收盘"
# 有效交易状态
VALID_TRADE_STATUSES = [
InstrumentStatus.CONTINOUS,
InstrumentStatus.AUCTION_ORDERING,
InstrumentStatus.AUCTION_BALANCE,
InstrumentStatus.AUCTION_MATCH
]
# 集合竞价交易状态
AUCTION_STATUS = [
InstrumentStatus.AUCTION_ORDERING,
InstrumentStatus.AUCTION_BALANCE,
InstrumentStatus.AUCTION_MATCH
]
class StatusEnterReason(Enum):
"""
品种进入交易状态原因类型 hxxjava debug
"""
AUTOMATIC = "自动切换"
MANUAL = "手动切换"
FUSE = "熔断"
修改vnpy\trader\object.py,添加如下内容:
3.1 在文件的前面添加这样的内容:
from .constant import InstrumentStatus,StatusEnterReason
3.2 在文件的后面添加下面的内容:
def left_alphas(instr:str):
""" get lefe alphas of a string """
ret_str = ''
for s in instr:
if s.isalpha():
ret_str += s
else:
break
return ret_str
@dataclass
class StatusData(BaseData):
"""
hxxjava debug
"""
symbol:str
exchange : Exchange
settlement_group_id : str = ""
instrument_status : InstrumentStatus = None
trading_segment_sn : int = None
enter_time : str = ""
enter_reason : StatusEnterReason = StatusEnterReason.AUTOMATIC
exchange_inst_id : str = ""
def __post_init__(self):
""" """
self.vt_symbol = f"{self.symbol}.{self.exchange.value}"
def belongs_to(self,vt_symbol:str):
symbol,exchange_str = vt_symbol.split(".")
instrument = left_alphas(symbol).upper()
return (self.symbol.upper() == instrument) and (self.exchange.value == exchange_str)
修改vnpy\trader\gateway.py,添加下面内容:
添加引用部分
from .event import EVENT_ORIGIN_TICK,EVENT_STATUS, EVENT_STATUS_END
from .object import StatusData, # hxxjava debug
这样修改on_tick():
def on_tick(self, tick: TickData) -> None:
"""
Tick event push.
Tick event of a specific vt_symbol is also pushed.
"""
# self.on_event(EVENT_TICK, tick)
# self.on_event(EVENT_TICK + tick.vt_symbol, tick)
self.on_event(EVENT_ORIGIN_TICK, tick)
添加下面的两个函数:
def on_status(self, status: StatusData) -> None: # hxxjava debug
"""
Instrument Status event push.
"""
self.on_event(EVENT_STATUS, status)
self.on_event(EVENT_STATUS + status.vt_symbol, status)
def on_status_end(self, stats: List[StatusData]) -> None: # hxxjava debug
"""
Instrument Status list event push.
"""
self.on_event(EVENT_STATUS_END, stats)
修改vnpy_ctp\gateway\ctp_gateway.py,步骤如下:
from vnpy.trader.constant import InstrumentStatus,StatusEnterReason
from vnpy.trader.object import StatusData, # hxxjava debug
def __init__(self, gateway: CtpGateway) -> None:
"""构造函数"""
super().__init__()
self.gateway: CtpGateway = gateway
self.gateway_name: str = gateway.gateway_name
self.reqid: int = 0
self.order_ref: int = 0
self.connect_status: bool = False
self.login_status: bool = False
self.auth_status: bool = False
self.login_failed: bool = False
self.contract_inited: bool = False
self.userid: str = ""
self.password: str = ""
self.brokerid: str = ""
self.auth_code: str = ""
self.appid: str = ""
self.frontid: int = 0
self.sessionid: int = 0
self.inited = False # hxxjava add
self.status_data: List[dict] = [] # hxxjava add
self.order_data: List[dict] = []
self.trade_data: List[dict] = []
self.positions: Dict[str, PositionData] = {}
self.sysid_orderid_map: Dict[str, str] = {}
添加下面的两个函数:
def onRtnInstrumentStatus(self,data:dict):
"""
当接收到合约品种状态信息 # hxxjava debug
"""
if not self.contract_inited:
self.status_data.append(data)
return
status = self.extractInstrumentStatus(data)
self.gateway.on_status(status)
def extractInstrumentStatus(self,data:dict): # hxxjava add
""" 提取合约品种状态信息 """
return StatusData(
symbol = data["InstrumentID"],
exchange = EXCHANGE_CTP2VT[data["ExchangeID"]],
settlement_group_id = data["SettlementGroupID"],
instrument_status = INSTRUMENTSTATUS_CTP2VT[data["InstrumentStatus"]],
trading_segment_sn = data["TradingSegmentSN"],
enter_time = data["EnterTime"],
enter_reason = ENTERREASON_CTP2VT[data["EnterReason"]],
exchange_inst_id = data["ExchangeInstID"],
gateway_name=self.gateway_name
)
def onRspQryInstrument(self, data: dict, error: dict, reqid: int, last: bool) -> None:
"""合约查询回报"""
product: Product = PRODUCT_CTP2VT.get(data["ProductClass"], None)
if product:
contract: ContractData = ContractData(
symbol=data["InstrumentID"],
exchange=EXCHANGE_CTP2VT[data["ExchangeID"]],
name=data["InstrumentName"],
product=product,
size=data["VolumeMultiple"],
pricetick=data["PriceTick"],
# hxxjava add start
max_market_order_volume=data["MaxMarketOrderVolume"],
min_market_order_volume=data["MinMarketOrderVolume"],
max_limit_order_volume=data["MaxLimitOrderVolume"],
min_limit_order_volume=data["MinLimitOrderVolume"],
open_date=data["OpenDate"],
expire_date=data["ExpireDate"],
is_trading=data["IsTrading"],
long_margin_ratio=data["LongMarginRatio"],
short_margin_ratio=data["ShortMarginRatio"],
# hxxjava add end
gateway_name=self.gateway_name
)
# 期权相关
if contract.product == Product.OPTION:
# 移除郑商所期权产品名称带有的C/P后缀
if contract.exchange == Exchange.CZCE:
contract.option_portfolio = data["ProductID"][:-1]
else:
contract.option_portfolio = data["ProductID"]
contract.option_underlying = data["UnderlyingInstrID"]
contract.option_type = OPTIONTYPE_CTP2VT.get(data["OptionsType"], None)
contract.option_strike = data["StrikePrice"]
contract.option_index = str(data["StrikePrice"])
contract.option_expiry = datetime.strptime(data["ExpireDate"], "%Y%m%d")
self.gateway.on_contract(contract)
symbol_contract_map[contract.symbol] = contract
if last:
self.contract_inited = True
self.gateway.write_log("合约信息查询成功")
# self.gateway.write_log(f"收到{len(symbol_contract_map)}条合约信息")
self.gateway.write_log(f"提取{len(self.status_data)}条状态信息")
if self.status_data:
statuses = []
for data in self.status_data:
statuses.append(self.extractInstrumentStatus(data))
self.gateway.on_status_end(statuses)
self.status_data.clear()
# self.gateway.write_log(f"提取{len(self.order_data)}条委托单信息")
for data in self.order_data:
self.onRtnOrder(data)
self.order_data.clear()
# self.gateway.write_log(f"提取{len(self.trade_data)}条成交单信息")
for data in self.trade_data:
self.onRtnTrade(data)
self.trade_data.clear()
self.inited = True
修改vnpy\trader\engine.py,OmsEngine的代码如下:
class OmsEngine(BaseEngine):
"""
Provides order management system function for VN Trader.
"""
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
""""""
super(OmsEngine, self).__init__(main_engine, event_engine, "oms")
self.ticks: Dict[str, TickData] = {}
self.orders: Dict[str, OrderData] = {}
self.trades: Dict[str, TradeData] = {}
self.positions: Dict[str, PositionData] = {}
self.accounts: Dict[str, AccountData] = {}
self.contracts: Dict[str, ContractData] = {}
self.quotes: Dict[str, QuoteData] = {}
self.active_orders: Dict[str, OrderData] = {}
self.active_quotes: Dict[str, QuoteData] = {}
self.auction_ticks: Dict[str, List[TickData]] = {} # hxxjava 集合竞价tick字典,每个品种一个列表
self.statuses:Dict[str,StatusData] = {} # hxxjava add
self.add_function()
self.register_event()
def add_function(self) -> None:
"""Add query function to main engine."""
self.main_engine.get_tick = self.get_tick
self.main_engine.get_order = self.get_order
self.main_engine.get_trade = self.get_trade
self.main_engine.get_position = self.get_position
self.main_engine.get_account = self.get_account
self.main_engine.get_contract = self.get_contract
self.main_engine.get_quote = self.get_quote
self.main_engine.get_all_ticks = self.get_all_ticks
self.main_engine.get_all_orders = self.get_all_orders
self.main_engine.get_all_trades = self.get_all_trades
self.main_engine.get_all_positions = self.get_all_positions
self.main_engine.get_all_accounts = self.get_all_accounts
self.main_engine.get_all_contracts = self.get_all_contracts
self.main_engine.get_all_quotes = self.get_all_quotes
self.main_engine.get_all_active_orders = self.get_all_active_orders
self.main_engine.get_all_active_qutoes = self.get_all_active_quotes
self.main_engine.get_status = self.get_status # hxxjava add
def register_event(self) -> None:
""""""
self.event_engine.register(EVENT_TICK, self.process_tick_event)
self.event_engine.register(EVENT_ORDER, self.process_order_event)
self.event_engine.register(EVENT_TRADE, self.process_trade_event)
self.event_engine.register(EVENT_POSITION, self.process_position_event)
self.event_engine.register(EVENT_ACCOUNT, self.process_account_event)
self.event_engine.register(EVENT_CONTRACT, self.process_contract_event)
self.event_engine.register(EVENT_QUOTE, self.process_quote_event)
self.event_engine.register(EVENT_ORIGIN_TICK, self.process_origin_tick_event) # hxxjava add
self.event_engine.register(EVENT_STATUS, self.process_status_event) # hxxjava add
self.event_engine.register(EVENT_STATUS_END, self.process_status_end) # hxxjava add
def process_origin_tick_event(self, event: Event) -> None:
""""""
tick: TickData = event.data
# 得到tick合约的当前交易状态
vt_symbol = get_vt_instrument(tick.vt_symbol)
status = self.statuses.get(vt_symbol,None)
if status:
if status.instrument_status in AUCTION_STATUS:
# 收到了集合竞价时段tick
# 保存集合竞价tick到品种列表
if vt_symbol not in self.auction_ticks:
self.auction_ticks[vt_symbol] = []
self.auction_ticks[vt_symbol].append(tick)
# 发出集合竞价tick消息
self.event_engine.put(Event(EVENT_AUCTION_TICK,tick))
print(f"集合竞价状态={status} 收到tick={tick}")
elif status.instrument_status == InstrumentStatus.CONTINOUS:
# 其他时段的tick
self.event_engine.put(Event(EVENT_TICK,tick))
self.event_engine.put(Event(EVENT_TICK + tick.vt_symbol,tick))
else:
# 按说应该不存在这种情况
self.event_engine.put(Event(EVENT_TICK,tick))
self.event_engine.put(Event(EVENT_TICK + tick.vt_symbol,tick))
def process_tick_event(self, event: Event) -> None:
""""""
tick: TickData = event.data
self.ticks[tick.vt_symbol] = tick
def process_order_event(self, event: Event) -> None:
""""""
order: OrderData = event.data
self.orders[order.vt_orderid] = order
def process_trade_event(self, event: Event) -> None:
""""""
trade: TradeData = event.data
self.trades[trade.vt_tradeid] = trade
print(f"process_trade_event:{trade}")
def process_position_event(self, event: Event) -> None:
""""""
position: PositionData = event.data
self.positions[position.vt_positionid] = position
def process_account_event(self, event: Event) -> None:
""""""
account: AccountData = event.data
self.accounts[account.vt_accountid] = account
def process_contract_event(self, event: Event) -> None:
""""""
contract: ContractData = event.data
self.contracts[contract.vt_symbol] = contract
def process_status_event(self, event: Event) -> None: # hxxjava add
""" 交易状态通知消息处理 """
status:StatusData = event.data
# print(f"process_status_event {status}")
vt_symbol = status.vt_symbol
pre_status = self.statuses.get(vt_symbol,None)
self.statuses[vt_symbol] = status
if pre_status and pre_status.instrument_status in AUCTION_STATUS \
and status.instrument_status == InstrumentStatus.CONTINOUS:
# 当从状态为集合竞价状态进入连续竞价状态之时,
# 把所有集合竞价的tick时间变换为连续竞价开始,
# 然后重新把tick发送到系统的信息循环之中
ticks = self.auction_ticks.get(vt_symbol,[])
for t in ticks:
tick:TickData = copy(t)
hh,mm,ss = status.enter_time.split(':')
tick0 = copy(tick)
tick.datetime = tick.datetime.replace(hour=int(hh),minute=int(mm),second=int(ss),microsecond=0)
print(f"集合竞价{tick0} 集合竞价后{tick}")
self.event_engine.put(Event(EVENT_TICK,tick))
self.ticks.clear()
def process_status_end(self, event: Event) -> None: # hxxjava add
""" 交易状态通知消息处理 """
statuses:List[StatusData] = event.data
for status in statuses:
self.statuses[status.vt_symbol] = status
print(f"status:{status}")
def process_quote_event(self, event: Event) -> None:
""""""
quote: QuoteData = event.data
self.quotes[quote.vt_quoteid] = quote
# If quote is active, then update data in dict.
if quote.is_active():
self.active_quotes[quote.vt_quoteid] = quote
# Otherwise, pop inactive quote from in dict
elif quote.vt_quoteid in self.active_quotes:
self.active_quotes.pop(quote.vt_quoteid)
def get_tick(self, vt_symbol: str) -> Optional[TickData]:
"""
Get latest market tick data by vt_symbol.
"""
return self.ticks.get(vt_symbol, None)
def get_order(self, vt_orderid: str) -> Optional[OrderData]:
"""
Get latest order data by vt_orderid.
"""
return self.orders.get(vt_orderid, None)
def get_trade(self, vt_tradeid: str) -> Optional[TradeData]:
"""
Get trade data by vt_tradeid.
"""
return self.trades.get(vt_tradeid, None)
def get_position(self, vt_positionid: str) -> Optional[PositionData]:
"""
Get latest position data by vt_positionid.
"""
return self.positions.get(vt_positionid, None)
def get_account(self, vt_accountid: str) -> Optional[AccountData]:
"""
Get latest account data by vt_accountid.
"""
return self.accounts.get(vt_accountid, None)
def get_contract(self, vt_symbol: str) -> Optional[ContractData]:
"""
Get contract data by vt_symbol.
"""
return self.contracts.get(vt_symbol, None)
def get_quote(self, vt_quoteid: str) -> Optional[QuoteData]:
"""
Get latest quote data by vt_orderid.
"""
return self.quotes.get(vt_quoteid, None)
def get_all_ticks(self) -> List[TickData]:
"""
Get all tick data.
"""
return list(self.ticks.values())
def get_all_orders(self) -> List[OrderData]:
"""
Get all order data.
"""
return list(self.orders.values())
def get_all_trades(self) -> List[TradeData]:
"""
Get all trade data.
"""
return list(self.trades.values())
def get_all_positions(self) -> List[PositionData]:
"""
Get all position data.
"""
return list(self.positions.values())
def get_all_accounts(self) -> List[AccountData]:
"""
Get all account data.
"""
return list(self.accounts.values())
def get_all_contracts(self) -> List[ContractData]:
"""
Get all contract data.
"""
return list(self.contracts.values())
def get_all_quotes(self) -> List[QuoteData]:
"""
Get all quote data.
"""
return list(self.quotes.values())
def get_status(self,vt_symbol:str) -> Optional[StatusData]: # hxxjava add
"""
Get the vt_symbol's status data.
"""
return self.statuses.get(vt_symbol,None)
def get_all_statuses(self) -> List[StatusData]: # hxxjava add
"""
Get the vt_symbol's status data.
"""
return self.statuses.values()
def get_all_active_orders(self, vt_symbol: str = "") -> List[OrderData]:
"""
Get all active orders by vt_symbol.
If vt_symbol is empty, return all active orders.
"""
if not vt_symbol:
return list(self.active_orders.values())
else:
active_orders = [
order
for order in self.active_orders.values()
if order.vt_symbol == vt_symbol
]
return active_orders
def get_all_active_quotes(self, vt_symbol: str = "") -> List[QuoteData]:
"""
Get all active quotes by vt_symbol.
If vt_symbol is empty, return all active qutoes.
"""
if not vt_symbol:
return list(self.active_quotes.values())
else:
active_quotes = [
quote
for quote in self.active_quotes.values()
if quote.vt_symbol == vt_symbol
]
return active_quotes
修改vnpy\trader\ui\widget.py,内容如下:
"""
Basic widgets for VN Trader.
"""
import csv
import platform
from enum import Enum
from typing import Any, Dict
from copy import copy
from tzlocal import get_localzone
from PyQt5 import QtCore, QtGui, QtWidgets, Qt
import importlib_metadata
import vnpy
from vnpy.event import Event, EventEngine
from ..constant import Direction, Exchange, Offset, OrderType
from ..engine import MainEngine
from ..event import (
EVENT_QUOTE,
EVENT_AUCTION_TICK,
EVENT_TICK,
EVENT_TRADE,
EVENT_ORDER,
EVENT_POSITION,
EVENT_ACCOUNT,
EVENT_STRATEGY_ACCOUNT, # hxxjava
EVENT_LOG
)
from ..object import OrderRequest, SubscribeRequest, PositionData
from ..utility import load_json, save_json, get_digits
from ..setting import SETTING_FILENAME, SETTINGS
COLOR_LONG = QtGui.QColor("red")
COLOR_SHORT = QtGui.QColor("green")
COLOR_BID = QtGui.QColor(255, 174, 201)
COLOR_ASK = QtGui.QColor(160, 255, 160)
COLOR_BLACK = QtGui.QColor("black")
class BaseCell(QtWidgets.QTableWidgetItem):
"""
General cell used in tablewidgets.
"""
def __init__(self, content: Any, data: Any):
""""""
super(BaseCell, self).__init__()
self.setTextAlignment(QtCore.Qt.AlignCenter)
self.set_content(content, data)
def set_content(self, content: Any, data: Any) -> None:
"""
Set text content.
"""
self.setText(str(content))
self._data = data
def get_data(self) -> Any:
"""
Get data object.
"""
return self._data
class EnumCell(BaseCell):
"""
Cell used for showing enum data.
"""
def __init__(self, content: str, data: Any):
""""""
super(EnumCell, self).__init__(content, data)
def set_content(self, content: Any, data: Any) -> None:
"""
Set text using enum.constant.value.
"""
if content:
super(EnumCell, self).set_content(content.value, data)
class DirectionCell(EnumCell):
"""
Cell used for showing direction data.
"""
def __init__(self, content: str, data: Any):
""""""
super(DirectionCell, self).__init__(content, data)
def set_content(self, content: Any, data: Any) -> None:
"""
Cell color is set according to direction.
"""
super(DirectionCell, self).set_content(content, data)
if content is Direction.SHORT:
self.setForeground(COLOR_SHORT)
else:
self.setForeground(COLOR_LONG)
class BidCell(BaseCell):
"""
Cell used for showing bid price and volume.
"""
def __init__(self, content: Any, data: Any):
""""""
super(BidCell, self).__init__(content, data)
self.setForeground(COLOR_BID)
class AskCell(BaseCell):
"""
Cell used for showing ask price and volume.
"""
def __init__(self, content: Any, data: Any):
""""""
super(AskCell, self).__init__(content, data)
self.setForeground(COLOR_ASK)
class PnlCell(BaseCell):
"""
Cell used for showing pnl data.
"""
def __init__(self, content: Any, data: Any):
""""""
super(PnlCell, self).__init__(content, data)
def set_content(self, content: Any, data: Any) -> None:
"""
Cell color is set based on whether pnl is
positive or negative.
"""
super(PnlCell, self).set_content(content, data)
if str(content).startswith("-"):
self.setForeground(COLOR_SHORT)
else:
self.setForeground(COLOR_LONG)
class DateTimeCell(BaseCell):
"""
Cell used for showing time string from datetime object.
"""
local_tz = get_localzone()
def __init__(self, content: Any, data: Any):
""""""
super(DateTimeCell, self).__init__(content, data)
def set_content(self, content: Any, data: Any) -> None:
""""""
if content is None:
return
content = content.astimezone(self.local_tz)
timestamp = content.strftime("%Y-%m-%d %H:%M:%S")
millisecond = int(content.microsecond / 1000)
if millisecond:
timestamp = f"{timestamp}.{millisecond}"
self.setText(timestamp)
self._data = data
class TimeCell(BaseCell):
"""
Cell used for showing time string from datetime object.
"""
local_tz = get_localzone()
def __init__(self, content: Any, data: Any):
""""""
super(TimeCell, self).__init__(content, data)
def set_content(self, content: Any, data: Any) -> None:
""""""
if content is None:
return
content = content.astimezone(self.local_tz)
timestamp = content.strftime("%H:%M:%S")
millisecond = int(content.microsecond / 1000)
if millisecond:
timestamp = f"{timestamp}.{millisecond}"
else:
timestamp = f"{timestamp}.000"
self.setText(timestamp)
self._data = data
class MsgCell(BaseCell):
"""
Cell used for showing msg data.
"""
def __init__(self, content: str, data: Any):
""""""
super(MsgCell, self).__init__(content, data)
self.setTextAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
class BaseMonitor(QtWidgets.QTableWidget):
"""
Monitor data update in VN Trader.
"""
event_type: str = ""
data_key: str = ""
sorting: bool = False
headers: Dict[str, dict] = {}
signal: QtCore.pyqtSignal = QtCore.pyqtSignal(Event)
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
""""""
super(BaseMonitor, self).__init__()
self.main_engine: MainEngine = main_engine
self.event_engine: EventEngine = event_engine
self.cells: Dict[str, dict] = {}
self.init_ui()
self.load_setting()
self.register_event()
def __del__(self) -> None:
""""""
self.save_setting()
def init_ui(self) -> None:
""""""
self.init_table()
self.init_menu()
def init_table(self) -> None:
"""
Initialize table.
"""
self.setColumnCount(len(self.headers))
labels = [d["display"] for d in self.headers.values()]
self.setHorizontalHeaderLabels(labels)
self.verticalHeader().setVisible(False)
self.setEditTriggers(self.NoEditTriggers)
self.setAlternatingRowColors(True)
self.setSortingEnabled(self.sorting)
def init_menu(self) -> None:
"""
Create right click menu.
"""
self.menu = QtWidgets.QMenu(self)
resize_action = QtWidgets.QAction("调整列宽", self)
resize_action.triggered.connect(self.resize_columns)
self.menu.addAction(resize_action)
save_action = QtWidgets.QAction("保存数据", self)
save_action.triggered.connect(self.save_csv)
self.menu.addAction(save_action)
def register_event(self) -> None:
"""
Register event handler into event engine.
"""
if self.event_type:
self.signal.connect(self.process_event)
if type(self.event_type) == list: # hxxjava debug
for ev in self.event_type:
self.event_engine.register(ev, self.signal.emit)
# print(f"已经订阅 {ev} 消息")
else:
self.event_engine.register(self.event_type, self.signal.emit)
def process_event(self, event: Event) -> None:
"""
Process new data from event and update into table.
"""
# Disable sorting to prevent unwanted error.
if self.sorting:
self.setSortingEnabled(False)
# Update data into table.
data = event.data
if not self.data_key:
self.insert_new_row(data)
else:
key = data.__getattribute__(self.data_key)
if key in self.cells:
self.update_old_row(data)
else:
self.insert_new_row(data)
# Enable sorting
if self.sorting:
self.setSortingEnabled(True)
def insert_new_row(self, data: Any):
"""
Insert a new row at the top of table.
"""
self.insertRow(0)
row_cells = {}
for column, header in enumerate(self.headers.keys()):
setting = self.headers[header]
content = data.__getattribute__(header)
cell = setting["cell"](content, data)
self.setItem(0, column, cell)
if setting["update"]:
row_cells[header] = cell
if self.data_key:
key = data.__getattribute__(self.data_key)
self.cells[key] = row_cells
def update_old_row(self, data: Any) -> None:
"""
Update an old row in table.
"""
key = data.__getattribute__(self.data_key)
row_cells = self.cells[key]
for header, cell in row_cells.items():
content = data.__getattribute__(header)
cell.set_content(content, data)
def resize_columns(self) -> None:
"""
Resize all columns according to contents.
"""
self.horizontalHeader().resizeSections(QtWidgets.QHeaderView.ResizeToContents)
def save_csv(self) -> None:
"""
Save table data into a csv file
"""
path, _ = QtWidgets.QFileDialog.getSaveFileName(
self, "保存数据", "", "CSV(*.csv)")
if not path:
return
with open(path, "w") as f:
writer = csv.writer(f, lineterminator="\n")
headers = [d["display"] for d in self.headers.values()]
writer.writerow(headers)
for row in range(self.rowCount()):
if self.isRowHidden(row):
continue
row_data = []
for column in range(self.columnCount()):
item = self.item(row, column)
if item:
row_data.append(str(item.text()))
else:
row_data.append("")
writer.writerow(row_data)
def contextMenuEvent(self, event: QtGui.QContextMenuEvent) -> None:
"""
Show menu with right click.
"""
self.menu.popup(QtGui.QCursor.pos())
def save_setting(self) -> None:
""""""
settings = QtCore.QSettings(self.__class__.__name__, "custom")
settings.setValue("column_state", self.horizontalHeader().saveState())
def load_setting(self) -> None:
""""""
settings = QtCore.QSettings(self.__class__.__name__, "custom")
column_state = settings.value("column_state")
if isinstance(column_state, QtCore.QByteArray):
self.horizontalHeader().restoreState(column_state)
self.horizontalHeader().setSortIndicator(-1, QtCore.Qt.AscendingOrder)
class TickMonitor(BaseMonitor):
"""
Monitor for tick data.
"""
# event_type = EVENT_TICK
event_type = [EVENT_TICK,EVENT_AUCTION_TICK]
data_key = "vt_symbol"
sorting = True
headers = {
"symbol": {"display": "代码", "cell": BaseCell, "update": False},
"exchange": {"display": "交易所", "cell": EnumCell, "update": False},
"name": {"display": "名称", "cell": BaseCell, "update": True},
"last_price": {"display": "最新价", "cell": BaseCell, "update": True},
"volume": {"display": "成交量", "cell": BaseCell, "update": True},
"open_price": {"display": "开盘价", "cell": BaseCell, "update": True},
"high_price": {"display": "最高价", "cell": BaseCell, "update": True},
"low_price": {"display": "最低价", "cell": BaseCell, "update": True},
"bid_price_1": {"display": "买1价", "cell": BidCell, "update": True},
"bid_volume_1": {"display": "买1量", "cell": BidCell, "update": True},
"ask_price_1": {"display": "卖1价", "cell": AskCell, "update": True},
"ask_volume_1": {"display": "卖1量", "cell": AskCell, "update": True},
"datetime": {"display": "时间", "cell": TimeCell, "update": True},
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
}
class LogMonitor(BaseMonitor):
"""
Monitor for log data.
"""
event_type = EVENT_LOG
data_key = ""
sorting = False
headers = {
"time": {"display": "时间", "cell": TimeCell, "update": False},
"msg": {"display": "信息", "cell": MsgCell, "update": False},
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
}
class TradeMonitor(BaseMonitor):
"""
Monitor for trade data.
"""
event_type = EVENT_TRADE
data_key = "tradeid" # hxxjava chanage
sorting = True
headers: Dict[str, dict] = {
"tradeid": {"display": "成交号 ", "cell": BaseCell, "update": False},
"orderid": {"display": "委托号", "cell": BaseCell, "update": False},
"symbol": {"display": "代码", "cell": BaseCell, "update": False},
"exchange": {"display": "交易所", "cell": EnumCell, "update": False},
"direction": {"display": "方向", "cell": DirectionCell, "update": False},
"offset": {"display": "开平", "cell": EnumCell, "update": False},
"price": {"display": "价格", "cell": BaseCell, "update": False},
"volume": {"display": "数量", "cell": BaseCell, "update": False},
"datetime": {"display": "时间", "cell": DateTimeCell, "update": False},
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
# "reference": {"display": "策略", "cell": BaseCell, "update": False},
}
class OrderMonitor(BaseMonitor):
"""
Monitor for order data.
"""
event_type = EVENT_ORDER
data_key = "vt_orderid"
sorting = True
headers: Dict[str, dict] = {
"orderid": {"display": "委托号", "cell": BaseCell, "update": False},
"reference": {"display": "来源", "cell": BaseCell, "update": False},
"symbol": {"display": "代码", "cell": BaseCell, "update": False},
"exchange": {"display": "交易所", "cell": EnumCell, "update": False},
"type": {"display": "类型", "cell": EnumCell, "update": False},
"direction": {"display": "方向", "cell": DirectionCell, "update": False},
"offset": {"display": "开平", "cell": EnumCell, "update": False},
"price": {"display": "价格", "cell": BaseCell, "update": False},
"volume": {"display": "总数量", "cell": BaseCell, "update": True},
"traded": {"display": "已成交", "cell": BaseCell, "update": True},
"status": {"display": "状态", "cell": EnumCell, "update": True},
"datetime": {"display": "时间", "cell": DateTimeCell, "update": True},
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
}
def init_ui(self):
"""
Connect signal.
"""
super(OrderMonitor, self).init_ui()
self.setToolTip("双击单元格撤单")
self.itemDoubleClicked.connect(self.cancel_order)
def cancel_order(self, cell: BaseCell) -> None:
"""
Cancel order if cell double clicked.
"""
order = cell.get_data()
req = order.create_cancel_request()
self.main_engine.cancel_order(req, order.gateway_name)
class PositionMonitor(BaseMonitor):
"""
Monitor for position data.
"""
event_type = EVENT_POSITION
data_key = "vt_positionid"
sorting = True
headers = {
"symbol": {"display": "代码", "cell": BaseCell, "update": False},
"exchange": {"display": "交易所", "cell": EnumCell, "update": False},
"direction": {"display": "方向", "cell": DirectionCell, "update": False},
"volume": {"display": "数量", "cell": BaseCell, "update": True},
"yd_volume": {"display": "昨仓", "cell": BaseCell, "update": True},
"frozen": {"display": "冻结", "cell": BaseCell, "update": True},
"price": {"display": "均价", "cell": BaseCell, "update": True},
"pnl": {"display": "盈亏", "cell": PnlCell, "update": True},
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
}
class AccountMonitor(BaseMonitor):
"""
Monitor for account data.
"""
event_type = EVENT_ACCOUNT
data_key = "vt_accountid"
sorting = True
headers = {
"accountid": {"display": "账号", "cell": BaseCell, "update": False},
"balance": {"display": "余额", "cell": BaseCell, "update": True},
"frozen": {"display": "冻结", "cell": BaseCell, "update": True},
"available": {"display": "可用", "cell": BaseCell, "update": True},
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
}
class QuoteMonitor(BaseMonitor):
"""
Monitor for quote data.
"""
event_type = EVENT_QUOTE
data_key = "vt_quoteid"
sorting = True
headers: Dict[str, dict] = {
"quoteid": {"display": "报价号", "cell": BaseCell, "update": False},
"reference": {"display": "来源", "cell": BaseCell, "update": False},
"symbol": {"display": "代码", "cell": BaseCell, "update": False},
"exchange": {"display": "交易所", "cell": EnumCell, "update": False},
"bid_offset": {"display": "买开平", "cell": EnumCell, "update": False},
"bid_volume": {"display": "买量", "cell": BidCell, "update": False},
"bid_price": {"display": "买价", "cell": BidCell, "update": False},
"ask_price": {"display": "卖价", "cell": AskCell, "update": False},
"ask_volume": {"display": "卖量", "cell": AskCell, "update": False},
"ask_offset": {"display": "卖开平", "cell": EnumCell, "update": False},
"status": {"display": "状态", "cell": EnumCell, "update": True},
"datetime": {"display": "时间", "cell": TimeCell, "update": True},
"gateway_name": {"display": "接口", "cell": BaseCell, "update": False},
}
def init_ui(self):
"""
Connect signal.
"""
super().init_ui()
self.setToolTip("双击单元格撤销报价")
self.itemDoubleClicked.connect(self.cancel_quote)
def cancel_quote(self, cell: BaseCell) -> None:
"""
Cancel quote if cell double clicked.
"""
quote = cell.get_data()
req = quote.create_cancel_request()
self.main_engine.cancel_quote(req, quote.gateway_name)
class StrategyAccountMonitor(BaseMonitor): # hxxjava add
"""
Monitor for account data.
"""
event_type = EVENT_STRATEGY_ACCOUNT
data_key = "strategy_name"
sorting = True
headers = {
"strategy_name": {"display": "策略", "cell": BaseCell, "update": False},
"capital": {"display": "本金", "cell": BaseCell, "update": True},
"money": {"display": "权益", "cell": BaseCell, "update": True},
"margin": {"display": "保证金", "cell": BaseCell, "update": True},
"available": {"display": "可用资金", "cell": BaseCell, "update": True},
"commission": {"display": "手续费", "cell": BaseCell, "update": True},
}
class ConnectDialog(QtWidgets.QDialog):
"""
Start connection of a certain gateway.
"""
def __init__(self, main_engine: MainEngine, gateway_name: str):
""""""
super().__init__()
self.main_engine: MainEngine = main_engine
self.gateway_name: str = gateway_name
self.filename: str = f"connect_{gateway_name.lower()}.json"
self.widgets: Dict[str, QtWidgets.QWidget] = {}
self.init_ui()
def init_ui(self) -> None:
""""""
self.setWindowTitle(f"连接{self.gateway_name}")
# Default setting provides field name, field data type and field default value.
default_setting = self.main_engine.get_default_setting(
self.gateway_name)
# Saved setting provides field data used last time.
loaded_setting = load_json(self.filename)
# Initialize line edits and form layout based on setting.
form = QtWidgets.QFormLayout()
for field_name, field_value in default_setting.items():
field_type = type(field_value)
if field_type == list:
widget = QtWidgets.QComboBox()
widget.addItems(field_value)
if field_name in loaded_setting:
saved_value = loaded_setting[field_name]
ix = widget.findText(saved_value)
widget.setCurrentIndex(ix)
else:
widget = QtWidgets.QLineEdit(str(field_value))
if field_name in loaded_setting:
saved_value = loaded_setting[field_name]
widget.setText(str(saved_value))
if "密码" in field_name:
widget.setEchoMode(QtWidgets.QLineEdit.Password)
if field_type == int:
validator = QtGui.QIntValidator()
widget.setValidator(validator)
form.addRow(f"{field_name} <{field_type.__name__}>", widget)
self.widgets[field_name] = (widget, field_type)
button = QtWidgets.QPushButton("连接")
button.clicked.connect(self.connect)
form.addRow(button)
self.setLayout(form)
def connect(self) -> None:
"""
Get setting value from line edits and connect the gateway.
"""
setting = {}
for field_name, tp in self.widgets.items():
widget, field_type = tp
if field_type == list:
field_value = str(widget.currentText())
else:
try:
field_value = field_type(widget.text())
except ValueError:
field_value = field_type()
setting[field_name] = field_value
save_json(self.filename, setting)
self.main_engine.connect(setting, self.gateway_name)
self.accept()
class TradingWidget(QtWidgets.QWidget):
"""
General manual trading widget.
"""
signal_tick = QtCore.pyqtSignal(Event)
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
""""""
super().__init__()
self.main_engine: MainEngine = main_engine
self.event_engine: EventEngine = event_engine
self.vt_symbol: str = ""
self.price_digits: int = 0
self.init_ui()
self.register_event()
def init_ui(self) -> None:
""""""
self.setFixedWidth(300)
# Trading function area
exchanges = self.main_engine.get_all_exchanges()
self.exchange_combo = QtWidgets.QComboBox()
self.exchange_combo.addItems([exchange.value for exchange in exchanges])
self.symbol_line = QtWidgets.QLineEdit()
self.symbol_line.returnPressed.connect(self.set_vt_symbol)
self.name_line = QtWidgets.QLineEdit()
self.name_line.setReadOnly(True)
self.direction_combo = QtWidgets.QComboBox()
self.direction_combo.addItems(
[Direction.LONG.value, Direction.SHORT.value])
self.offset_combo = QtWidgets.QComboBox()
self.offset_combo.addItems([offset.value for offset in Offset])
self.order_type_combo = QtWidgets.QComboBox()
self.order_type_combo.addItems(
[order_type.value for order_type in OrderType])
double_validator = QtGui.QDoubleValidator()
double_validator.setBottom(0)
self.price_line = QtWidgets.QLineEdit()
self.price_line.setValidator(double_validator)
self.volume_line = QtWidgets.QLineEdit()
self.volume_line.setValidator(double_validator)
self.gateway_combo = QtWidgets.QComboBox()
self.gateway_combo.addItems(self.main_engine.get_all_gateway_names())
self.price_check = QtWidgets.QCheckBox()
self.price_check.setToolTip("设置价格随行情更新")
send_button = QtWidgets.QPushButton("委托")
send_button.clicked.connect(self.send_order)
cancel_button = QtWidgets.QPushButton("全撤")
cancel_button.clicked.connect(self.cancel_all)
grid = QtWidgets.QGridLayout()
grid.addWidget(QtWidgets.QLabel("交易所"), 0, 0)
grid.addWidget(QtWidgets.QLabel("代码"), 1, 0)
grid.addWidget(QtWidgets.QLabel("名称"), 2, 0)
grid.addWidget(QtWidgets.QLabel("方向"), 3, 0)
grid.addWidget(QtWidgets.QLabel("开平"), 4, 0)
grid.addWidget(QtWidgets.QLabel("类型"), 5, 0)
grid.addWidget(QtWidgets.QLabel("价格"), 6, 0)
grid.addWidget(QtWidgets.QLabel("数量"), 7, 0)
grid.addWidget(QtWidgets.QLabel("接口"), 8, 0)
grid.addWidget(self.exchange_combo, 0, 1, 1, 2)
grid.addWidget(self.symbol_line, 1, 1, 1, 2)
grid.addWidget(self.name_line, 2, 1, 1, 2)
grid.addWidget(self.direction_combo, 3, 1, 1, 2)
grid.addWidget(self.offset_combo, 4, 1, 1, 2)
grid.addWidget(self.order_type_combo, 5, 1, 1, 2)
grid.addWidget(self.price_line, 6, 1, 1, 1)
grid.addWidget(self.price_check, 6, 2, 1, 1)
grid.addWidget(self.volume_line, 7, 1, 1, 2)
grid.addWidget(self.gateway_combo, 8, 1, 1, 2)
grid.addWidget(send_button, 9, 0, 1, 3)
grid.addWidget(cancel_button, 10, 0, 1, 3)
# Market depth display area
bid_color = "rgb(255,174,201)"
ask_color = "rgb(160,255,160)"
self.bp1_label = self.create_label(bid_color)
self.bp2_label = self.create_label(bid_color)
self.bp3_label = self.create_label(bid_color)
self.bp4_label = self.create_label(bid_color)
self.bp5_label = self.create_label(bid_color)
self.bv1_label = self.create_label(
bid_color, alignment=QtCore.Qt.AlignRight)
self.bv2_label = self.create_label(
bid_color, alignment=QtCore.Qt.AlignRight)
self.bv3_label = self.create_label(
bid_color, alignment=QtCore.Qt.AlignRight)
self.bv4_label = self.create_label(
bid_color, alignment=QtCore.Qt.AlignRight)
self.bv5_label = self.create_label(
bid_color, alignment=QtCore.Qt.AlignRight)
self.ap1_label = self.create_label(ask_color)
self.ap2_label = self.create_label(ask_color)
self.ap3_label = self.create_label(ask_color)
self.ap4_label = self.create_label(ask_color)
self.ap5_label = self.create_label(ask_color)
self.av1_label = self.create_label(
ask_color, alignment=QtCore.Qt.AlignRight)
self.av2_label = self.create_label(
ask_color, alignment=QtCore.Qt.AlignRight)
self.av3_label = self.create_label(
ask_color, alignment=QtCore.Qt.AlignRight)
self.av4_label = self.create_label(
ask_color, alignment=QtCore.Qt.AlignRight)
self.av5_label = self.create_label(
ask_color, alignment=QtCore.Qt.AlignRight)
self.lp_label = self.create_label()
self.return_label = self.create_label(alignment=QtCore.Qt.AlignRight)
form = QtWidgets.QFormLayout()
form.addRow(self.ap5_label, self.av5_label)
form.addRow(self.ap4_label, self.av4_label)
form.addRow(self.ap3_label, self.av3_label)
form.addRow(self.ap2_label, self.av2_label)
form.addRow(self.ap1_label, self.av1_label)
form.addRow(self.lp_label, self.return_label)
form.addRow(self.bp1_label, self.bv1_label)
form.addRow(self.bp2_label, self.bv2_label)
form.addRow(self.bp3_label, self.bv3_label)
form.addRow(self.bp4_label, self.bv4_label)
form.addRow(self.bp5_label, self.bv5_label)
# Overall layout
vbox = QtWidgets.QVBoxLayout()
vbox.addLayout(grid)
vbox.addLayout(form)
self.setLayout(vbox)
def create_label(
self,
color: str = "",
alignment: int = QtCore.Qt.AlignLeft
) -> QtWidgets.QLabel:
"""
Create label with certain font color.
"""
label = QtWidgets.QLabel()
if color:
label.setStyleSheet(f"color:{color}")
label.setAlignment(alignment)
return label
def register_event(self) -> None:
""""""
self.signal_tick.connect(self.process_tick_event)
self.event_engine.register(EVENT_TICK, self.signal_tick.emit)
def process_tick_event(self, event: Event) -> None:
""""""
tick = event.data
if tick.vt_symbol != self.vt_symbol:
return
price_digits = self.price_digits
self.lp_label.setText(f"{tick.last_price:.{price_digits}f}")
self.bp1_label.setText(f"{tick.bid_price_1:.{price_digits}f}")
self.bv1_label.setText(str(tick.bid_volume_1))
self.ap1_label.setText(f"{tick.ask_price_1:.{price_digits}f}")
self.av1_label.setText(str(tick.ask_volume_1))
if tick.pre_close:
r = (tick.last_price / tick.pre_close - 1) * 100
self.return_label.setText(f"{r:.2f}%")
if tick.bid_price_2:
self.bp2_label.setText(f"{tick.bid_price_2:.{price_digits}f}")
self.bv2_label.setText(str(tick.bid_volume_2))
self.ap2_label.setText(f"{tick.ask_price_2:.{price_digits}f}")
self.av2_label.setText(str(tick.ask_volume_2))
self.bp3_label.setText(f"{tick.bid_price_3:.{price_digits}f}")
self.bv3_label.setText(str(tick.bid_volume_3))
self.ap3_label.setText(f"{tick.ask_price_3:.{price_digits}f}")
self.av3_label.setText(str(tick.ask_volume_3))
self.bp4_label.setText(f"{tick.bid_price_4:.{price_digits}f}")
self.bv4_label.setText(str(tick.bid_volume_4))
self.ap4_label.setText(f"{tick.ask_price_4:.{price_digits}f}")
self.av4_label.setText(str(tick.ask_volume_4))
self.bp5_label.setText(f"{tick.bid_price_5:.{price_digits}f}")
self.bv5_label.setText(str(tick.bid_volume_5))
self.ap5_label.setText(f"{tick.ask_price_5:.{price_digits}f}")
self.av5_label.setText(str(tick.ask_volume_5))
if self.price_check.isChecked():
self.price_line.setText(f"{tick.last_price:.{price_digits}f}")
def set_vt_symbol(self) -> None:
"""
Set the tick depth data to monitor by vt_symbol.
"""
symbol = str(self.symbol_line.text())
if not symbol:
return
# Generate vt_symbol from symbol and exchange
exchange_value = str(self.exchange_combo.currentText())
vt_symbol = f"{symbol}.{exchange_value}"
if vt_symbol == self.vt_symbol:
return
self.vt_symbol = vt_symbol
# Update name line widget and clear all labels
contract = self.main_engine.get_contract(vt_symbol)
if not contract:
self.name_line.setText("")
gateway_name = self.gateway_combo.currentText()
else:
self.name_line.setText(contract.name)
gateway_name = contract.gateway_name
# Update gateway combo box.
ix = self.gateway_combo.findText(gateway_name)
self.gateway_combo.setCurrentIndex(ix)
# Update price digits
self.price_digits = get_digits(contract.pricetick)
self.clear_label_text()
self.volume_line.setText("")
self.price_line.setText("")
# Subscribe tick data
req = SubscribeRequest(
symbol=symbol, exchange=Exchange(exchange_value)
)
self.main_engine.subscribe(req, gateway_name)
def clear_label_text(self) -> None:
"""
Clear text on all labels.
"""
self.lp_label.setText("")
self.return_label.setText("")
self.bv1_label.setText("")
self.bv2_label.setText("")
self.bv3_label.setText("")
self.bv4_label.setText("")
self.bv5_label.setText("")
self.av1_label.setText("")
self.av2_label.setText("")
self.av3_label.setText("")
self.av4_label.setText("")
self.av5_label.setText("")
self.bp1_label.setText("")
self.bp2_label.setText("")
self.bp3_label.setText("")
self.bp4_label.setText("")
self.bp5_label.setText("")
self.ap1_label.setText("")
self.ap2_label.setText("")
self.ap3_label.setText("")
self.ap4_label.setText("")
self.ap5_label.setText("")
def send_order(self) -> None:
"""
Send new order manually.
"""
symbol = str(self.symbol_line.text())
if not symbol:
QtWidgets.QMessageBox.critical(self, "委托失败", "请输入合约代码")
return
volume_text = str(self.volume_line.text())
if not volume_text:
QtWidgets.QMessageBox.critical(self, "委托失败", "请输入委托数量")
return
volume = float(volume_text)
price_text = str(self.price_line.text())
if not price_text:
price = 0
else:
price = float(price_text)
req = OrderRequest(
symbol=symbol,
exchange=Exchange(str(self.exchange_combo.currentText())),
direction=Direction(str(self.direction_combo.currentText())),
type=OrderType(str(self.order_type_combo.currentText())),
volume=volume,
price=price,
offset=Offset(str(self.offset_combo.currentText())),
reference="ManualTrading"
)
gateway_name = str(self.gateway_combo.currentText())
self.main_engine.send_order(req, gateway_name)
def cancel_all(self) -> None:
"""
Cancel all active orders.
"""
order_list = self.main_engine.get_all_active_orders()
for order in order_list:
req = order.create_cancel_request()
self.main_engine.cancel_order(req, order.gateway_name)
def update_with_cell(self, cell: BaseCell) -> None:
""""""
data = cell.get_data()
self.symbol_line.setText(data.symbol)
self.exchange_combo.setCurrentIndex(
self.exchange_combo.findText(data.exchange.value)
)
self.set_vt_symbol()
if isinstance(data, PositionData):
if data.direction == Direction.SHORT:
direction = Direction.LONG
elif data.direction == Direction.LONG:
direction = Direction.SHORT
else: # Net position mode
if data.volume > 0:
direction = Direction.SHORT
else:
direction = Direction.LONG
self.direction_combo.setCurrentIndex(
self.direction_combo.findText(direction.value)
)
self.offset_combo.setCurrentIndex(
self.offset_combo.findText(Offset.CLOSE.value)
)
self.volume_line.setText(str(abs(data.volume)))
class ActiveOrderMonitor(OrderMonitor):
"""
Monitor which shows active order only.
"""
def process_event(self, event) -> None:
"""
Hides the row if order is not active.
"""
super(ActiveOrderMonitor, self).process_event(event)
order = event.data
row_cells = self.cells[order.vt_orderid]
row = self.row(row_cells["volume"])
if order.is_active():
self.showRow(row)
else:
self.hideRow(row)
class ContractManager(QtWidgets.QWidget):
"""
Query contract data available to trade in system.
"""
headers: Dict[str, str] = {
"vt_symbol": "本地代码",
"symbol": "代码",
"exchange": "交易所",
"name": "名称",
"product": "合约分类",
"size": "合约乘数",
"pricetick": "价格跳动",
"min_volume": "最小委托量",
"gateway_name": "交易接口",
}
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
super().__init__()
self.main_engine: MainEngine = main_engine
self.event_engine: EventEngine = event_engine
self.init_ui()
def init_ui(self) -> None:
""""""
self.setWindowTitle("合约查询")
self.resize(1000, 600)
self.filter_line = QtWidgets.QLineEdit()
self.filter_line.setPlaceholderText("输入合约代码或者交易所,留空则查询所有合约")
self.button_show = QtWidgets.QPushButton("查询")
self.button_show.clicked.connect(self.show_contracts)
labels = []
for name, display in self.headers.items():
label = f"{display}\n{name}"
labels.append(label)
self.contract_table = QtWidgets.QTableWidget()
self.contract_table.setColumnCount(len(self.headers))
self.contract_table.setHorizontalHeaderLabels(labels)
self.contract_table.verticalHeader().setVisible(False)
self.contract_table.setEditTriggers(self.contract_table.NoEditTriggers)
self.contract_table.setAlternatingRowColors(True)
hbox = QtWidgets.QHBoxLayout()
hbox.addWidget(self.filter_line)
hbox.addWidget(self.button_show)
vbox = QtWidgets.QVBoxLayout()
vbox.addLayout(hbox)
vbox.addWidget(self.contract_table)
self.setLayout(vbox)
def show_contracts(self) -> None:
"""
Show contracts by symbol
"""
flt = str(self.filter_line.text())
all_contracts = self.main_engine.get_all_contracts()
if flt:
contracts = [
contract for contract in all_contracts if flt in contract.vt_symbol
]
else:
contracts = all_contracts
self.contract_table.clearContents()
self.contract_table.setRowCount(len(contracts))
for row, contract in enumerate(contracts):
for column, name in enumerate(self.headers.keys()):
value = getattr(contract, name)
if isinstance(value, Enum):
cell = EnumCell(value, contract)
else:
cell = BaseCell(value, contract)
self.contract_table.setItem(row, column, cell)
self.contract_table.resizeColumnsToContents()
class AboutDialog(QtWidgets.QDialog):
"""
About VN Trader.
"""
def __init__(self, main_engine: MainEngine, event_engine: EventEngine):
""""""
super().__init__()
self.main_engine: MainEngine = main_engine
self.event_engine: EventEngine = event_engine
self.init_ui()
def init_ui(self) -> None:
""""""
self.setWindowTitle("关于VN Trader")
text = f"""
By Traders, For Traders.
License:MIT
Website:www.vnpy.com
Github:www.github.com/vnpy/vnpy
vn.py - {vnpy.__version__}
Python - {platform.python_version()}
PyQt5 - {Qt.PYQT_VERSION_STR}
NumPy - {importlib_metadata.version("numpy")}
pandas - {importlib_metadata.version("pandas")}
RQData - {importlib_metadata.version("rqdatac")}
"""
label = QtWidgets.QLabel()
label.setText(text)
label.setMinimumWidth(500)
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(label)
self.setLayout(vbox)
class GlobalDialog(QtWidgets.QDialog):
"""
Start connection of a certain gateway.
"""
def __init__(self):
""""""
super().__init__()
self.widgets: Dict[str, Any] = {}
self.init_ui()
def init_ui(self) -> None:
""""""
self.setWindowTitle("全局配置")
self.setMinimumWidth(800)
settings = copy(SETTINGS)
settings.update(load_json(SETTING_FILENAME))
# Initialize line edits and form layout based on setting.
form = QtWidgets.QFormLayout()
for field_name, field_value in settings.items():
field_type = type(field_value)
widget = QtWidgets.QLineEdit(str(field_value))
form.addRow(f"{field_name} <{field_type.__name__}>", widget)
self.widgets[field_name] = (widget, field_type)
button = QtWidgets.QPushButton("确定")
button.clicked.connect(self.update_setting)
form.addRow(button)
scroll_widget = QtWidgets.QWidget()
scroll_widget.setLayout(form)
scroll_area = QtWidgets.QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setWidget(scroll_widget)
vbox = QtWidgets.QVBoxLayout()
vbox.addWidget(scroll_area)
self.setLayout(vbox)
def update_setting(self) -> None:
"""
Get setting value from line edits and update global setting file.
"""
settings = {}
for field_name, tp in self.widgets.items():
widget, field_type = tp
value_text = widget.text()
if field_type == bool:
if value_text == "True":
field_value = True
else:
field_value = False
else:
field_value = field_type(value_text)
settings[field_name] = field_value
QtWidgets.QMessageBox.information(
self,
"注意",
"全局配置的修改需要重启VN Trader后才会生效!",
QtWidgets.QMessageBox.Ok
)
save_json(SETTING_FILENAME, settings)
self.accept()
未完待续 ... ...
morgan66b5694ca1a94a98 wrote:
self.event_engine:EventEngine = self.strategy_engine.event_engine
这行在价差回测引擎执行的时候报错,strategy_engine:BacktestingEngine 是没有event_einge的
是不是说有事件信号的,回测引擎都暂时不支持?
印象中价差回测是没有现成的测试界面,你是如何编写回测代码的呢?
总之交易线需要和策略引擎使用同一个消息引擎就可以了。
本来合约价格是可以通过订阅获得的,就是考虑到回测时无法订阅价格,
所以改成了由策略通知交易线价格发生了变化的。
def on_spread_tick(self, tick: TickData):
"""
Callback when new spread tick data is generated.
"""
self.bg.update_tick(tick)
if self.trading:
pc = PriceData(sponsor=self.strategy_name,price_name=self.spread_name,price=tick.last_price)
self.event_engine.put(Event(EVENT_PRICE_CHANGE,data=pc))
当然如果你的米筐账号没有tick数据(通常贵多了),那么你也可以把通知价格更新的语句放在on_bar()中,
tick.last_price用bar.close代替,当然这也是没有办法的事情。
期货交易结算主要有两种方式:
1、平仓盈亏(逐笔对冲)=开仓价与平仓价之差×手数×交易单位
2、浮动盈亏(持仓盈亏)=当日结算价与开仓价之差×手数×交易单位
3、当日结存(逐笔对冲)=上日结存(逐笔对冲)+ 当日存取合计 + 平仓盈亏 (逐笔对冲)-当日手续费
4、客户权益(逐笔对冲)=当日结存(逐笔对冲)+ 浮动盈亏(持仓盈亏)
1、平仓盈亏(逐日盯市)=平当日仓盈亏+平历史仓盈亏
(1)平当日仓盈亏=当日开仓价与平仓价之差×手数×交易单位
(2)平历史仓盈亏=平仓价与昨日结算价之差×手数×交易单位
2、持仓盈亏(逐日盯市)=持当日仓盈亏+持历史仓盈亏
(1)持当日仓盈亏=当日结算价与当日开仓价之差×手数×交易单位
(2)持历史仓盈亏=当日结算价与昨日结算价之差×手数×交易单位
1、逐日盯市每天都对客户持有的合约进行盈亏计算,并且按照风控水平的设置判断客户账户的风险,如果出现爆仓或者穿仓,实时对用户进行风险通知或者风险处置。
2、因此每天接口只对当前交易日的历史委托单、成交单、当前持仓和账户信息进行推送,如果当前交易日的没有委托、成交操作,那么当前交易日的历史委托单、成交单记录没有任何内容推送。即使有当前持仓,当前交易日之前的历史委托单、成交单也不会被推送给客户端。
3、当前vnpy就是按照大部分接口都支持结算方式,但单逐日盯市更适合交易所和期货公司使用,而非交易者。
1、一笔交易是自从客户对某个合约建仓开始,到该合约的持仓量为零结束;
2、逐笔对冲的成本价会因为加减仓而发生变化;
3、逐笔对冲结算需要记录该笔交易过程中的所有成交单,才能够计算其盈亏曲线,它可以在逐日盯市结算的基础上进行改造得到;
4、可以很好地描述交易策略的交易过程和账户的权益变化情况;
5、更适合交易者和交易策略使用。
yfclark wrote:
大佬,新人小白一个,数字货币怎么有一手的说法,不是本来就可以是小数吗?只要比对应的min_volume大不就行了吗?好像只有币本位有开多少张的。
你说的和本贴中说的不是以吗事,就如你说数字货币也有min_volume的规定,就是说你只能交易min_volume*n手,n为大于0的整数。
而本帖说的是PaperAcount可以在CTA交易模块,允许你交易任意数量的合约,实际交易中是做不到的,这是不对的。
洋生 wrote:
mark
在C:\vnstudio\Lib\site-packages\vnpy\app\algo_trading的engine.py中修改load_algo_template()另外,想请教C:\vnstudio\Lib\site-packages\vnpy\app\algo_trading\algos__pycache__中的.pyc文档是干嘛的?
python的.py文件必须先编译为.pyc文件,然后执行同名的.pyc文件。
把下面的代码保持为vnpy\usertools\trade_line.py:
'''
Author: hxxjava
Date: 2021-06-24 19:57:32
LastEditTime: 2021-07-05 21:23:04
LastEditors: Please set LastEditors
Description:
'''
from dataclasses import dataclass
from typing import Callable
from vnpy.trader.constant import Direction,Offset
from vnpy.event import EventEngine,Event
from copy import deepcopy
EVENT_PRICE_CHANGE = "ePriceChange." # 价格变化消息
@dataclass
class PriceData:
"""
价格变化数据定义
"""
sponsor:str # 发起者
price_name:str # 价格名称
price:float # 价格
@dataclass
class TradeCommand:
"""
交易指令数据定义
"""
trade_line_name:str
sponsor:str # 发起者
price_name:str # 价格名称
direction:Direction
offset:Offset
price:float
payup:float
trade_num:int
class TradeLine():
"""
名称:交易线。按照设定的条件,发出交易指令。相当于预警下单线。
"""
def __init__(self,event_engine:EventEngine,tc:TradeCommand,condition:str,on_trade_command: Callable,excuted:bool=False):
self.event_engine = event_engine
self.tc = tc
self.condition = condition
self.price = None
self.excuted = excuted
self.on_trade_command = on_trade_command
self.register_event()
def register_event(self):
""" 注册消息 """
self.event_engine.register(EVENT_PRICE_CHANGE,self.process_pricechange_event)
def process_pricechange_event(self,event:Event):
""" """
pc:PriceData = event.data
if self.tc.sponsor == pc.sponsor and self.tc.price_name == pc.price_name:
self.check_price(pc.price)
def check_price(self,price:float):
""" """
if self.excuted:
self.price = price
return
if self.condition == ">":
# 大于触发价
if price > self.tc.price:
self.send_trade_event(price)
elif self.condition == "<":
# 大于触发价
if price < self.tc.price:
self.send_trade_event(price)
elif self.condition == ">=":
# 大于触发价
if price >= self.tc.price:
self.send_trade_event(price)
elif self.condition == "<=":
# 大于触发价
if price <= self.tc.price:
self.send_trade_event(price)
elif self.condition == "^":
# 上穿触发价
if self.price is not None:
if self.price <= self.tc.price < price:
self.send_trade_event(price)
elif self.condition == "v":
# 下穿触发价
if self.price is not None:
if self.price >= self.tc.price > price:
self.send_trade_event(price)
self.price = price
def send_trade_event(self,price:float):
""" """
if self.excuted:
return
tc = deepcopy(self.tc) # 简单,不模棱两可
# tc.price=price # 注释掉,直接用交易线的价格
self.excuted = True
# 执行交易指令
self.on_trade_command(tc)
def set_emit_price(self,price:float):
""" 设置交易线触发价格 """
self.tc.price = price
def set_excuted(self,excuted:bool):
""" 设置交易线是否交易过标志 """
self.excuted = excuted
def reset(self):
""" 复位交易线 """
self.price = None
self.excuted = False
def __repr__(self) -> str:
out_str = "TradeLine=({},condition={},on_trade_command={},price={},excuted={})".\
format(
self.tc,
self.condition,
self.on_trade_command,
self.price,
self.excuted
)
return out_str
下面是一个测试用的价差交易的策略例子,只为可以演示交易线的创建、触发价格更新和最新价格的更新和交易动作的执行,并不表示该策略可以盈利。
把下面代码保存为boll_arbitrage_strategy.py,保存到vnpy_spreadtrading\stategies目录下,就可以创建价差策略来观察trade_line的运作机制。
""" """
from datetime import datetime
from typing import List,Dict,Tuple,Union
from vnpy.event.engine import Event, EventEngine
from os import close, name, write
from typing import Dict
from vnpy.trader.constant import Direction, Offset,InstrumentStatus
from numpy import nan
import talib
from vnpy.trader.utility import BarGenerator, ArrayManager
from vnpy_spreadtrading.base import LegData
from vnpy_spreadtrading import (
SpreadStrategyTemplate,
SpreadAlgoTemplate,
SpreadData,
OrderData,
TradeData,
TickData,
BarData
)
from vnpy.usertools.trade_line import (
EVENT_PRICE_CHANGE,
PriceData,
TradeCommand,
TradeLine
)
class BollArbitrageStrategy(SpreadStrategyTemplate):
"""
利用Keltner通道进行套利的一种价差交易
"""
author = "hxxjava"
bar_window = 5 # K线周期
boll_window = 26 # BOLL参数1
max_steps = 4 # 最大开仓次数
min_step = 2 # 最小开仓距离
step_pos = 1 # 每步开仓数量
payup = 10
interval = 5
spread_pos = 0.0 # 当前价差数量
boll_mid = nan # BOLL中轨
step = nan # 开仓步长
parameters = [
"bar_window",
"boll_window",
"max_steps",
"min_step",
"step_pos",
"payup",
"interval"
]
variables = [
"boll_mid",
"step",
"spread_pos",
]
def __init__(
self,
strategy_engine,
strategy_name: str,
spread: SpreadData,
setting: dict
):
""""""
super().__init__(
strategy_engine, strategy_name, spread, setting
)
self.event_engine:EventEngine = self.strategy_engine.event_engine
self.bg = BarGenerator(self.on_spread_bar,self.bar_window,self.on_xmin_spread_bar)
self.am = ArrayManager(size=60)
self.pre_spread_pos = self.spread_pos
self.trade_lines:Dict[str,TradeLine] = {}
self.init_trade_lines()
self.bars:List[BarData] = []
def on_init(self):
"""
Callback when strategy is inited.
"""
self.write_log("策略初始化")
self.load_bar(days=10,callback=self.on_spread_bar)
# self.load_tick(days=1)
def on_start(self):
"""
Callback when strategy is started.
"""
# 开启响应的所有交易线的交易功能
self.write_log("策略启动")
self.adjust_trade_lines_emitprice()
self.set_trade_lines_excuted("all_lines",False)
self.adjust_trade_lines_excute()
def on_stop(self):
"""
Callback when strategy is stopped.
"""
self.write_log("策略停止")
self.put_event()
def on_spread_data(self):
"""
Callback when spread price is updated.
"""
tick = self.get_spread_tick()
self.on_spread_tick(tick)
def on_spread_tick(self, tick: TickData):
"""
Callback when new spread tick data is generated.
"""
self.bg.update_tick(tick)
if self.trading:
pc = PriceData(sponsor=self.strategy_name,price_name=self.spread_name,price=tick.last_price)
self.event_engine.put(Event(EVENT_PRICE_CHANGE,data=pc))
def on_spread_bar(self,bar:BarData):
"""
Callback when 1 min spread bar data is generated.
"""
# print(f"on_spread_bar bar={bar}")
self.bg.update_bar(bar)
def on_xmin_spread_bar(self, bar: BarData):
"""
Callback when x min spread bar data is generated.
"""
self.bars.append(bar)
if len(self.bars) > 100:
self.bars.pop(0)
self.am.update_bar(bar)
if not self.am.inited:
return
# self.boll_mid = self.am.ma(self.boll_window)
self.boll_mid = talib.MA(self.am.close,self.boll_window)[-1]
# BOLL通道
std = self.am.std(self.boll_window,array=True)
temp = talib.MA(std,5)
self.step = 2*temp[-1]
self.adjust_trade_lines_emitprice()
print(f"boll_mid,step=({self.boll_mid,self.step})")
self.put_event()
def on_spread_pos(self):
"""
Callback when spread position is updated.
"""
self.spread_pos = self.get_spread_pos()
if self.spread_pos == 0:
if self.pre_spread_pos>0:
# 复位做多交易线和空平交易线交易标志
self.set_trade_lines_excuted("below_lines",False)
if self.pre_spread_pos<0:
# 复位做多交易线和空平交易线交易标志
self.set_trade_lines_excuted("above_lines",False)
if self.pre_spread_pos == 0:
if self.spread_pos>0:
# 当持有多仓时,使能平多仓交易线
self.set_trade_lines_excuted("above_mid",False)
if self.spread_pos<0:
# 当持有空仓时,使能平多仓交易线
self.set_trade_lines_excuted("below_mid",False)
self.pre_spread_pos = self.spread_pos
self.put_event()
def init_trade_lines(self):
"""
init all trade lines
"""
for step in range(self.min_step,self.min_step+self.max_steps):
line_name = f"trade_line{step}"
tc = TradeCommand(trade_line_name=line_name,
sponsor=self.strategy_name,
price_name=self.spread_name,
direction=Direction.SHORT,
offset=Offset.OPEN,
price=0.0,trade_num=self.step_pos,payup=self.payup)
trade_line = TradeLine(
event_engine = self.event_engine,
condition=">=",
tc = tc,on_trade_command=self.on_trade_command,
excuted=True)
self.trade_lines[line_name] = trade_line
line_name = f"trade_line{-step}"
tc = TradeCommand(trade_line_name=line_name,
sponsor=self.strategy_name,
price_name=self.spread_name,
direction=Direction.LONG,
offset=Offset.OPEN,
price=0.0,trade_num=self.step_pos,payup=self.payup)
trade_line = TradeLine(
event_engine = self.event_engine,
condition="<=",
tc = tc,on_trade_command=self.on_trade_command,
excuted=True)
self.trade_lines[line_name] = trade_line
line_name = f"below_mid"
tc = TradeCommand(trade_line_name=line_name,
sponsor=self.strategy_name,
price_name=self.spread_name,
direction=Direction.LONG,
offset=Offset.CLOSE,
price=0.0,trade_num=-1,payup=self.payup)
trade_line = TradeLine(
event_engine = self.event_engine,
condition="v", # 中轨之下
tc = tc,on_trade_command=self.on_trade_command,
excuted=False if self.spread_pos < 0 else True)
self.trade_lines[line_name] = trade_line
line_name = f"above_mid"
tc = TradeCommand(trade_line_name=line_name,
sponsor=self.strategy_name,
price_name=self.spread_name,
direction=Direction.SHORT,
offset=Offset.CLOSE,
price=0.0,trade_num=-1,payup=self.payup)
trade_line = TradeLine(
event_engine = self.event_engine,
condition="^", # 中轨之上
tc = tc,on_trade_command=self.on_trade_command,
excuted=False if self.spread_pos > 0 else True)
self.trade_lines[line_name] = trade_line
def on_trade_command(self,tc:TradeCommand):
"""
process trade command
"""
if not self.trading:
return
# print(f"excute trade command :{self.get_spread_tick()} {tc}")
if nan in [self.boll_mid,self.step]:
return
if tc.direction == Direction.LONG:
volume = tc.trade_num
if tc.trade_line_name == "below_mid":
# 中轨下全部清空仓
if not self.spread_pos < 0:
self.write_log("无空仓可平!")
return
volume = abs(self.spread_pos)
if volume == 0:
self.write_log("做多0$错误!")
return
next_vol = abs(self.spread_pos+volume)
if not next_vol <= self.max_steps*self.step_pos:
self.write_log("再做多将超过最大仓位")
return
algoid = self.start_long_algo(
price=tc.price,
volume=volume,
payup=tc.payup,
offset=tc.offset,
interval=self.interval)
print(f"executed start_long_algo : {algoid}")
elif tc.direction == Direction.SHORT:
volume = tc.trade_num
if tc.trade_line_name == "above_mid":
# 中轨下全部清多仓
if not self.spread_pos > 0:
self.write_log("无空仓可平!")
return
volume = abs(self.spread_pos)
if volume == 0:
self.write_log("做空0$错误!")
return
next_vol = abs(self.spread_pos-volume)
if not next_vol <= self.max_steps*self.step_pos:
self.write_log("再做空将超过最大仓位")
return
algoid = self.start_short_algo(
price=tc.price,
volume=volume,
payup=tc.payup,
offset=tc.offset,
interval=self.interval)
print(f"executed start_short_algo : {algoid}")
def adjust_trade_lines_emitprice(self):
""" 调整各个交易线的触发价格 """
for step in range(self.min_step,self.min_step+self.max_steps):
# 调整做空交易线的触发价格
line_name = f"trade_line{step}"
trade_line = self.trade_lines.get(line_name,None)
if trade_line:
trade_line.set_emit_price(self.boll_mid+step*self.step)
# 调整做多交易线的触发价格
line_name = f"trade_line{-step}"
trade_line = self.trade_lines.get(line_name,None)
if trade_line:
trade_line.set_emit_price(self.boll_mid-step*self.step)
# 调整上中轨线和下中轨线的触发价格
for line_name in ["above_mid","below_mid"]:
trade_line = self.trade_lines.get(line_name,None)
if trade_line:
trade_line.set_emit_price(self.boll_mid)
def set_trade_lines_excuted(self,select:str,excuted:bool):
"""
设置两套多交易线的执行标志
select = "all_lines":设置所有交易线的执行标志
"above_lines":设置所有做空交易线的执行标志
"below_lines":设置所有做多交易线的执行标志
"below_mid":设置平空仓交易线的执行标志
"above_mid":设置平多仓交易线的执行标志
excuted = True:已执行;False:未执行
"""
for step in range(self.min_step,self.min_step+self.max_steps):
if select in ["above_lines","all_lines"]:
# 做空交易线
line_name = f"trade_line{step}"
trade_line = self.trade_lines.get(line_name,None)
if trade_line:
trade_line.set_excuted(excuted)
if select in ["below_lines","all_lines"]:
# 做多交易线
line_name = f"trade_line{-step}"
trade_line = self.trade_lines.get(line_name,None)
if trade_line:
trade_line.set_excuted(excuted)
if select in ["below_mid","all_lines"]:
# 下中轨线
trade_line = self.trade_lines.get("below_mid",None)
if trade_line:
trade_line.set_excuted(excuted)
if select in ["above_mid","all_lines"]:
# 上中轨线
trade_line = self.trade_lines.get("above_mid",None)
if trade_line:
trade_line.set_excuted(excuted)
def adjust_trade_lines_excute(self):
""" 调整各个交易线的执行标志 """
if self.spread_pos == 0:
self.set_trade_lines_excuted("above_lines",False)
self.set_trade_lines_excuted("below_lines",False)
self.set_trade_lines_excuted("above_mid",True)
self.set_trade_lines_excuted("below_mid",True)
elif self.spread_pos > 0:
self.set_trade_lines_excuted("above_lines",True)
self.set_trade_lines_excuted("above_mid",True)
self.set_trade_lines_excuted("below_mid",False)
elif self.spread_pos < 0:
self.set_trade_lines_excuted("below_lines",True)
self.set_trade_lines_excuted("above_mid",False)
self.set_trade_lines_excuted("below_mid",True)
def on_spread_algo(self, algo: SpreadAlgoTemplate):
"""
Callback when algo status is updated.
"""
if not algo.is_active():
print(f"algoid = {algo.algoid}")
def on_order(self, order: OrderData):
"""
Callback when order status is updated.
"""
print(f"{self.spread_name} {order}")
def on_trade(self, trade: TradeData):
"""
Callback when new trade data is received.
"""
print(f"{self.spread_name} {trade}")
慢体会它用法,您也许会有惊喜发现!
xiaohe wrote:
tonyhe6688 wrote:
Traceback (most recent call last):
File "C:\vnstudio\lib\site-packages\vnpy_ctabacktester\engine.py", line 404, in run_downloading
data = self.datafeed.query_bar_history(req)
File "C:\vnstudio\lib\site-packages\vnpy_rqdata\rqdata_datafeed.py", line 164, in query_bar_history
adjust_type="none"
File "C:\vnstudio\lib\site-packages\rqdatac\decorators.py", line 150, in wrap
return func(args, **kwargs)
File "C:\vnstudio\lib\site-packages\rqdatac\services\get_price.py", line 103, in get_price
order_book_ids, market
File "C:\vnstudio\lib\site-packages\rqdatac\services\get_price.py", line 222, in classify_order_book_ids
ins_list = ensure_instruments(order_book_ids, market=market)
File "C:\vnstudio\lib\site-packages\rqdatac\validators.py", line 168, in ensure_instruments
all_instruments = _all_instruments_dict(market)
File "C:\vnstudio\lib\site-packages\rqdatac\decorators.py", line 129, in wrapper
value = user_function(args, kwargs)
File "C:\vnstudio\lib\site-packages\rqdatac\services\basic.py", line 139, in _all_instruments_dict
ins = _all_cached_instruments_list(market)
File "C:\vnstudio\lib\site-packages\rqdatac\decorators.py", line 129, in wrapper
value = user_function(*args, kwargs)
File "C:\vnstudio\lib\site-packages\rqdatac\services\basic.py", line 134, in _all_cached_instruments_list
return _all_instruments_list(market)
File "C:\vnstudio\lib\site-packages\rqdatac\services\basic.py", line 112, in _all_instruments_list
ins = [Instrument(i) for i in get_client().execute("all_instruments", market=market)]
File "C:\vnstudio\lib\site-packages\rqdatac\client.py", line 31, in execute
raise RuntimeError("rqdatac is not initialized")
RuntimeError: rqdatac is not initialized按照楼主的提示操作了,依然回测下载RQData数据报错。
请问你参考2.6.0发布公告配置米筐账号了吗?
错误提示你是米筐账户的用户名或者密码错误了,除了datafeed的三项之外还要配置米筐账号和密码。
王野 wrote:
期货交易所提供了一些标准价差,例如 SP jd2201&jd2205 等,请问一下大佬,做价差交易,都用自己合成的价差吗,为啥不用交易所提供的价差,交易所提供的价差有啥缺点
from vnpy.trader.database import get_database
database = get_database()
... ...
from vnpy.trader.datafeed import get_datafeed
database = get_datafeed()
... ...
沉386ebe9fb6a246a6 wrote:
xiaohe wrote:
如果觉得漏了tick,可以自己打印一下收到的tick看看
测试了下。。。确实是会漏收。。。这个咋解决呢。。
在春节、国庆等长假前后,国内期货行情可能会受国外行情的影响而发生剧烈波动,交易风险急剧加大。为了能在节后有效控制投资者的风险,需要在节前提前调整投资者保证金率,通知投资者风险。节后过一段时间,期货行情恢复日常状况后,节前调整的投资者保证金率需要恢复调整前的原值。
系统为此在结算柜台中提供了一项便捷的功能,即投资者“保证金率模型”。保证金率模型,其实是先对“费率设置”目录下的“投资者保证金率属性”、“投资者结算保证金率调整”进行备份,然后在需要的时候进行手工恢复。
在flex结算柜台里,可以“从当前数据创建”一个保证金率模型,保存备份“费率设置”目录下的“投资者保证金”、“投资者保证金调整”设置。
Flex柜台界面操作流程:
“费率设置->保证金率模型管理->保证金率模型->添加模型代码、名称、说明->从当前数据创建”
备份之后,在“投资者保证金模型”、“投资者保证金调整模型”菜单下,可以对一个保证金模型的费率设置就行修改,同时也可以在保证金模型之间进行复制。
投资者保证金率模型修改的Flex柜台中界面操作流程:
“费率设置_>保证金率模型管理->投资者保证金率模型->修改->修改- -档保证金率(按金额和按手数) ->点击确认按钮”
查询:可以按照模型代码、交易所、产品/合约、投资者范围、属性、组织架构进行查询,若为空,则查询全部;
新增:添加模型保证金率率的设置;
修改:只能修改相关保证金率字段,不能修改模板、交易所、产品/合约等字段;
删除:选中记录,点击“删除”按钮即可;
批量修改:可以实现对公司范围、模板、单一投资者进行批量修改,可分为:绝对调整(调整后的值为当前调整)和相对调整(调整后的值为当前调整值加上原有值),调整完成后,可以进行试算操作,检查是否正确,若正确则点击确认按钮。
批量删除:可以对查询到的记录进行全部删除;
模型复制的flex柜台界面操作流程:
“费率设置->保证金率模型管理->保证金率模型复制->选择交易所、产品/合约、源模型、目标模型(保证投资者范围一致) ->复制”
现有的保证金模型,可以灵活地恢复到“费率设置”目录里。恢复时,在“保证金率模型”菜单下,“激活”对应模型进行启用。启用时,先删除“费率设置”目录下已有的“投资者保证金”、“投资者保证金调整”中的费率设置,然后再从模型里复制。
模型激活的Flex柜台界面操作流程:
“费率设置->保证金率模型管理->保证金率模型->查询->选中记录->激活”
综合交易平台保证金设置的钩稽关系见下图:
若公司需要对投资者保证金率进行调整,需要在投资者结算保证金率调整中操作,flex操作界面流程:
“费率设置->投资者结算保证金率管理->投资者结算保证金率调整->新增->投资者范围选择模板、填写产品/合约、产品交易日期段、选择交易所、跟随交易所->添加一档保证金率(按金额和按手数) ->点击确认按钮”如图所示:
针对投资者是否跟随交易所,在系统里分别实现保证金率调整的相关操作流程:
“跟随交易所调整”对应flex结算柜台里的“费率设置>交易所保证金率管理>交易所结算保证金率调整”菜单中“跟随交易所投资者保证金率调整”,如下图:
“不跟随交易所调整”对应于结算柜台里的“费率设置>交易所保证金率管理交易所结算保证金率调整”菜单中“不跟随交易所投资者保证金率调整”,如下图:
在实际业务中,从保证金率的设置情况上看,投资者大概可以分为A和B两类,A类是跟随交易所变动的投资者,B类是不跟随交易所的投资者。A类投资者的保证金率,以交易所保证金率为基础,略微上浮几个百分点,跟随交易所保证金率变动而变动。B类投资者的保证金率,并不以交易所保证金率为基础,直接由一个比率数值设定,不受交易所保证金率变动影响。
投资者保证金率设置分为投资者结算保证金率设置和投资者交易保证金率设置。
投资者结算保证金率设置分为投资者结算保证金率属性和投资者结算保证金率调整。
投资者交易保证金率设置分为投资者交易保证金率属性和投资者交易保证金率调整。
投资者的保证金率(M investor),主要是用于期货公司向投资者收取保证金。由“既定日期规则”部分(简记为Rinvestor)、“公司调整要求”部分(简记为Cinvestor)、“结算调整要求”部分(简记为Ainvestor) 三部分组成,因此投资者保证金率计算公式如下:
保证金率设置主要是对期货合约的保证金率按照合约文本的既定日期规则和交易所的结算调整要求进行设置。目前,系统里保证金率主要有两套设置,一套是交易所的保证金率设置,另一套是投资者的保证金率设置。
日期表达式:主要是用于期货产品创建以及合约品种的保证金比率设置。就针对合约品种设置保证金而言,根据合约文本,期货合约品种的保证金遵循一定的日期规则。从上市开始,期货合约需要跨越不同的交易阶段,这个过程中保证金有规律地阶梯式的上浮递增。
例如,燃料油FU的保证金变化规律如下图所示:
燃料油FU的保证金率,共分成6个阶段,每个交易阶段都定义了该阶段的“起始日期”(A1、 A、A3、A4、A5、A6)。 合约一旦进入该阶段,保证金率将上浮。
“起始日期”与具体的日期无关,也与具体的合约无关,是一个相对的描述,表示的是合约的生命周期中的每一个阶段的开始日期。日期表达式,就是在合约生命周期中对每个阶段起始日期的进行抽象化的描述。
日期表达式,有A、B、C三部分组成,分别是A-“基准月”、B-相对基准月的“月前/月后”、C-前移或后推的“日期”。
■ A-基准月:主要有三个参数值“交割月”(DLY)、“上市月”(OPN)、“定制月”(具体月份)
■ B-月前/月后:相对基准月的月数。“前”(-)、“后”(+)
■ C-日期: 由A和B两部分锁定月份后,再由C部分定位到该月的某一天。“公历 日”(DAT)、“交易日”(TRA)、“工作日”(WRK)、“月初”(STR)、“月末”(TAL)、“周一”(MON)、“周二”(TUE)、“周三”(WED)、“周四”(THR)、“周五”(FRI)、“周六”(SAT)、 “周日”(SUN)、“前”(<)、“后”(>)、“前(含)”(-)、“后(含)”(+)。
具体的示例如”下:
■ OPN-000+001TRA: 上市月前0月第1个交易日,即上市当月的第1个交易日。
■ DLY-001+001DAT:交割月前1月第1个公历日。
■ OPN-000+003FRI+001TRA>001TRA: 上市月前0月,后(含)第3周周五,后(含)第1个交易日,后第1个交易日,共4句。首先定位到“上市当月的第3周周五”那一天。如果这一天是交易日,那么“后(含)第1个交易日”就指的是“ 第3周周五”当天,否则往后顺延。“后第1个交 易日”,表示再往后推1个交易日。对于IF合约,前3句就表示了一个IF合约的最后交易日,再加第4句就表示了下一个IF合约的上市日期。
■ DLY-001+001TAL-001TRA <002TRA:交割月前1月,后(含)第1个月末,前(含)第1个交易日,前第2个交易日,共4句。首先定位到“ 交割前1月的月末”那一天。如果这一天是交易日,那么“前(含)第1个交易日”就指的是“月末”当天,否则往前顺延。“前第2个交易日”,表示再往前移2个交易日。对于FU合约,前3句就表示了一个FU合约的最后交易日,再加第4句就表示了一个FU合约最后交易日的前2个交易日。
通过“日期表达式”定义各个交易阶段的“起始日期”,就可以表达出期货合约的保证金率r与交易日t的函数关系,也就是合约交易保证金率的日期规则。对于燃料油FU,其保证金率日期规则共有6条,如下图所示:
这6条保证金日期规则,描述每一个Fu合约从上市到下市整个生命周期的6个阶段的保证金的变化规律。
▲“前第1个交易日”与“前(含)第1个交易日”区别:以某一公历日为基准日A来看,如果A日为交易日,那么前者表示A日前面的一个交易日,后者表示A日;如果A日为非交易日,那么两者均表示A日前面的一个交易日。
▲“后第 1个交易日”与“后(含)第1个交易日”区别:以某一公历日为基准日A来看,如果A日为交易日,那么前者表示A日后面的一个交易日,后者表示A日;如果A日为非交易日,那么两者均表示A日后面的一个交易日。
交易所的保证金率(简记为Mexchange),主要是用于期货公司与交易所进行保证金比对检查。由“既定日期规则”部分(简记为Rexchange)、“结算调整要求”部分(简记为Aexchange)两部分组成,因此交易所保证金率计算公式如下:
Mexchange = Rexchange + Aexchange
交易所保证金率设置分为两部分:交易所结算保证金率属性和交易所结算保证金率调整。
【注:以下的内容来自SFIT的官方文档《综合交易平台结算平台业务操作手册》,上传以下的几个帖子的目的是:通过对《综合交易平台结算平台业务操作手册》中对费率的设置的研究,让大家明白手续费率和保证金率的概念和计算方法,进而正确地计算出交易中发生的手续费和保证金,以及为了计算交易手续费和保证金所需要的参数有哪些。】
手续费设置本平台分为交易所手续费率设置和投资者手续费率设置,主要是对期货合约的手续费率按照交易所规定和公司要求进行设置,分为开仓、平仓、平今、结算、交割、移仓等6种手续费率,其中结算、移仓手续费率并未启用,不需要设置。每种手续费率,以按金额、按手数等2种方式取和收取手续费。记按金额手续费率为R金额、按手数手续费率为R手数,手续费C手续费的计算公式如下:
C手续费=R金额×成交金额+R手数×成交手数
对于新合约的上市,需要设置交易所手续费率,在flex柜台中进行新增、修改、删除、查询操作,交易所手续费设置,主要用于设置交易所手续费率,以计算每笔成交的上交手续费,分为按产品和合约设置,同一产品和该产品的合约,合约优先级大于产品。在结算柜台中的“手续费率”目录下,“交易所手续费率设置”“交易所手续费率查询”菜单可以进行交易所手续费率的新增、修改和查询操作。手续费率设置后重新结算,就按新设置的手续费率计算手续费,盘中设置手续费率是否实时上场。
Flex柜台界面操作:
“费率设置->手续费率->交易所手续费率设置->新增即可”
查询:选择交易所、产品/合约,若均为空的话,则是对所有产品和合约进行查询;
新增:选择交易所;选择产品/合约,合约的优先级高于产品;对开仓、平仓、平今、结算、交割四个手续费率进行添加,结算、移仓手续费率并未启用,不需要设置,按金额、按手数等2种方式取和收取手续费;
修改:选中需要修改的记录,点击“修改”按钮,进入“交易所手续费率(修改)”界面,可以修改费率,不能修改交易所和产品/合约;
删除:选中需要删除的记录,即可进行删除操作。
投资者手续费设置,主要用于设置投资者手续费率,以计算每笔成交的投资者手续费。在结算柜台中的“手续费率”目录下,“投资者手续费率设置”菜单可以进行投资者手续费率的新增、修改和查询操作。投资者手续费率分为公司标准、模板、单一投资者,按照所述对象范围的粒度大小,这3种情况对应的保证金率设置存在如下的优先级关系:
单一投资者>模板>公司标准
系统按照投资者范围优先级的先后关系,三种情况设置分为按品种和按合约。对某一个投资者手续费率生效的优先级别是单一投资者>手续费率模板>公司标准,同一产品和该产品的合约,合约优先级别大于产品,系统依次去寻找投资者的手续费率设置,直到找到相应设置为止。
手续费率设置后重新结算,就按新设置的手续费率计算手续费,盘中设置手续费率是实时上场。手续费率实时同步的设置在flex柜台界面的操作流程:
“费率设置->手续费率盘中同步参数设置->选择“是”->点击确认”。
针对投资者手续费率设置中,分为三种情况,一、设置所有投资者手续费率,二、设置手续费率模板手续费率,三、 设置单一投资者手续费率,单一投资者设置与公司标准设置相对比较简单,下面主要介绍一新开户投资者模板手续费率设置步骤。
对投资者手续费率进行批量调整时,可以通过手续费率模型来实现。可以通过创建一个模型A实现对当前投资者手续费率数据进行备份,同时将数据复制到一个新模型B,如果手续费进行批量调整时可以对模型B进行批量修改,激活启用,那么投资者手续费率即按照模型B来进行收取;若过段时间手续费率需要进行恢复以前设置,则激活模型A即可。
手续费率模型应用步骤如下:
在flex中界面操作:
▶“ 费率设置->手续费率模型管理->手续费率模型-> 点击新增->填写手续费率模型代码、名称、选择交易所和产品、备注->从当前数据创建或确认”
注:若点击“确认”,手续费率需要到“模型投资者手续费率”中进行添加设置,添加过程中是与所创建模型是相对应的,包括:交易所得选择、产品/合约的选择;若是“从当前数据创建”,则所创建模型即为当前手续费率数据的设置;注:当交易所为空时,表示该手续费率模型的适用范围为所有交易所所有产品,激活时将覆盖所有手续费率;创建模型时,当交易所不为空,产品为空时,表示该手续费率模型的适用范围为特定交易所所有产品,激活时将覆盖特定交易所的所有产品;当交易所、产品都不为空时,表示该手续费率模型的适用范围为特定交易所特定产品,激活时将覆盖特定交易所特定产品的手续费率。
▶“费率设置-> 手续费率模型管理->模型手续费率->新增->填写模型代码、选择交易所、产品/合约、投资者范围、添加开仓、平仓、平今、交割手续费率(按金额和按手数) ->点击确认按钮”
查询:可以按照模型代码、交易所、产品/合约、投资者范围、组织架构进行查询,若为空,则查询全部;
新增:添加模型手续费率的设置;
修改:只能修改相关费率字段,不能修改费率模板、交易所、产品/合约;
删除:选中记录,点击“删除”按钮即可;
批量修改:可以实现对公司范围、模板、单一投资者进行批量修改,可分为:绝对调整(调整后的值为当前调整)和相对调整(调整后的值为调整值加上原有值),调整完成后,可以进行试算操作,检查是否正确,若正确则点击确认按钮。
批量删除:可以对查询到的记录进行全部删除;
复制:是将一个手续费率模型复制到另一个手续费率模型,可以按照投资者范围、交易所的产品/合约进行复制,则目标的模型手续费率设置将被删除、替换为源的模型手续
费率。
▶在“手续费率模型”界面中,选中所需要模型,点击“激活”即可,这样就实现对一个新建模型的启用。
▲交易所手续费率、投资者手续费率设置都是实时生效的。
回测不需要行情服务器连接成功,必须datafeed配置正确并且可以连接成功,例如米筐、tushare或者tqdata等。
条件单是一个带有触发条件的指令。该触发条件可以以市场上的最新行情为基准,也可以以指定价格为基准。比如:一个投资者有1手IF1910的空头持仓,并希望在市场价低于2200时买入平仓,他就可以使用条件单。这样当行情波动到满足该条件时,该报单就会被自动触发报出,而不需要他本人时刻盯着电脑屏幕去监视市场行情。
申报条件单时,条件单的报单回报中OrderSysID是以字符串“TJBD_”开头的由CTP系统自定义的报单编号,针对该交易所当日唯一;只对成功申报的条件单编号,错单不编号。因为这种情况下的编号由CTP系统自行维护,与交易所无关,仅用于管理本系统内的该条件单。
有效的使用条件单,可以做出限价止损指令(stop-and-limit order)和触及市价指令(market-if-touched order)。
CTP条件单为CTP后台系统自带指令,并非交易所官方支持指令。
报入条件单指令使用ReqOrderInsert函数
其中的核心数据结构是CThostFtdcInputOrderField
在结构体中和普通报单有区别的字段如下:
///触发条件
TThostFtdcContingentConditionType ContingentCondition;
///止损价
TThostFtdcPriceType StopPrice;
条件单有效的触发条件如下:
///最新价大于条件价
#define THOST_FTDC_CC_LastPriceGreaterThanStopPrice '5'
///最新价大于等于条件价
#define THOST_FTDC_CC_LastPriceGreaterEqualStopPrice '6'
///最新价小于条件价
#define THOST_FTDC_CC_LastPriceLesserThanStopPrice '7'
///最新价小于等于条件价
#define THOST_FTDC_CC_LastPriceLesserEqualStopPrice '8'
///卖一价大于条件价
#define THOST_FTDC_CC_AskPriceGreaterThanStopPrice '9'
///卖一价大于等于条件价
#define THOST_FTDC_CC_AskPriceGreaterEqualStopPrice 'A'
///卖一价小于条件价
#define THOST_FTDC_CC_AskPriceLesserThanStopPrice 'B'
///卖一价小于等于条件价
#define THOST_FTDC_CC_AskPriceLesserEqualStopPrice 'C'
///买一价大于条件价
#define THOST_FTDC_CC_BidPriceGreaterThanStopPrice 'D'
///买一价大于等于条件价
#define THOST_FTDC_CC_BidPriceGreaterEqualStopPrice 'E'
///买一价小于条件价
#define THOST_FTDC_CC_BidPriceLesserThanStopPrice 'F'
///买一价小于等于条件价
#define THOST_FTDC_CC_BidPriceLesserEqualStopPrice 'H'
条件价对应的字段为StopPrice