<commit_before>
from collections import defaultdict
from typing import Dict, List, Set, Callable
from copy import copy

from vnpy.trader.object import (
    TickData, TradeData, OrderData, ContractData, BarData
)
from vnpy.trader.constant import Direction, Status, Offset, Interval
from vnpy.trader.utility import virtual, floor_to, ceil_to, round_to

from .base import SpreadData, calculate_inverse_volume


class SpreadAlgoTemplate:
    """
    Template for implementing spread trading algos.
    """
    algo_name = "AlgoTemplate"

    def __init__(
        self,
        algo_engine,
        algoid: str,
        spread: SpreadData,
        direction: Direction,
        offset: Offset,
        price: float,
        volume: float,
        payup: int,
        interval: int,
        lock: bool,
    ):
        """"""
        self.algo_engine = algo_engine
        self.algoid: str = algoid

        self.spread: SpreadData = spread
        self.spread_name: str = spread.name

        self.offset: Offset = offset
        self.direction: Direction = direction
        self.price: float = price
        self.volume: float = volume
        self.payup: int = payup
        self.interval = interval
        self.lock = lock

        if direction == Direction.LONG:
            self.target = volume
        else:
            self.target = -volume

        self.status: Status = Status.NOTTRADED  # Algo status
        self.count: int = 0                     # Timer count
        self.traded: float = 0                  # Volume traded
        self.traded_volume: float = 0           # Volume traded (Abs value)

        self.leg_traded: Dict[str, float] = defaultdict(int)
        self.leg_orders: Dict[str, List[str]] = defaultdict(list)

        self.order_trade_volume: Dict[str, int] = defaultdict(int)
        self.orders: Dict[str, OrderData] = {}

        self.write_log("算法已启动")

    def is_active(self):
        """"""
        if self.status not in [Status.CANCELLED, Status.ALLTRADED]:
            return True
        else:
            return False

    def check_order_finished(self):
        """"""
        finished = True

        for leg in self.spread.legs.values():
            vt_orderids = self.leg_orders[leg.vt_symbol]

            if vt_orderids:
                finished = False
                break

        return finished

    def check_hedge_finished(self):
        """"""
        active_symbol = self.spread.active_leg.vt_symbol
        active_traded = self.leg_traded[active_symbol]

        spread_volume = self.spread.calculate_spread_volume(
            active_symbol, active_traded
        )

        finished = True

        for leg in self.spread.passive_legs:
            passive_symbol = leg.vt_symbol

            leg_target = self.spread.calculate_leg_volume(
                passive_symbol, spread_volume
            )
            leg_traded = self.leg_traded[passive_symbol]

            # For linear contract, traded volume must be same as target volume
            if not self.spread.is_inverse(leg.vt_symbol):
                if leg_traded != leg_target:
                    finished = False
                    break
            # For inverse contract, the difference must be lower than min volume
            else:
                leg_min_volume = self.spread.calculate_leg_volume(
                    passive_symbol, self.spread.min_volume
                )

                dif = leg_target - leg_traded
                if abs(dif) < leg_min_volume:
                    finished = False
                    break

        return finished

    def check_algo_finished(self) -> bool:
        """"""
        finished = True

        for vt_symbol, leg in self.spread.legs.items():
            leg_traded = self.leg_traded[vt_symbol]

            trading_multiplier = self.spread.trading_multipliers[vt_symbol]
            size = self.spread.get_leg_size(vt_symbol)

            if self.spread.is_inverse(vt_symbol):
                leg_target = calculate_inverse_volume(
                    self.target * trading_multiplier,
                    leg.last_price,
                    size
                )
                min_change = calculate_inverse_volume(
                    leg.min_volume,
                    leg.last_price,
                    size
                )
            else:
                leg_target = self.target * trading_multiplier
                min_change = leg.min_volume

            leg_left = leg_target - leg_traded
            if abs(leg_left) >= min_change:
                finished = False

        return finished

    def stop(self):
        """"""
        if self.is_active():
            self.cancel_all_order()
            self.status = Status.CANCELLED
            self.write_log("算法已停止")
            self.put_event()

    def update_tick(self, tick: TickData):
        """"""
        self.on_tick(tick)

    def update_trade(self, trade: TradeData):
        """"""
        # For inverse contract:
        # record coin trading volume as leg trading volume,
        # not contract volume!
        if self.spread.is_inverse(trade.vt_symbol):
            size = self.spread.get_leg_size(trade.vt_symbol)

            trade_volume = calculate_inverse_volume(
                trade.volume,
                trade.price,
                size
            )
        else:
            trade_volume = trade.volume

        if trade.direction == Direction.LONG:
            self.leg_traded[trade.vt_symbol] += trade_volume
        else:
            self.leg_traded[trade.vt_symbol] -= trade_volume

        self.calculate_traded()

        # Sum up total traded volume of each order,
        self.order_trade_volume[trade.vt_orderid] += trade.volume

        # Remove order from active list if all volume traded
        order = self.orders[trade.vt_orderid]
        trade_volume = self.order_trade_volume[order.vt_orderid]

        if trade_volume == order.volume:
            vt_orderids = self.leg_orders[order.vt_symbol]
            if order.vt_orderid in vt_orderids:
                vt_orderids.remove(order.vt_orderid)

        msg = "委托成交，{}，{}，{}@{}".format(
            trade.vt_symbol,
            trade.direction,
            trade.volume,
            trade.price
        )
        self.write_log(msg)

        self.put_event()
        self.on_trade(trade)

    def update_order(self, order: OrderData):
        """"""
        self.orders[order.vt_orderid] = order

        # Remove order from active list if rejected or cancelled
        if order.status in {Status.REJECTED, Status.CANCELLED}:
            vt_orderids = self.leg_orders[order.vt_symbol]
            if order.vt_orderid in vt_orderids:
                vt_orderids.remove(order.vt_orderid)<commit_msg>[Fix] bug when checking hedge finished spread containing inverse contract<commit_after>
