VeighNa量化社区
你的开源社区量化交易平台
hxxjava's Avatar
Member
离线
419 帖子
声望: 155

jingsiaosing wrote:

这样的话第一个20:59:00的tick接收到后 会初始化一个时间戳为21:00:00的bar,一分钟之后连续竞价交易过来的tick的分钟与其相同,就不会推bar,直到这一分钟走完。

这样的做法是错误的:

  1. 不是所有合约都是21:00:00开盘,还有的合约根本就没有夜盘,有的是上午9:00,还有其他时间开盘的;用固定时间来判断是在凑合事。
  2. 只有集合竞价时段收到的那一个tick才可以这么做,其它的情况不不可以这么做。

一个交易所可能有多个柜台连接,比如CTP柜台、恒牛柜台、金仕达柜台,不管来自哪个柜台,是不是报单编号和成交编号在一个交易 日内,在交易所内是唯一的吗?
报单编号是唯一的, 但是成交编号是不唯一的,因为撮合的双方,成交编号是一致的。也就是说一个报单可能在撮合过程中被拆分为多个成交单,交易所赋予成交单的相同的成交编号会被推送给多空双方,同时还包含了个不相同的报单编号。
另外,因为自成交情况存在,所以在写程序时需要注意,用成交方向+成交编号才能确定笔成交记录。

naive wrote:

def send_buy_orders(self, price):
""""""
t = self.pos / self.fixed_size

    if 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+2
ATR,P+3ATR);
如果已经持有3份仓,就下1份停止单,价格为(P+3
ATR);
如果已经持有4份仓,就不下停止单了;

1. 消息定义

修改vnpy\trader\event.py,添加如下内容:

EVENT_ORIGIN_TICK = "eOriginTick."              # 原始tick消息
EVENT_AUCTION_TICK = "eAuctionTick."         # 集合竞价tick消息
EVENT_STATUS = "eStatus."                              # 交易状态消息
EVENT_STATUS_END = "eStatusEnd."              # 交易状态结束消息

2. 常量定义

修改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 = "熔断"

3. 添加合约交易状态数据类型

修改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)

4. 网关修改

4.1 gateway修改

修改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)

4.2 ctp_gateway修改

修改vnpy_ctp\gateway\ctp_gateway.py,步骤如下:

4.2.1 修改引用部分

from vnpy.trader.constant import InstrumentStatus,StatusEnterReason
from vnpy.trader.object import StatusData,     # hxxjava debug

4.2.2 修改CtpTdApi的构造函数init()

    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
        )

4.2.3 这样修改合约查询回报函数

    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

5. OmsEngine修改

修改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

6. app中如何获得集合竞价tick

6.1 主界面中如何显示集合竞价tick ?

修改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()

6.2 在策略中如何订阅集合竞价tick?

未完待续 ... ...

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. 逐笔对冲

1、平仓盈亏(逐笔对冲)=开仓价与平仓价之差×手数×交易单位
2、浮动盈亏(持仓盈亏)=当日结算价与开仓价之差×手数×交易单位
3、当日结存(逐笔对冲)=上日结存(逐笔对冲)+ 当日存取合计 + 平仓盈亏 (逐笔对冲)-当日手续费
4、客户权益(逐笔对冲)=当日结存(逐笔对冲)+ 浮动盈亏(持仓盈亏)

3. 逐日盯市

1、平仓盈亏(逐日盯市)=平当日仓盈亏+平历史仓盈亏
(1)平当日仓盈亏=当日开仓价与平仓价之差×手数×交易单位
(2)平历史仓盈亏=平仓价与昨日结算价之差×手数×交易单位
2、持仓盈亏(逐日盯市)=持当日仓盈亏+持历史仓盈亏
(1)持当日仓盈亏=当日结算价与当日开仓价之差×手数×交易单位
(2)持历史仓盈亏=当日结算价与昨日结算价之差×手数×交易单位

4 两种结算方式的特点

4.1 逐日盯市的特点

