VeighNa量化社区
你的开源社区量化交易平台
Member
avatar
加入于:
帖子: 420
声望: 173

说明:本帖只讨论了如何解决CTP接口中,集合竞价后立即下单被拒的问题
对集合竞价不太了解的童鞋们可以参阅 期货集合竞价时间期货集合竞价规则

1. 集合竞价之后开盘价往往是策略频繁出现信号的时候

做量化交易的都知道,在各个交易日的前都有集合竞价时段,集合竞价产生该交易日的开盘价。这个开盘价格是自上一个交易日结束至今所有市场因素对交易对象价格影响的集中体现,这些因素既包括基本面、技术面和消息面等因素!
行情也往往出现这些时候,尤其在经过一个交叉的节假日或者突发利空或者利多消息出现的时候更是如此!

2. 在收到集合竞价之后的开盘价下单会被拒

行情出现了,当然策略就会发出信号,进而下单交易。
可是当策略收到收到开盘价的时候,市场还处在集合竞价阶段,此时下单将会被拒(相信大家都遇到过),而这个问题目前vnpy并没有解决。
如果是手工操盘,当然没有问题,可是我们的通过使用策略或者算法来交易的,怎么可以不解决这个问题 ?!!!

2.1 解决因为开盘价下单会被拒问题的思路

此时下单会被拒主要是因为市场还处在集合竞价状态,我们应该选择在连续竞价状态时下单即可。
就这么简单,可是实现起来有点儿复杂。
你可以把下单的逻辑简单地规定为21:00~23:00,9:00~11:30和13:30~15:00这几个时段才可以下单,其他时段禁止下单。
这倒是也可以解决一部分问题,但这样总会顾头不顾尾,很多品种你是无法处理的!

3. 新版的CTP接口协议中增加了合约交易状态通知

3.1 合约交易状态通知推送接口函数

下面是合约交易状态通知推送接口函数和合约交易状态通知数据结构。
合约交易状态通知,主动推送。私有流回报。

◇ 1.函数原型virtual void OnRtnInstrumentStatus(CThostFtdcInstrumentStatusField *pInstrumentStatus) {};

回到顶部

◇ 2.参数pInstrumentStatus:合约状态

struct CThostFtdcInstrumentStatusField
{
    ///交易所代码
    TThostFtdcExchangeIDType ExchangeID;
    ///合约在交易所的代码
    TThostFtdcExchangeInstIDType ExchangeInstID;
    ///结算组代码
    TThostFtdcSettlementGroupIDType SettlementGroupID;
    ///合约代码
    TThostFtdcInstrumentIDType InstrumentID;
    ///合约交易状态
    TThostFtdcInstrumentStatusType InstrumentStatus;
    ///交易阶段编号
    TThostFtdcTradingSegmentSNType TradingSegmentSN;
    ///进入本状态时间
    TThostFtdcTimeType EnterTime;
    ///进入本状态原因
    TThostFtdcInstStatusEnterReasonType EnterReason;
};

◇ 3.返回无

3.2 下面合约当前所处的交易状态C语言类型和枚举值

/////////////////////////////////////////////////////////////////////////
///TFtdcInstrumentStatusType是一个合约交易状态类型
/////////////////////////////////////////////////////////////////////////
///开盘前
#define THOST_FTDC_IS_BeforeTrading '0'
///非交易
#define THOST_FTDC_IS_NoTrading '1'
///连续交易
#define THOST_FTDC_IS_Continous '2'
///集合竞价报单
#define THOST_FTDC_IS_AuctionOrdering '3'
///集合竞价价格平衡
#define THOST_FTDC_IS_AuctionBalance '4'
///集合竞价撮合
#define THOST_FTDC_IS_AuctionMatch '5'
///收盘
#define THOST_FTDC_IS_Closed '6'

typedef char TThostFtdcInstrumentStatusType;

4. 解决CTP接口中集合竞价后立即下单被拒问题的实现步骤

4.1 定义相关的常量和数据类

在vnpy\trader\constant.py中增加下面的合约交易状态InstrumentStatus常量类型定义:

class InstrumentStatus(Enum):
    """
    合约交易状态类型 hxxjava debug
    """
    BEFORE_TRADING = "开盘前"
    NO_TRADING = "非交易"
    CONTINOUS = "连续交易" 
    AUCTION_ORDERING = "集合竞价报单"
    AUCTION_BALANCE = "集合竞价价格平衡"
    AUCTION_MATCH = "集合竞价撮合"
    CLOSE = "收盘"