from collections import defaultdict
from typing import Dict, List, Set, Callable
from copy import copy

from vnpy.trader.object import (
    TickData, TradeData, OrderData, ContractData, BarData
)
from vnpy.trader.constant import Direction, Status, Offset, Interval
from vnpy.trader.utility import virtual, floor_to, ceil_to, round_to

from .base import SpreadData, calculate_inverse_volume


class SpreadAlgoTemplate:
    """
    Template for implementing spread trading algos.
    """
    algo_name = "AlgoTemplate"

    def __init__(
        self,
        algo_engine,
        algoid: str,
        spread: SpreadData,
        direction: Direction,
        offset: Offset,
        price: float,
        volume: float,
        payup: int,
        interval: int,
        lock: bool,
    ):
        """"""
        self.algo_engine = algo_engine
        self.algoid: str = algoid

        self.spread: SpreadData = spread
        self.spread_name: str = spread.name

        self.offset: Offset = offset
        self.direction: Direction = direction
        self.price: float = price
        self.volume: float = volume
        self.payup: int = payup
        self.interval = interval
        self.lock = lock

        if direction == Direction.LONG:
            self.target = volume
        else:
            self.target = -volume

        self.status: Status = Status.NOTTRADED  # Algo status
        self.count: int = 0                     # Timer count
        self.traded: float = 0                  # Volume traded
        self.traded_volume: float = 0           # Volume traded (Abs value)

        self.leg_traded: Dict[str, float] = defaultdict(int)
        self.leg_orders: Dict[str, List[str]] = defaultdict(list)

        self.order_trade_volume: Dict[str, int] = defaultdict(int)
        self.orders: Dict[str, OrderData] = {}

        self.write_log("算法已启动")

    def is_active(self):
        """"""
        if self.status not in [Status.CANCELLED, Status.ALLTRADED]:
            return True
        else:
            return False

    def check_order_finished(self):
        """"""
        finished = True

        for leg in self.spread.legs.values():
            vt_orderids = self.leg_orders[leg.vt_symbol]

            if vt_orderids:
                finished = False
                break

        return finished

    def check_hedge_finished(self):
        """"""
        active_symbol = self.spread.active_leg.vt_symbol
        active_traded = self.leg_traded[active_symbol]

        spread_volume = self.spread.calculate_spread_volume(
            active_symbol, active_traded
        )
        
        finished = True

        for leg in self.spread.passive_legs:
            passive_symbol = leg.vt_symbol

            leg_target = self.spread.calculate_leg_volume(
                passive_symbol, spread_volume
            )
            leg_traded = self.leg_traded[passive_symbol]

            # For linear contract, traded volume must be no less than target volume
            if not self.spread.is_inverse(leg.vt_symbol):
                if leg_target > 0 and leg_traded < leg_target:
                    finished = False
                elif leg_target < 0 and leg_traded > leg_target:
                    finished = False
            # For inverse contract, the difference must be lower than min volume
            else:
                leg_min_volume = self.spread.calculate_leg_volume(
                    passive_symbol, self.spread.min_volume
                )

                dif = leg_target - leg_traded
                if leg_target > 0 and dif >= leg_min_volume:
                    finished = False
                elif leg_target < 0 and dif <= -leg_min_volume:
                    finished = False

            if not finished:
                break

        return finished

    def check_algo_finished(self) -> bool:
        """"""
        finished = True

        for vt_symbol, leg in self.spread.legs.items():
            leg_traded = self.leg_traded[vt_symbol]

            trading_multiplier = self.spread.trading_multipliers[vt_symbol]
            size = self.spread.get_leg_size(vt_symbol)

            if self.spread.is_inverse(vt_symbol):
                leg_target = calculate_inverse_volume(
                    self.target * trading_multiplier,
                    leg.last_price,
                    size
                )
                min_change = calculate_inverse_volume(
                    leg.min_volume,
                    leg.last_price,
                    size
                )
            else:
                leg_target = self.target * trading_multiplier
                min_change = leg.min_volume

            leg_left = leg_target - leg_traded
            if abs(leg_left) >= min_change:
                finished = False

        return finished

    def stop(self):
        """"""
        if self.is_active():
            self.cancel_all_order()
            self.status = Status.CANCELLED
            self.write_log("算法已停止")
            self.put_event()

    def update_tick(self, tick: TickData):
        """"""
        self.on_tick(tick)

    def update_trade(self, trade: TradeData):
        """"""
        # For inverse contract:
        # record coin trading volume as leg trading volume,
        # not contract volume!
        if self.spread.is_inverse(trade.vt_symbol):
            size = self.spread.get_leg_size(trade.vt_symbol)

            trade_volume = calculate_inverse_volume(
                trade.volume,
                trade.price,
                size
            )
        else:
            trade_volume = trade.volume

        if trade.direction == Direction.LONG:
            self.leg_traded[trade.vt_symbol] += trade_volume
        else:
            self.leg_traded[trade.vt_symbol] -= trade_volume

        self.calculate_traded()

        # Sum up total traded volume of each order,
        self.order_trade_volume[trade.vt_orderid] += trade.volume

        # Remove order from active list if all volume traded
        order = self.orders[trade.vt_orderid]
        contract = self.get_contract(trade.vt_symbol)

        trade_volume = round_to(
            self.order_trade_volume[order.vt_orderid],
            contract.min_volume
        )

        if trade_volume == order.volume:
            vt_orderids = self.leg_orders[order.vt_symbol]
            if order.vt_orderid in vt_orderids:
                vt_orderids.remove(order.vt_orderid)

        msg = "委托成交，{}，{}，{}@{}".format(
            trade.vt_symbol,
            trade.direction,
            trade.volume,
            trade.price
        )
        self.write_log(msg)

        self.put_event()
        self.on_trade(trade)

    def update_order(self, order: OrderData):
        """"""
        self.orders[order.vt_orderid] = order

        # Remove order from active list if rejected or cancelled
        if order.status in {Status.REJECTED, Status.CANCELLED}:
            vt_orderids = self.leg_orders[order.vt_symbol]
            if order.vt_orderid in vt_orderids:
                vt_orderids.remove(order.vt_orderid)
{'fraction_non_alphanumeric': 0.07997702634790724, 'fraction_numerical': 0.0007179266278986287, 'mean_word_length': 1.722298221614227, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1348527', 'n_tokens_mistral': 4038, 'n_tokens_neox': 3823, 'n_words': 875}