1、逐日盯市每天都对客户持有的合约进行盈亏计算,并且按照风控水平的设置判断客户账户的风险,如果出现爆仓或者穿仓,实时对用户进行风险通知或者风险处置。
2、因此每天接口只对当前交易日的历史委托单、成交单、当前持仓和账户信息进行推送,如果当前交易日的没有委托、成交操作,那么当前交易日的历史委托单、成交单记录没有任何内容推送。即使有当前持仓,当前交易日之前的历史委托单、成交单也不会被推送给客户端。
3、当前vnpy就是按照大部分接口都支持结算方式,但单逐日盯市更适合交易所和期货公司使用,而非交易者。

4.2 逐笔对冲的特点

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文件。

1. 交易线的代码

把下面的代码保持为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

2. 如何使用交易线

下面是一个测试用的价差交易的策略例子,只为可以演示交易线的创建、触发价格更新和最新价格的更新和交易动作的执行,并不表示该策略可以盈利。
把下面代码保存为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}")

3 交易线的优点

  • vnpy停止单只能在vnpy的CTA策略模块中使用,而交易线可以在几乎所有的vnpy模块的策略中使用;
  • 交易线的触发交易的价格是可变的,它可以让您的指标具备交易功能,这个在上面的例子中已经做了演示;
  • 和vnpy停止单一样,交易线也是本地的条件单,但它只有符合条件才出发交易动作。

慢体会它用法,您也许会有惊喜发现!

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 等,请问一下大佬,做价差交易,都用自己合成的价差吗,为啥不用交易所提供的价差,交易所提供的价差有啥缺点

  1. 只有CZCE和DCE都有交易所套利组合,SHFE、CFFEX和INE却没有,那么这些交易所的合约如何做套利?解决的方法是自己合成价差。
  2. CZCE和DCE的合约组合发现有套利机会的,可是它们也不是交易所的套利组合,那么怎么样办?解决的方法是自己合成价差。
  3. 通常跨市场的品种之间存在套利机会的时候,一定不会有交易所套利组合的。比如SHFE的螺纹钢某个合约与DCE的铁矿石的某个合约可以构成一个套利组合,此时我们也可以自己合成一个价差;再比如伦敦金与SHFE的沪金一定不会有交易所套利组合的,也可以自己生成自己合成一个价差来做套利。

答:

2.6版以后没有database_manager了,取而代之的是database和datafeed。

database用get_database()来获取

from vnpy.trader.database import get_database
database = get_database()
 ... ...

datafeed用get_datafeed()来获取

from vnpy.trader.datafeed import get_datafeed
database = get_datafeed()
  ... ...

沉386ebe9fb6a246a6 wrote:

xiaohe wrote:

如果觉得漏了tick,可以自己打印一下收到的tick看看
测试了下。。。确实是会漏收。。。这个咋解决呢。。

答案:

因为class BTCSpot中使用的bar的来源有三个:

  1. 利用on_tick从行情接口收到的tick自己生成的bar
  2. init()函数中利用self.load_bar()从datafeed中类似米筐接口中读取的历史1分钟1bar
  3. init()函数中利用self.load_bar()从datafeed数据库中读取的历史1分钟1bar
    如果1的合成1分钟bar错误了,可是重新启动策略时会先执行2或者3,而接口或者数据库中的1分钟bar是正确的,那么1的错误就会被掩盖。

解决的方法:

  1. 你要搞清楚是否是问题,你是以什么标准认为有问题的呢?
  2. 是因为丢失了tick,还是因为对集合竞价的认识问题
  3. 如果都不是,把on_tick()检查仔细了。

保证金率模型

在春节、国庆等长假前后,国内期货行情可能会受国外行情的影响而发生剧烈波动,交易风险急剧加大。为了能在节后有效控制投资者的风险,需要在节前提前调整投资者保证金率,通知投资者风险。节后过一段时间,期货行情恢复日常状况后,节前调整的投资者保证金率需要恢复调整前的原值。
系统为此在结算柜台中提供了一项便捷的功能,即投资者“保证金率模型”。保证金率模型,其实是先对“费率设置”目录下的“投资者保证金率属性”、“投资者结算保证金率调整”进行备份,然后在需要的时候进行手工恢复。