在vnpy\trader\object.py中增加下面的交易状态数据类StatusData:

@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 : str = ""  
    # 合约在交易所的代码       
    exchange_inst_id : str = ""     

    def __post_init__(self):
        """  """
        self.vt_symbol = f"{self.symbol}.{self.exchange.value}"

    def belongs_to(self,vt_symbol:str):
        """ 判断状态是否属于一个合约 """
        symbol,exchange_str = vt_symbol.split(".")
        instrument = left_alphas(symbol).upper()
        return (self.symbol.upper() == instrument) and (self.exchange.value == exchange_str)

在vnpy\trader\event.py中增加交易状态消息类型

EVENT_STATUS = "eStatus"                        # hxxjava debug

4.2 为gateway添加on_status()函数

在vnpy\trader\gateway.py中修改网关父类gateway,为其增加on_status()函数:

    def on_status(self, status: StatusData) -> None:    # hxxjava debug
        """
        Instrument Status event push.
        """
        self.on_event(EVENT_STATUS, status)

4.3 为CTP接口中的TdApi接口

在vnpy_ctp\gateway\ctp_gateway.py中为CtpTdApi增加交易状态通知推送接口函数onRtnInstrumentStatus():

    def onRtnInstrumentStatus(self,data:dict):
        """ 
        当接收到合约品种状态信息 # hxxjava debug 
        """
        if data:
            # print(f"【data={data}】")
            status =  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
            )
            # print(f"status={status}")
            self.gateway.on_status(status)

4.3.1 别忘了其中用到的两个映射字典:

映射字典INSTRUMENTSTATUS_CTP2VT:

# 品种状态进入原因映射  hxxjava debug
INSTRUMENTSTATUS_CTP2VT: Dict[str, InstrumentStatus] = {
    "0": InstrumentStatus.BEFORE_TRADING,
    "1": InstrumentStatus.NO_TRADING,
    "2": InstrumentStatus.CONTINOUS,
    "3": InstrumentStatus.AUCTION_ORDERING,
    "4": InstrumentStatus.AUCTION_BALANCE,
    "5": InstrumentStatus.AUCTION_MATCH,
    "6": InstrumentStatus.CLOSE,
    "7": InstrumentStatus.CLOSE
}

映射字典ENTERREASON_CTP2VT:

# 品种状态进入原因映射  hxxjava debug
ENTERREASON_CTP2VT: Dict[str, StatusEnterReason] = {
    "1": StatusEnterReason.AUTOMATIC,
    "2": StatusEnterReason.MANUAL,
    "3": StatusEnterReason.FUSE
}

4.4 将交易状态通知保存在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.statuses: Dict[str, StatusData] = {}       # hxxjava debug
        self.active_orders: Dict[str, OrderData] = {}

        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_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_active_orders = self.get_all_active_orders
        self.main_engine.get_all_statuses = self.get_all_statuses       # hxxjava debug
        self.main_engine.get_status = self.get_status                   # hxxjava debug

    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_STATUS, self.process_status_event)  # hxxjava debug

    def process_tick_event(self, event: Event) -> None:
        """"""
        tick = event.data
        self.ticks[tick.vt_symbol] = tick

    def process_order_event(self, event: Event) -> None:
        """"""
        order = event.data
        self.orders[order.vt_orderid] = order

        # If order is active, then update data in dict.
        if order.is_active():
            self.active_orders[order.vt_orderid] = order
        # Otherwise, pop inactive order from in dict
        elif order.vt_orderid in self.active_orders:
            self.active_orders.pop(order.vt_orderid)

    def process_trade_event(self, event: Event) -> None:
        """"""
        trade = event.data
        self.trades[trade.vt_tradeid] = trade

    def process_position_event(self, event: Event) -> None:
        """"""
        position = event.data
        self.positions[position.vt_positionid] = position

    def process_account_event(self, event: Event) -> None:
        """"""
        account = event.data
        self.accounts[account.vt_accountid] = account

    def process_contract_event(self, event: Event) -> None:
        """"""
        contract = event.data
        self.contracts[contract.vt_symbol] = contract

    def process_status_event(self, event: Event) -> None:   # hxxjava debug
        """"""
        status = event.data
        # print(f"got a status = {status}")
        self.statuses[status.vt_symbol] = status

    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_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_statuses(self) -> List[StatusData]:     # hxxjava debug
        """
        Get all status data.
        """
        return list(self.statuses.values())

    def get_status(self,vt_symbol:str) -> List[StatusData]:     # hxxjava debug
        """
        Get the vt_symbol's status data.
        """
        symbol,exchange_str = vt_symbol.split('.')
        instrment = left_alphas(symbol)
        instrment_vt_symbol = f"{instrment}.{exchange_str}"
        return self.statuses.get(instrment_vt_symbol,None)

    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

