from AlgorithmImports import *
import numpy as np
from collections import deque


class HKUSTIntradayMomentum(QCAlgorithm):

    def initialize(self): # Исправлено: Initialize
        self.SetStartDate(2023, 1, 1)
        self.SetCash(1000000)
        self.max_order_percentage = 0.3

        symbol_list = [ 'AAPL', 'AMGN', 'AXP' ] #'BA', 'CAT', 'CRM',
        #'CSCO', 'CVX', 'DIS', 'DOW', 'GS', 'HD', 'HON', 'INTC',
        #'JNJ', 'JPM', 'KO', 'MCD', 'MMM', 'MRK', 'MSFT', 'NKE', 'PG', 'TRV',
        #'UNH', 'V', 'VZ', 'WMT', "AAP", "ACHR", "ADMA", "AI", "ALAB",
        #"APP", "AR", "ASTS", "AUR", "BE", "BILL", "BROS", "CART",
        #"CELH", "CHWY", "CLSK", "CORZ", "CRDO", "CVNA", "ENPH", "ETSY",
        #"FL", "GERN", "GME", "GO", "GSAT", "GT", "HIMS" ]

        self.SetWarmUp(timedelta(100))

        for symbol in symbol_list:
            equity = self.add_equity( # Исправлено: AddEquity
                symbol,
                Resolution.MINUTE,
                data_normalization_mode=DataNormalizationMode.TOTAL_RETURN
            )
            equity.SetMarginModel(PatternDayTradingMarginModel())
            equity._vwap = self.VWAP(equity.Symbol)
            equity._roc = self.ROCP(equity.Symbol, 1, Resolution.DAILY)
            equity._vol = IndicatorExtensions.Of(StandardDeviation(7), equity._roc)
            equity._deviation = AbsoluteDeviation('deviation', 60)
            equity._previous_date = None
            equity._open_price = None
            equity._previous_close = None
            equity._last_trade_date = None
            equity._bars = deque(maxlen=2)
            self.Consolidate(equity.Symbol, timedelta(minutes=30), self.consolidate_handler)

        self.Schedule.On(
            self.DateRules.EveryDay(symbol_list[0]),
            self.TimeRules.BeforeMarketClose(symbol_list[0], 1), # Исправлено: TimeRules.BeforeMarketClose
            self.end_of_day
        )

    def consolidate_handler(self, bar):
        symbol = bar.Symbol
        security = self.Securities[symbol]

        current_date = bar.end_time.date()

        security._bars.append(bar)

        if current_date != security._previous_date:
            security._previous_date = current_date
            security._open_price = bar.Open
            if len(security._bars) >= 2: # Исправлено: Проверка длины deque
                security._previous_close = security._bars[-2].Close
            else:
                security._previous_close = None

        security._deviation.update(bar)

        if not security._vol.IsReady or security._previous_close is None or not security._deviation.IsReady or security._vwap.Current is None: # Исправлено: Проверка на None для VWAP
            return

        upper_bound = (max(security._open_price, security._previous_close) * (1 + security._deviation.Value))
        lower_bound = (min(security._open_price, security._previous_close) * (1 - security._deviation.Value))

        vwap_price = security._vwap.Current.Value

        long_stop_price = max(vwap_price, upper_bound)
        short_stop_price = min(vwap_price, lower_bound)

        is_up_trend = bar.Close > vwap_price
        is_down_trend = bar.Close < vwap_price

        is_long = self.Portfolio[symbol].IsLong
        is_short = self.Portfolio[symbol].IsShort

        is_long_stopped_out = is_long and bar.Close <= long_stop_price
        is_short_stopped_out = is_short and bar.Close >= short_stop_price

        is_not_last_trade_date = security._last_trade_date != current_date

        if self.IsWarmingUp: # Исправлено: IsWarmingUp
            return

        if is_long_stopped_out or is_short_stopped_out:
            self.Liquidate(symbol)

        if bar.Close > upper_bound and not is_long and is_up_trend and is_not_last_trade_date:
            self.open_position(symbol, 1)
        elif bar.Close < lower_bound and not is_short and is_down_trend and is_not_last_trade_date:
            self.open_position(symbol, -1)

    def open_position(self, symbol, direction):
        security = self.Securities[symbol]
        min_order_size = security.SymbolProperties.MinimumOrderSize # Исправлено: SymbolProperties

        target_position_value = self.Portfolio.TotalPortfolioValue * self.max_order_percentage * direction
        quantity = int(target_position_value / security.Close)

        if direction > 0:
            quantity = max(quantity, min_order_size)
        else:
            quantity = min(quantity, -min_order_size)

        self.Debug(f"{symbol}: Target Value: {target_position_value}, Quantity: {quantity}, Close: {security.Close}, Min Order Size: {min_order_size}")

        if quantity != 0:
            self.SetHoldings(symbol, quantity) # Использование SetHoldings для внутридневной торговли
            security._last_trade_date = self.Time.date()

    def end_of_day(self):
        self.Liquidate()

class AbsoluteDeviation(PythonIndicator):

    def __init__(self, name, period):
        super().__init__()
        self.name = name
        self.period = period
        self.data = {}
        self.ready = False

        self.previous_data = None
        self.open_price = None

    def update(self, data: BaseData):
        current_data = data.end_time.date()

        if current_data != self.previous_data:
            self.previous_data = current_data
            self.open_price = data.open

        current_time = data.end_time.time()
        
        if current_time not in self.data:
            self.data[current_time] = deque(maxlen=self.period)

        self.data[current_time].append(
            np.abs(data.close / self.open_price - 1)
            )
        
        if len(self.data[current_time]) == self.period:
            self.ready = True
        
        self.value = np.mean(self.data[current_time])

        return len(self.data[current_time]) == self.period