一、备份

在flex结算柜台里,可以“从当前数据创建”一个保证金率模型,保存备份“费率设置”目录下的“投资者保证金”、“投资者保证金调整”设置。
Flex柜台界面操作流程:
“费率设置->保证金率模型管理->保证金率模型->添加模型代码、名称、说明->从当前数据创建”

description

备份之后,在“投资者保证金模型”、“投资者保证金调整模型”菜单下,可以对一个保证金模型的费率设置就行修改,同时也可以在保证金模型之间进行复制。
投资者保证金率模型修改的Flex柜台中界面操作流程:
“费率设置_>保证金率模型管理->投资者保证金率模型->修改->修改- -档保证金率(按金额和按手数) ->点击确认按钮”
查询:可以按照模型代码、交易所、产品/合约、投资者范围、属性、组织架构进行查询,若为空,则查询全部;
新增:添加模型保证金率率的设置;
修改:只能修改相关保证金率字段,不能修改模板、交易所、产品/合约等字段;
删除:选中记录,点击“删除”按钮即可;
批量修改:可以实现对公司范围、模板、单一投资者进行批量修改,可分为:绝对调整(调整后的值为当前调整)和相对调整(调整后的值为当前调整值加上原有值),调整完成后,可以进行试算操作,检查是否正确,若正确则点击确认按钮。
批量删除:可以对查询到的记录进行全部删除;
模型复制的flex柜台界面操作流程:
“费率设置->保证金率模型管理->保证金率模型复制->选择交易所、产品/合约、源模型、目标模型(保证投资者范围一致) ->复制”

二、恢复

现有的保证金模型,可以灵活地恢复到“费率设置”目录里。恢复时,在“保证金率模型”菜单下,“激活”对应模型进行启用。启用时,先删除“费率设置”目录下已有的“投资者保证金”、“投资者保证金调整”中的费率设置,然后再从模型里复制。
模型激活的Flex柜台界面操作流程:
“费率设置->保证金率模型管理->保证金率模型->查询->选中记录->激活”

description

保证金调整规则

综合交易平台保证金设置的钩稽关系见下图:

description

名词解释:

  1. 交易所基础(A):根据“费率设置-交易所保证金率管理-交易所结算保证金率属性”设置的产品规则,生成的对应合约今日的交易所保证金率基础,即“费率设置-交易所保证金率管理-当前交易所结算保证金率属性”
  2. 交易所调整(H): “费率设置-交易所保证金率管理-交易所保证金率调整”里的第一行四个“交易所xxxx保证金率”
  3. 跟随交易所调整(B):“费率设置-交易所保证金率管理-交易所保证金率调整”里的第二行四个“跟随交易所投资者xxx保证金率”
  4. 跟随交易所调整(C):“费率设置-交易所保证金率管理-交易所保证金率调整”里的第三行四个“不跟随交易所投资者xxxx保证金率”
  5. 跟随交易所的“投资者基础”(D):根据“费率设置-投资者结算保证金率管理投资者保证金率属性”设置的产品规则(只包含跟随交易所的记录),生成的对应合约今日的投资者保证金率基础,即“费率设置投资者结算保证金率管理-当前投资者结算保证金率属性”里的“是否跟随交易所收取”为“是”的记录
  6. 不跟随交易所的“投资者基础”(E): 根据“费率设置-投资者结算保证金率管理-投资者保证金率属性”设置的产品规则(只包含不跟随交易所的记录),生成的对应合约今日的投资者保证金率基础,即“费率设置-投资者结算保证金率管理-当前投资者结算保证金率属性”里的“是否跟随交易所收取”为“否”的记录
  7. 跟随交易所的“投资者调整”(F):“费率设置-投资者结算保证金率管理-投资者结算保证金率调整”,“是否跟随交易所”为“是”的记录
  8. 不跟随交易所的“投资者调整”(G):“费率设置-投资者结算保证金率调整”,“是否跟随交易所”为“否”的记录