5. 如何使用交易状态通知相关函数?

5.1 修改CTA交易策略

5.1.1 修改交易模板CtaTemplate

修改vnpy\app\cta_strategy\template.py,增加得到交易合约当前交易状态的函数get_trade_status():

    def get_trade_status(self):
        """
        得到交易合约当前交易状态
        """
        main_engine = self.cta_engine.main_engine
        return main_engine.get_status(self.vt_symbol)

5.1.2 修改您的CTA策略下单控制

在你的策略中调用buy()、sell()、short()、cover()或者send_order()下单函数之前,使用类似这样的判断:
下面使用某个CTA策略的on_bar()函数的来说明对交易状态的使用。

    def on_bar(self, bar: BarData):
        """
        Callback of new bar data update.
        """
        self.cancel_all()

        am = self.am
        am.update_bar(bar)
        if not am.inited:
            return

        atr_array = am.atr(self.atr_length, array=True)
        self.atr_value = atr_array[-1]
        self.atr_ma = atr_array[-self.atr_ma_length:].mean()
        self.rsi_value = am.rsi(self.rsi_length)

        if self.get_trade_status() != InstrumentStatus.CONTINOUS:      
             # 注意:这里,如果当前合约不在连续竞价状态,放弃下面的信号计算和下单操作!!!
             self.put_event()
             return

        if self.pos == 0:
            self.intra_trade_high = bar.high_price
            self.intra_trade_low = bar.low_price

            if self.atr_value > self.atr_ma:
                if self.rsi_value > self.rsi_buy:
                    self.buy(bar.close_price + 5, self.fixed_size)
                elif self.rsi_value < self.rsi_sell:
                    self.short(bar.close_price - 5, self.fixed_size)

        elif self.pos > 0:
            self.intra_trade_high = max(self.intra_trade_high, bar.high_price)
            self.intra_trade_low = bar.low_price

            long_stop = self.intra_trade_high * \
                (1 - self.trailing_percent / 100)
            self.sell(long_stop, abs(self.pos), stop=True)

        elif self.pos < 0:
            self.intra_trade_low = min(self.intra_trade_low, bar.low_price)
            self.intra_trade_high = bar.high_price

            short_stop = self.intra_trade_low * \
                (1 + self.trailing_percent / 100)
            self.cover(short_stop, abs(self.pos), stop=True)

        self.put_event()

5.2 修改价差交易相关模板及价差交易策略

5.2.1 修改SpreadAlgoTemplate

修改vnpy\app\spread_trading\template.py中的SpreadAlgoTemplate模板,增加get_legs_status()读取价差算法的各个腿当前交易状态:

    def get_legs_status(self):
        """
        得到当前策略所交易的价差交易状态    # hxxjava add
        """
        legs_status:Dict[str:InstrumentStatus] = {}
        main_engine = self.algo_engine.main_engine
        for leg in self.spread.legs.values():
            astatus:StatusData = main_engine.get_status(leg.vt_symbol)
            legs_status[leg.vt_symbol] = astatus.instrument_status
        return legs_status

5.2.2 修改SpreadStrategyTemplate

修改vnpy\app\spread_trading\template.py中的SpreadStrategyTemplate模板,增加get_legs_status()读取价差交易策略的各个腿当前交易状态:

    def get_legs_status(self):
        """
        得到当前策略所交易的价差交易状态    # hxxjava add
        """
        legs_status:Dict[str:InstrumentStatus] = {}
        main_engine = self.strategy_engine.main_engine
        for leg in self.spread.legs.values():
            astatus:StatusData = main_engine.get_status(leg.vt_symbol)
            legs_status[leg.vt_symbol] = astatus.instrument_status
        return legs_status

