<filename>examples/backtesting/strategies/as_strategy.py
from vnpy.app.cta_strategy import (
    CtaTemplate,
    StopOrder,
    TickData,
    BarData,
    TradeData,
    OrderData,
    BarGenerator,
    ArrayManager,
)
import time, datetime
import math

class AsStrategy(CtaTemplate):

    k = 0.1
    sigma = 1
    gamma = 0.5
    fixed_size = 1000

    delta_t = 0
    r = 0
    spread = 0
    bid_price = 0
    ask_price = 0
    last_bid_price = 0
    last_ask_price = 0

    milli_second_per_day = 86400000
    cover_only_milli = 1000 * 60 * 15
    cover_only_request = 400
    vt_orderids = []
    last_tick = None

    parameters = [
        "k",
        "sigma",
        "gamma",
        "fixed_size",
        "cover_only_milli",
        "cover_only_request",
    ]

    variables = [
        "delta_t",
        "r",
        "spread",
        "bid_price",
        "ask_price"
    ]

    def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
        """"""
        super().__init__(cta_engine, strategy_name, vt_symbol, setting)
        self.request_counter = 0
        self.last_now = datetime.datetime.now()
        
    
    def on_init(self):
        """
        Callback when strategy is inited.
        """
        self.write_log("策略初始化")
        self.load_tick(0)
        print("k: "+str(self.k))
        print("gamma: "+ str(self.gamma))
        print("sigma: "+ str(self.sigma))
        print("request limit: "+ str(self.cover_only_request))

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



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

    @classmethod
    def remain_milli_today(cls, now):
        milli_sec_now = int(round(now.timestamp() * 1000))
        zeroToday = now - datetime.timedelta(hours=now.hour, minutes=now.minute, seconds=now.second, microseconds=now.microsecond)
        milli_sec_endtime = int(round(zeroToday.timestamp()*1000)) + cls.milli_second_per_day
        remain_time = milli_sec_endtime - milli_sec_now
        return remain_time
   
    def new_minute(self, now):
        
        if now.minute != self.last_now.minute:
            return True
        return False


    def force_cover(self):
        pass

    def out_of_request_limit(self):
        if self.request_counter > self.cover_only_request:

            # print("out of request!! called: {}, limit: {}".format(self.request_counter, self.cover_only_request))
            return True
        return False
    
    def end_of_day(self, remain_time):
        if remain_time < self.cover_only_milli:
            return True
        return False
        


    def on_tick(self,tick: TickData):
        """
        Callback of new tick data update.
        """
        #get position: self.pos
        #calculate time left (T-t) delta_t 
        now = tick.datetime
        if self.new_minute(now):
            self.request_counter = 0
        

        self.last_now = now
        
        remain_time = self.remain_milli_today(now)

        if self.end_of_day(remain_time) or self.out_of_request_limit():
            cover_only = True
        else:
            cover_only = False

        self.delta_t = remain_time / self.milli_second_per_day

        #calculate indifferent price r(s,t) = s - q * gamma * sigma^2 * delta_t
        self.r = (tick.ask_price_1 + tick.bid_price_1)/2 - self.pos * self.gamma * self.sigma**2 * self.delta_t
        #calculate spread, spread = 2 / gamma * ln(1 + gamma/k)
        self.spread = self.gamma * self.sigma**2 * self.delta_t + 2 / self.gamma * math.log(1 + self.gamma / self.k)
        #calculate ask, ask_price = r + spread/2
        #calculate bid, bid_price = r - spread/2
        self.ask_price = self.round_up(self.r + self.spread/2, self.get_pricetick)
        self.bid_price = self.round_down(self.r - self.spread/2, self.get_pricetick)

        #如果上个tick提交的委托没成交
        if self.vt_orderids:
            #新tick的bid ask和上个tick不变，返回
            if(self.bid_price == self.last_bid_price and self.ask_price == self.last_ask_price):
                return
            #新tick的bid ask发生变化，撤旧单，发新单
            else:
                for orderid in self.vt_orderids:
                    self.cancel_remove_order(orderid)
                self.send_new_order(cover_only=cover_only)
        else:
            self.send_new_order(cover_only=cover_only)

        self.last_ask_price = self.ask_price
        self.last_bid_price = self.bid_price
        self.write_log("{}\n delta_t: {}, spread: {}, r: {}, request counter: {}".format(tick, 
                          self.delta_t, self.spread, self.r, self.request_counter))
            # self.last_tick = tick
    @staticmethod
    def round_up(price, price_tick):
        reminder = price % price_tick 
        rounded = x + price_tick - reminder
        return rounded

    @staticmethod
    def round_down(price, price_tick):
        rounded = price //  price_tick  * price_tick
        return rounded




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

    def on_trade(self, trade: TradeData):
        """
        Callback of new trade data update.
        """
        if trade.vt_orderid in self.vt_orderids:
            self.vt_orderids.remove(trade.vt_orderid)
        self.write_log(" order filled {}".format(trade))
    
    def cancel_remove_order(self, orderid):
            self.cancel_order(orderid)
            self.vt_orderids.remove(orderid)
            self.request_counter += 1
    # def force_cover(self):
    #     if self.pos = 0:
    #         return
    #     elif self.pos > 0:
    #         self.sell(self.pos)
    

            
    def send_new_order(self, cover_only=False):
        if self.pos == 0 and not cover_only:
            vt_orderids = self.buy(self.bid_price, self.fixed_size)
            self.vt_orderids.extend(vt_orderids)
            vt_orderids = self.short(self.ask_price, self.fixed_size)
            self.vt_orderids.extend(vt_orderids)
            self.request_counter += 2
        elif self.pos > 0:
            if not cover_only:
                vt_orderids = self.buy(self.bid_price, self.fixed_size)
                self.vt_orderids.extend(vt_orderids)
                self.request_counter += 1
            vt_orderids = self.sell(self.ask_price, self.fixed_size)
            self.vt_orderids.extend(vt_orderids)
            self.request_counter += 1
        elif self.pos < 0:
            if not cover_only:
                vt_orderids =self.short(self.ask_price, self.fixed_size)
                self.vt_orderids.extend(vt_orderids)
                self.request_counter += 1
            vt_orderids = self.cover(self.bid_price, self.fixed_size)
            self.vt_orderids.extend(vt_orderids)
            self.request_counter += 1
        