算法简述:

  1. 交易所保证金率=交易所基础(A) +交易所调整设置中的“交易所调整”(H)交易所调整设置中的“交易所调整”(H)不用于任何客户保证金比率计算公式,仅仅用于交易所保证金率。
  2. 如果投资者基础设置选择了“跟随交易所”,并且投资者调整设置有值,且也选择了“跟随交易所”,那么最终计算公式为:客户保证金率=交易所基础(A) +交易所调整设置中的“跟随交易所调整”(B) +投资者基础设置中那些选择了跟随交易所的“投资者基础”(D) +投资者调整设置中那些选择了跟随交易所的“投资者调整”(F)
  3. 如果投资者基础设置选择了“不跟随交易所”,并且投资者调整设置有值,且也选择了“不跟随交易所”,那么最终计算公式为:
    客户保证金比率=投资者基础设置中那些选择了不跟随交易所的“投资者基础"(E)+交易所调整设置中的“不跟随交易所调整”(C) +投资者调整设置中那些选择了不跟随交易所的“投资者调整”(G)
  4. 如果投资者基础设置选择了“跟随交易所”,并且投资者调整设置有值,且选择了“不跟随交易所”(设置与前面“跟随”不一致,投资者调整无效),那么最终计算公式为:
    客户保证金率=交易所基础(A) +交易所调整设置中的“跟随交易所调整”(B) +投资者基础设置中那些选择了跟随交易所的“投资者基础”(D)
  5. 如果投资者基础设置选择了“不跟随交易所”,并且投资者调整设置有值,且选择了“跟随交易所”(设置与前面“不跟随”不一致,投资者调整无效),那么最终计算公式为:
    客户保证金率=投资者基础设置中那些选择了不跟随交易所的“投资者基础”(E) +交易所调整设置中的“不跟随交易所调整”(C)

投资者结算保证金率调整

若公司需要对投资者保证金率进行调整,需要在投资者结算保证金率调整中操作,flex操作界面流程:
“费率设置->投资者结算保证金率管理->投资者结算保证金率调整->新增->投资者范围选择模板、填写产品/合约、产品交易日期段、选择交易所、跟随交易所->添加一档保证金率(按金额和按手数) ->点击确认按钮”如图所示:

description

针对投资者是否跟随交易所,在系统里分别实现保证金率调整的相关操作流程:
“跟随交易所调整”对应flex结算柜台里的“费率设置>交易所保证金率管理>交易所结算保证金率调整”菜单中“跟随交易所投资者保证金率调整”,如下图:

description

description

“不跟随交易所调整”对应于结算柜台里的“费率设置>交易所保证金率管理交易所结算保证金率调整”菜单中“不跟随交易所投资者保证金率调整”,如下图:
description

投资者保证金率

在实际业务中,从保证金率的设置情况上看,投资者大概可以分为A和B两类,A类是跟随交易所变动的投资者,B类是不跟随交易所的投资者。A类投资者的保证金率,以交易所保证金率为基础,略微上浮几个百分点,跟随交易所保证金率变动而变动。B类投资者的保证金率,并不以交易所保证金率为基础,直接由一个比率数值设定,不受交易所保证金率变动影响。

投资者保证金率设置分为投资者结算保证金率设置和投资者交易保证金率设置。

投资者结算保证金率设置分为投资者结算保证金率属性和投资者结算保证金率调整。
投资者交易保证金率设置分为投资者交易保证金率属性和投资者交易保证金率调整。

  1. 投资者保证金率设置分三种情况,一:设置公司标准的保证金率,二:设置保证金率模板保证金率,三:设置单一投资者保证金率,这三种情况设置分为按品种和按合约。对某一个投资者保证金率生效的优先级别是单一设置>保证金率模板设置>公司标准设置,同一产品和产品的合约,合约优先级别大约产品。投资者保证金率属性:支持按产品、合约设置,按产品交易日期段设置。
  2. 投资者结算保证金率调整:“投资者结算保证金率调整”可设置为“仅当日生效”或“长期生效”。“投资者交易保证金率调整”由上一交易日“投资者结算保证金率调整”生成。
  3. 当前投资者保证金率属性:根据“投资者结算保证金率属性”生成“当前投资者结算保证金率属性”,根据上一交易日“当前投资者结算保证金率属性”生成“当前投资者交易保证金率属性”。交易和结算费率均可增删改查,对交易费率的变更实时上场。
  4. 对投资者交易保证金率属性和投资者交易保证金率调整的修改会实时上场。