5.2.3 修改您的价差交易策略下单控制

在你的价差交易策略start_long_algo()和start_short_algo()下单函数之前,使用类似这样的判断:
下面使用某个价差交易策略的on_spread_data()函数的来说明对交易状态的使用,交易指令部分无需纠结从哪里来,只是举例。

    def on_spread_data(self):
        """
        Callback when spread price is updated.
        """
        tick = self.get_spread_tick()

        self.bg.update_tick(tick)

        # 得到价差交易策略的各个腿的交易状态
        legs_status = list(self.get_legs_status().values())

        # 这里要求价差交易策略的每个腿的交易状态均为连续竞价状态
        is_continous_status = legs_status.count(InstrumentStatus.CONTINOUS) == len(self.spread.legs)

        if self.trading and is_continous_status:
            # 这里是交易指令,无需纠结代码从哪里来
            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))

5.3 其他模块如何修改?

如果您感兴趣,其他的模块,如算法交易模块algo、投资组合模块PortfolioStrategy等,在对交易状态信息的使用,可以参考上述的CTA策略米筐和价差交易模块的做法,应该不难。
也许您也和我一样饱受这个问题的困扰,希望本人的辛苦付出能够帮到你!

记住:分享是一种美德

Administrator
avatar
加入于:
帖子: 4502
声望: 321

感谢分享,必须加个精华!

Member
avatar
加入于:
帖子: 23
声望: 0

description

description
手上持有空单,计算的空平触发价格应该是4744(前一天结算价为4800),即高于4744时就会止损。当天开盘价4711,未达到止损价,但是开盘系统直接触发了价格为0的多单进行平仓,也就是无条件平仓?麻烦问一下时哪里出了问题呢?是因为收盘价高于止损价的原因吗?(没有把开盘价格推送进去?)
备注:策略里用的停止单。

Member
avatar
加入于:
帖子: 420
声望: 173

陈慧 wrote:

description

description
手上持有空单,计算的空平触发价格应该是4744(前一天结算价为4800),即高于4744时就会止损。当天开盘价4711,未达到止损价,但是开盘系统直接触发了价格为0的多单进行平仓,也就是无条件平仓?麻烦问一下时哪里出了问题呢?是因为收盘价高于止损价的原因吗?(没有把开盘价格推送进去?)
备注:策略里用的停止单。

答复

  1. 没有策略思路和代码,你这样的提问方法,让人如何回答?
  2. 开盘系统直接触发了价格为0单进行平仓,看你图中是用了停止单的。
  3. 可以重点监视你send_order()里的价格参数,如果采用的是最新K线的close_price吗,那就是你集合竞价产生的K线错误了。
  4. 如果想详细讨论,可以私信代码给我讨论。
Member
avatar
加入于:
帖子: 23
声望: 0

hxxjava wrote:

陈慧 wrote:

description

description
手上持有空单,计算的空平触发价格应该是4744(前一天结算价为4800),即高于4744时就会止损。当天开盘价4711,未达到止损价,但是开盘系统直接触发了价格为0的多单进行平仓,也就是无条件平仓?麻烦问一下时哪里出了问题呢?是因为收盘价高于止损价的原因吗?(没有把开盘价格推送进去?)
备注:策略里用的停止单。

答复

  1. 没有策略思路和代码,你这样的提问方法,让人如何回答?
  2. 开盘系统直接触发了价格为0单进行平仓,看你图中是用了停止单的。
  3. 可以重点监视你send_order()里的价格参数,如果采用的是最新K线的close_price吗,那就是你集合竞价产生的K线错误了。
  4. 如果想详细讨论,可以私信代码给我讨论。

代码如下,麻烦大神帮忙看一下,开盘有时会出现价格为0的平仓,有时不会,不知道为什么,谢谢
class RuStrategy(CtaTemplate):
""""""
entry_window = 100
exit_window = 85
atr_window = 80
fixed_size =1
entry_dev = 1 # 入场通道宽度
exit_dev = 1 # 出场通道宽度
rsi_window=80
rsi_signal=15

entry_up = 0
entry_down = 0
exit_up = 0
exit_down = 0
atr_value = 0
rsi_value=0

long_entry = 0
short_entry = 0
long_stop = 0
short_stop = 0