投资者的保证金率(M investor),主要是用于期货公司向投资者收取保证金。由“既定日期规则”部分(简记为Rinvestor)、“公司调整要求”部分(简记为Cinvestor)、“结算调整要求”部分(简记为Ainvestor) 三部分组成,因此投资者保证金率计算公式如下:

description

3.2 保证金率设置

保证金率设置主要是对期货合约的保证金率按照合约文本的既定日期规则和交易所的结算调整要求进行设置。目前,系统里保证金率主要有两套设置,一套是交易所的保证金率设置,另一套是投资者的保证金率设置。
日期表达式:主要是用于期货产品创建以及合约品种的保证金比率设置。就针对合约品种设置保证金而言,根据合约文本,期货合约品种的保证金遵循一定的日期规则。从上市开始,期货合约需要跨越不同的交易阶段,这个过程中保证金有规律地阶梯式的上浮递增。

例如,燃料油FU的保证金变化规律如下图所示:

description

燃料油FU的保证金率,共分成6个阶段,每个交易阶段都定义了该阶段的“起始日期”(A1、 A、A3、A4、A5、A6)。 合约一旦进入该阶段,保证金率将上浮。

description

“起始日期”与具体的日期无关,也与具体的合约无关,是一个相对的描述,表示的是合约的生命周期中的每一个阶段的开始日期。日期表达式,就是在合约生命周期中对每个阶段起始日期的进行抽象化的描述。
日期表达式,有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条,如下图所示:

description

这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

交易所保证金率设置分为两部分:交易所结算保证金率属性和交易所结算保证金率调整。

  1. 交易所结算保证金率属性设置分为按产品和合约设置,同一产品和该产品的合约,合约优先级大于产品;按产品交易日期段进行设置。例如设置cu和cu1209 的保证金率,计算cu1209的保证金是取cu1209的保证金率。交易所保证金率设置不会实时上场,下一交易日生效。
  2. 当前交易所结算保证金属性:根据“交易所结算保证金率属性”生成“当前交易所结算保证金率属性”,根据上一交易日“当前交易所结算保证金率属性”生成“当前交易所交易保证金率属性”。支持“当前交易所结算保证金属性”增改查操作。不支持“当前交易所交易保证金率属性”增改操作。
  3. 交易所结算保证金率调整设置也分为按产品和合约设置,同一产品和该产品的合约,合约优先级大于产品;生效方式分为仅当日生效和长期生效;设置的保证金率有交易所保证金率、跟随交易所投资者保证金率,不跟随交易所投资者保证金率三种,三种设置分别用于计算交易所保证金率、跟随交易所的投资者的保证金率和不跟随交易所的投资者的保证金率。“交易所交易保证金率调整”沿用上一交易日的“交易所结算保证金率调整”。
  4. 交易所保证金率的计算公式:当前交易所保证金率=当前交易所保证金率属性+交易所保证金率调整。

【注:以下的内容来自SFIT的官方文档《综合交易平台结算平台业务操作手册》,上传以下的几个帖子的目的是:通过对《综合交易平台结算平台业务操作手册》中对费率的设置的研究,让大家明白手续费率和保证金率的概念和计算方法,进而正确地计算出交易中发生的手续费和保证金,以及为了计算交易手续费和保证金所需要的参数有哪些。】

3. 费率的设置

3.1 手续费率设置

手续费设置本平台分为交易所手续费率设置和投资者手续费率设置,主要是对期货合约的手续费率按照交易所规定和公司要求进行设置,分为开仓、平仓、平今、结算、交割、移仓等6种手续费率,其中结算、移仓手续费率并未启用,不需要设置。每种手续费率,以按金额、按手数等2种方式取和收取手续费。记按金额手续费率为R金额、按手数手续费率为R手数,手续费C手续费的计算公式如下:

C手续费=R金额×成交金额+R手数×成交手数

交易所手续费率设置:

对于新合约的上市,需要设置交易所手续费率,在flex柜台中进行新增、修改、删除、查询操作,交易所手续费设置,主要用于设置交易所手续费率,以计算每笔成交的上交手续费,分为按产品和合约设置,同一产品和该产品的合约,合约优先级大于产品。在结算柜台中的“手续费率”目录下,“交易所手续费率设置”“交易所手续费率查询”菜单可以进行交易所手续费率的新增、修改和查询操作。手续费率设置后重新结算,就按新设置的手续费率计算手续费,盘中设置手续费率是否实时上场。

Flex柜台界面操作:

“费率设置->手续费率->交易所手续费率设置->新增即可”
查询:选择交易所、产品/合约,若均为空的话,则是对所有产品和合约进行查询;
新增:选择交易所;选择产品/合约,合约的优先级高于产品;对开仓、平仓、平今、结算、交割四个手续费率进行添加,结算、移仓手续费率并未启用,不需要设置,按金额、按手数等2种方式取和收取手续费;
修改:选中需要修改的记录,点击“修改”按钮,进入“交易所手续费率(修改)”界面,可以修改费率,不能修改交易所和产品/合约;
删除:选中需要删除的记录,即可进行删除操作。

投资者手续费率的设置

投资者手续费设置,主要用于设置投资者手续费率,以计算每笔成交的投资者手续费。在结算柜台中的“手续费率”目录下,“投资者手续费率设置”菜单可以进行投资者手续费率的新增、修改和查询操作。投资者手续费率分为公司标准、模板、单一投资者,按照所述对象范围的粒度大小,这3种情况对应的保证金率设置存在如下的优先级关系:

    单一投资者>模板>公司标准

系统按照投资者范围优先级的先后关系,三种情况设置分为按品种和按合约。对某一个投资者手续费率生效的优先级别是单一投资者>手续费率模板>公司标准,同一产品和该产品的合约,合约优先级别大于产品,系统依次去寻找投资者的手续费率设置,直到找到相应设置为止。
手续费率设置后重新结算,就按新设置的手续费率计算手续费,盘中设置手续费率是实时上场。手续费率实时同步的设置在flex柜台界面的操作流程:
“费率设置->手续费率盘中同步参数设置->选择“是”->点击确认”。
针对投资者手续费率设置中,分为三种情况,一、设置所有投资者手续费率,二、设置手续费率模板手续费率,三、 设置单一投资者手续费率,单一投资者设置与公司标准设置相对比较简单,下面主要介绍一新开户投资者模板手续费率设置步骤。