parameters = ["entry_window", "exit_window", "atr_window","fixed_size","entry_dev","exit_dev","rsi_window","rsi_signal"]
variables =["entry_up", "entry_down", "exit_up", "exit_down", "atr_value"]

def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
    """"""
    super().__init__(cta_engine, strategy_name, vt_symbol, setting)

    self.bg = BarGenerator(
        self.on_bar,
        window=1,
        on_window_bar=self.on_hour_bar,
        interval=Interval.HOUR)
    self.am = ArrayManager(120)

def on_init(self):
    """
    Callback when strategy is inited.
    """
    self.write_log("策略初始化")
    self.load_bar(30)

def on_start(self):
    """
    Callback when strategy is started.
    """
    self.write_log("策略启动")

def on_stop(self):
    """
    Callback when strategy is stopped.
    """
    self.write_log("策略停止")

def on_tick(self, tick: TickData):
    """
    Callback of new tick data update.
    """
    self.bg.update_tick(tick)

def on_bar(self, bar: BarData):
    self.bg.update_bar(bar)
def on_hour_bar(self, bar: BarData):
    """
    Callback of new bar data update.
    """
    self.cancel_all()
    self.am.update_bar(bar)
    if not self.am.inited:
        return

    # Only calculates new entry channel when no position holding
    keltnerEntryUp, keltnerEntryDown = self.am.keltner(self.entry_window,self.entry_dev)
    keltnerExitUp, keltnerExitDown = self.am.keltner(self.exit_window,self.exit_dev)

    donchianEntryUp, donchianEntryDown = self.am.donchian(self.entry_window)
    donchianExitUp, donchianExitDown = self.am.donchian(self.exit_window)
    self.rsi_value=self.am.rsi(self.rsi_window)
    if not self.pos:
        self.entry_up = max(donchianEntryUp, keltnerEntryUp)
        self.entry_down = min(donchianEntryDown, keltnerEntryDown)

    self.exit_up = min(keltnerExitUp, donchianExitUp)
    self.exit_down = max(keltnerExitDown, donchianExitDown)


    if not self.pos:
        self.atr_value = self.am.atr(self.atr_window)

        self.long_entry = 0
        self.short_entry = 0
        self.long_stop = 0
        self.short_stop = 0

        self.send_buy_orders(self.entry_up)
        self.send_short_orders(self.entry_down)
    elif self.pos > 0:
        self.send_buy_orders(self.entry_up)

        sell_price = max(self.long_stop, self.exit_down)
        self.sell(sell_price, abs(self.pos), True)

    elif self.pos < 0:
        self.send_short_orders(self.entry_down)

        cover_price = min(self.short_stop, self.exit_up)
        self.cover(cover_price, abs(self.pos), True)

    self.put_event()

def on_trade(self, trade: TradeData):
    """
    Callback of new trade data update.
    """
    if trade.direction == Direction.LONG:
        self.long_entry = trade.price
        self.long_stop = self.long_entry - 2* self.atr_value
    else:
        self.short_entry = trade.price
        self.short_stop = self.short_entry + 2 * self.atr_value

def on_order(self, order: OrderData):
    """
    Callback of new order data update.
    """
    pass

def on_stop_order(self, stop_order: StopOrder):
    """
    Callback of stop order update.
    """
    pass

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

    if t < 1 and self.rsi_value<=(50+self.rsi_signal) :
        self.buy(price, self.fixed_size*2, True)

    if t < 2 and self.rsi_value<=(50+self.rsi_signal):
        self.buy(price + self.atr_value*0.5 , self.fixed_size*2, 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)

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

    if t > -1 and self.rsi_value>=(50-self.rsi_signal) :
        self.short(price, self.fixed_size*4, True)
Member
avatar
加入于:
帖子: 420
声望: 173

1. 对class RuStrategy(CtaTemplate)的代码分析如下:

class RuStrategy(CtaTemplate):
    """"""
    entry_window = 100
    exit_window = 85
    atr_window = 80
    fixed_size =1
    entry_dev = 1 # 入场通道宽度
    exit_dev = 1 # 出场通道宽度
    rsi_window=80
    rsi_signal=15

    entry_up = 0
    entry_down = 0
    exit_up = 0
    exit_down = 0
    atr_value = 0
    rsi_value=0

    long_entry = 0
    short_entry = 0
    long_stop = 0
    short_stop = 0

    parameters = ["entry_window", "exit_window", "atr_window","fixed_size","entry_dev","exit_dev","rsi_window","rsi_signal"]
    variables =["entry_up", "entry_down", "exit_up", "exit_down", "atr_value"]

    def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
        """"""
        super().__init__(cta_engine, strategy_name, vt_symbol, setting)

        self.bg = BarGenerator(
            self.on_bar,
            window=1,
            on_window_bar=self.on_hour_bar,
            interval=Interval.HOUR)
        self.am = ArrayManager(120)

    def on_init(self):
        """
        Callback when strategy is inited.
        """
        self.write_log("策略初始化")
        self.load_bar(30)

    def on_start(self):
        """
        Callback when strategy is started.
        """
        self.write_log("策略启动")

    def on_stop(self):
        """
        Callback when strategy is stopped.
        """
        self.write_log("策略停止")

    def on_tick(self, tick: TickData):
        """
        Callback of new tick data update.
        """
        self.bg.update_tick(tick)

    def on_bar(self, bar: BarData):
        self.bg.update_bar(bar)

    def on_hour_bar(self, bar: BarData):
        """
        Callback of new bar data update.
        """
        self.cancel_all()  # 放弃之前所有未成交委托单
        self.am.update_bar(bar)
        if not self.am.inited:
            return

        # Only calculates new entry channel when no position holding
        keltnerEntryUp, keltnerEntryDown = self.am.keltner(self.entry_window,self.entry_dev)
        keltnerExitUp, keltnerExitDown = self.am.keltner(self.exit_window,self.exit_dev)

        donchianEntryUp, donchianEntryDown = self.am.donchian(self.entry_window)
        donchianExitUp, donchianExitDown = self.am.donchian(self.exit_window)
        self.rsi_value=self.am.rsi(self.rsi_window)
        if not self.pos:  # 如果没有持仓
            # 做多开仓价=100日keltner上沿与100日donchian上沿的最高价
            self.entry_up = max(donchianEntryUp, keltnerEntryUp)
            # 做空开仓价=100日keltner下沿与100日donchian下沿的最低价
            self.entry_down = min(donchianEntryDown, keltnerEntryDown)

        # 多单止盈价=85日keltner上沿与85日donchian上沿的最低价
        self.exit_up = min(keltnerExitUp, donchianExitUp)
        # 空单止盈价=85日keltner下沿与85日donchian下沿的最高价
        self.exit_down = max(keltnerExitDown, donchianExitDown)

        if not self.pos: # 如果没有持仓
            self.atr_value = self.am.atr(self.atr_window)  # 80日平均涨幅

            self.long_entry = 0
            self.short_entry = 0
            self.long_stop = 0
            self.short_stop = 0

            self.send_buy_orders(self.entry_up)     # 发出做多停止单,价格为做多开仓价
            self.send_short_orders(self.entry_down) # 发出做空停止单,价格为做空开仓价

        elif self.pos > 0: # 如果持多仓
            self.send_buy_orders(self.entry_up)     # 发出做多停止单,价格为做多开仓价 (补全不足的多仓)

            sell_price = max(self.long_stop, self.exit_down) # 多单平仓价=max(多单止盈价,多单止损价格)
            self.sell(sell_price, abs(self.pos), True) # 以多单平仓价发出平全部仓停止单

        elif self.pos < 0: # 如果持空仓
            self.send_short_orders(self.entry_down) # 发出做空停止单,价格为做空开仓价(补全不足的空仓)

            cover_price = min(self.short_stop, self.exit_up) # 空单平仓价=max(空单止盈价,空单止损价格)
            self.cover(cover_price, abs(self.pos), True)     # 以空单平仓价发出平全部空仓停止单

        self.put_event()

    def on_trade(self, trade: TradeData):
        """
        Callback of new trade data update.
        """
        if trade.direction == Direction.LONG:
            # 开多仓成功后,计算并且记录止损价格=开仓价-2倍平均真实涨幅
            self.long_entry = trade.price
            self.long_stop = self.long_entry - 2* self.atr_value
        else:
            # 开空仓成功后,计算并且记录止损价格=开仓价+2倍平均真实涨幅
            self.short_entry = trade.price
            self.short_stop = self.short_entry + 2 * self.atr_value

    def on_order(self, order: OrderData):
        """
        Callback of new order data update.
        """
        pass

    def on_stop_order(self, stop_order: StopOrder):
        """
        Callback of stop order update.
        """
        pass

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


        if t < 1 and self.rsi_value<=(50+self.rsi_signal) :
            # 持多仓不足1份且self.rsi_value在65之下,发出2份做多委托停止单,委托价格为price
            self.buy(price, self.fixed_size*2, True)

        if t < 2 and self.rsi_value<=(50+self.rsi_signal):
            # 持多仓不足2份且self.rsi_value在65之下,发出2份做多委托停止单,委托价格为price+self.atr_value*0.5
            self.buy(price + self.atr_value*0.5 , self.fixed_size*2, 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)

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

        if t > -1 and self.rsi_value>=(50-self.rsi_signal) :
            # 持空仓不足1份且self.rsi_value在35之上,发出4份做空委托停止单,委托价格为price
            self.short(price, self.fixed_size*4, True)