对于新开户投资者手续费率设置步骤(模板):

  1. 设置投资者手续费率之前,要进行一些参数设置;
    ➢ 投资者手续费率设置是否启用复核流程,若启动复核流程,设置投资者手续费率时候要进行复核操作,复核通过之后手续费率设置才生效;
    Flex柜台界面操作:
    “流程管理->复核流程管理->流程ID选中投资者手续费率设置->查询”,
    “选中记录->修改(流程ID不允许修改) ->保存”
    是否启用:“是/否”
    最高复核级别:“零级复核”即不需要符合;“一级复核”即需要一次复核;“二级复核”需要复核两次;
    是否允许自复核:“是/否”,即选择一级、二级复核是可否由修改手续费的操作员自己进行复核。
    ➢ 投资者手续费率模板对应关系设置是否启动复核流程,若启动复核流程,设置投资者手续费率模板对应关系时候要进行复核操作,复核通过之后投资者手续费率模板对应关系设置才生效;
    Flex柜台界面操作:
    “流程管理->复核流程管理->流程ID选中投资者手续费率模板关系设置->查询”,选中记录->修改(流程ID不允许修改) ->保存
    是否启用:“是/否”
    最高复核级别:“零级复核”即不需要符合;“一级复核”即需要一次复核;“二级复核”需要复核两次;
    是否允许自复核:“是/否”,即选择- -级、二级复核是可否由修改手续费的操作员自己进行复核。
    ➢ 手续费率模板数据权限是否启用,若启用,则操作员只能操作和自己有关联关系的手续费率模板;若不启用,操作员可以操作所有手续费率模板;
    Flex柜台界面操作:
    “交易管理->基本参数设置->结算参数->手续费模板数据权限->启动”
  2. 创建投 资者手续费率模板;
    在实际业务操作中,为每一个投资者都单独设置一套 手续费率是不明智的。为了便于管理和操作的简洁性,手续费率设置中可以采用手续费率模板形式为投资者设置手续费率,以简化操作,故需要先创建一个模板。
    Flex柜台界面操作:
    “费率设置->手续费率->手续费率模板设置->新增(填写手续费模板代码、手续费模板名称及备注) ->点击确认
    查询:输入代码可以精确查询,“空”则为查询全部,同时在左下角可以查询模板手续费率及模板对应投资者;
    新增:添加手续费率模板;
    修改:选中所需修改手续费率模板,点击“修改”按钮,只能修改手续费率模板名称及备注;
    删除:选中所需修改手续费率模板,点击“删除”按钮。
  3. 建立操作 员和手续费率模板对应关系;
    模板创建完成之后,需要将某一操作员与此模板建立起对应关系,即此模板只有该操作员有权限。
    Flex柜台界面操作:
    “费率设置->手续费率->操作员对应手续费率模板权限管理->点击新增(填写之前创建的手续费率模板、操作员代码) ->点击确认”
    查询:选择手续费率模板或操作员代码,点击“查询”,若为“空”则查询所有模板;
    新增:添加手续费率模板与对应操作员之间的关系;
    删除:选中记录,点击“删除”按钮即可。
  4. 建立投资者手续 费率模板和投资者对应关系;
    需要将投资者归入一一个手续费率模板,这样投资者的手续费率会跟随该模板进行变动,且一个投资者只可以属于一个手续费率模板。
    Flex柜台界面操作:
    “费率设置->手续费率->投资者手续费率模板对应关系->点击新增(填写之前创建的手续费率模板、投资者代码) ->点击确认’
    查询:可以按照投资者代码、手续费率模板、投资者属性进行查询,若为空,则查询全部;
    新增:添加投资者手续费率模板对应关系;
    修改:只能修改手续费率模板字段,不能修改投资者代码;
    删除:选中记录,点击“删除”按钮即可;
    批量新增:新开户的同一属性投资者可以同时建立投资者手续费率模板对应关系;
    批量修改:同一属性投资者可以实现同步修改手续费率模板;
    批量删除:查询到的投资者与模板对应关系可以实现同时删除。
  5. 设置模板手续费率;
    在flex柜台中进行新增、修改操作,该项内容设置完成后就实现了对同一属性的投资者通过模板进行手续费率的设置。
    Flex界面操作流程:
    “费率设置->手续费率->模板手续费率设置->新增->填写费率模板、产品/合约、选择交易所->添加开仓、平仓、平今、交割手续费率(按金额和按手数) ->点击确认按钮”
    查询:可以按照费率模板、交易所进行查询,若为空,则查询全部;
    新增:添加模板手续费率的设置;
    修改:只能修改相关费率字段,不能修改费率模板、交易所、产品/合约;
    删除:选中记录,点击“删除”按钮即可;
    批量删除:可以对查询到的记录进行全部删除;
    复制:分为投资者复制和交易所复制,投资者复制将某一投资的手续费率设置复制到所创建的模板中:交易所产品复制是将所创建模板中某交易所的产品/合约复制到某交易所的相关产品/合约中。
    当然,以上过程的设置也可以通过“投资者手续费率设置->新增-> 投资者范围选模板->填写费率模板、产品/合约、选择交易所->添加开仓、平仓、平今、交割手续费率(按金额和按手数) ->点击确认按钮”来实现;
    按照模板手续费率设置流程,对于单一投资者和公司标准可以分别通过“投资者手续费率设置”和“单一投资者手续费率设置”来实现,步骤同上。
  6. 设置完成之后可以到“投资者费率查询”里进行确认投资者的手续费率设置是否正确;在“手续费率修改查询”里能查询修改记录。

新功能——手续费率模型:

对投资者手续费率进行批量调整时,可以通过手续费率模型来实现。可以通过创建一个模型A实现对当前投资者手续费率数据进行备份,同时将数据复制到一个新模型B,如果手续费进行批量调整时可以对模型B进行批量修改,激活启用,那么投资者手续费率即按照模型B来进行收取;若过段时间手续费率需要进行恢复以前设置,则激活模型A即可。
手续费率模型应用步骤如下:

  1. 创建投资者手续费率模型;
  2. 设置手续费率模型的相关费率;
  3. 对所创建模型进行激活操作。

在flex中界面操作:
▶“ 费率设置->手续费率模型管理->手续费率模型-> 点击新增->填写手续费率模型代码、名称、选择交易所和产品、备注->从当前数据创建或确认”
注:若点击“确认”,手续费率需要到“模型投资者手续费率”中进行添加设置,添加过程中是与所创建模型是相对应的,包括:交易所得选择、产品/合约的选择;若是“从当前数据创建”,则所创建模型即为当前手续费率数据的设置;注:当交易所为空时,表示该手续费率模型的适用范围为所有交易所所有产品,激活时将覆盖所有手续费率;创建模型时,当交易所不为空,产品为空时,表示该手续费率模型的适用范围为特定交易所所有产品,激活时将覆盖特定交易所的所有产品;当交易所、产品都不为空时,表示该手续费率模型的适用范围为特定交易所特定产品,激活时将覆盖特定交易所特定产品的手续费率。
▶“费率设置-> 手续费率模型管理->模型手续费率->新增->填写模型代码、选择交易所、产品/合约、投资者范围、添加开仓、平仓、平今、交割手续费率(按金额和按手数) ->点击确认按钮”
查询:可以按照模型代码、交易所、产品/合约、投资者范围、组织架构进行查询,若为空,则查询全部;
新增:添加模型手续费率的设置;
修改:只能修改相关费率字段,不能修改费率模板、交易所、产品/合约;
删除:选中记录,点击“删除”按钮即可;
批量修改:可以实现对公司范围、模板、单一投资者进行批量修改,可分为:绝对调整(调整后的值为当前调整)和相对调整(调整后的值为调整值加上原有值),调整完成后,可以进行试算操作,检查是否正确,若正确则点击确认按钮。
批量删除:可以对查询到的记录进行全部删除;
复制:是将一个手续费率模型复制到另一个手续费率模型,可以按照投资者范围、交易所的产品/合约进行复制,则目标的模型手续费率设置将被删除、替换为源的模型手续
费率。
▶在“手续费率模型”界面中,选中所需要模型,点击“激活”即可,这样就实现对一个新建模型的启用。
▲交易所手续费率、投资者手续费率设置都是实时生效的。

回测不需要行情服务器连接成功,必须datafeed配置正确并且可以连接成功,例如米筐、tushare或者tqdata等。

条件单规则

1. 简介

条件单是一个带有触发条件的指令。该触发条件可以以市场上的最新行情为基准,也可以以指定价格为基准。比如:一个投资者有1手IF1910的空头持仓,并希望在市场价低于2200时买入平仓,他就可以使用条件单。这样当行情波动到满足该条件时,该报单就会被自动触发报出,而不需要他本人时刻盯着电脑屏幕去监视市场行情。

申报条件单时,条件单的报单回报中OrderSysID是以字符串“TJBD_”开头的由CTP系统自定义的报单编号,针对该交易所当日唯一;只对成功申报的条件单编号,错单不编号。因为这种情况下的编号由CTP系统自行维护,与交易所无关,仅用于管理本系统内的该条件单。
有效的使用条件单,可以做出限价止损指令(stop-and-limit order)和触及市价指令(market-if-touched order)。
CTP条件单为CTP后台系统自带指令,并非交易所官方支持指令。

2.指令介绍

报入条件单指令使用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

© 2015-2022 微信 18391752892
备案服务号:沪ICP备18006526号

沪公网安备 31011502017034号

【用户协议】
【隐私政策】
【免责条款】