description

2. 问题出在self.short_stop和self.long_stop上

2.1 当策略开多仓/空仓成功之后

执行了:

def on_trade(self, trade: TradeData):
    """
    Callback of new trade data update.
    """
    if trade.direction == Direction.LONG:
        self.long_entry = trade.price
        self.long_stop = self.long_entry - 2* self.atr_value
    else:
        self.short_entry = trade.price
        self.short_stop = self.short_entry + 2 * self.atr_value
  • self.long_stop 为多仓的开仓价-2 * self.atr_value
  • self.short_stop为空仓的开仓价+2 * self.atr_value

2.2 当符合平空仓条件时

    elif self.pos < 0:
        self.send_short_orders(self.entry_down)

        cover_price = min(self.short_stop, self.exit_up)  # cover_price ,平空仓的价格为self.short_stop和self.exit_up的小者
        self.cover(cover_price, abs(self.pos), True)

2.2.1 当持有空仓后策略没有重新启动时

  • self.short_stop为空仓开仓时的止损价格,
  • self.exit_up为当前keltner通道下沿和dochain通道下沿的小者
  • 此时的cover_price是正确的

2.2.2 当持有空仓后策略重新启动过

  • self.short_stop为0
  • self.exit_up为当前keltner通道下沿和dochain通道下沿的小者
  • 此时的cover_price=0,错误!

2.3 当策略持有多仓后的情况与2.2一样

以你现在的代码,只是发现平空仓不对,其实策略持有多仓也可能遇到同样的问题。

3. 解决方法

把"long_stop","short_stop"放入variables 列表,variables 修改成这样:

variables =["entry_up", "entry_down", "exit_up", "exit_down", "atr_value","long_stop","short_stop"]

这样无论开仓后,策略是否被重新启动过,self.long_stop,self.short_stop都记住多仓或空仓的止损价格。

Member
avatar
加入于:
帖子: 23
声望: 0

description
感谢大神细致准确的分析,应该是这个问题,但是我加了这两个变量后,策略初始化就一直有问题,没有办法计算变量值,1h线走完了也还是显示值为0,但回测还可以正常出结果。v_v

Member
avatar
加入于:
帖子: 420
声望: 173

陈慧 wrote:

description
感谢大神细致准确的分析,应该是这个问题,但是我加了这两个变量后,策略初始化就一直有问题,没有办法计算变量值,1h线走完了也还是显示值为0,但回测还可以正常出结果。v_v

答复:

  • 修改了策略代码后,假设你的策略实例名称是“RuStrategy_xxx”,你需要到你的用户目录.vntrader\下cta_strategy_data.json,修改:

"RuStrategy_xxx": {
      "pos": ??,
      "entry_up": ??, 
      ... ...
      "long_stop":??,
      "short_stop:??" 
    },

其中long_stop和short_stop的值??,按照你当前策略事件运行的情况计算一下,替代其中的??。

  • 如果那样嫌麻烦,可以手动平仓策略RuStrategy_xxx开过的仓位,干脆删除cta_strategy_data.json中的RuStrategy_xxx项,然后重新加载、运行RuStrategy_xxx策略。
Member
avatar
加入于:
帖子: 23
声望: 0

问题解决了,谢谢大神!^-^

© 2015-2022 上海韦纳软件科技有限公司
备案服务号:沪ICP备18006526号

沪公网安备 31011502017034号

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