Overall Statistics
Total Orders
218
Average Win
13.50%
Average Loss
-4.89%
Compounding Annual Return
72.594%
Drawdown
33.500%
Expectancy
1.173
Start Equity
1000.00
End Equity
159678.08
Net Profit
15867.808%
Sharpe Ratio
2.201
Sortino Ratio
2.71
Probabilistic Sharpe Ratio
97.806%
Loss Rate
42%
Win Rate
58%
Profit-Loss Ratio
2.76
Alpha
0.517
Beta
0.235
Annual Standard Deviation
0.377
Annual Variance
0.142
Information Ratio
-0.785
Tracking Error
0.641
Treynor Ratio
3.533
Total Fees
$11717.57
Estimated Strategy Capacity
$3700000000000000.00
Lowest Capacity Asset
BTCUSD 2XR
Portfolio Turnover
6.41%
# region imports
from AlgorithmImports import *
# endregion

# Your New Python File

class PortfolioManager():

    def __init__(self, algo):
        self.algo = algo

    def HasHoldings(self, symbol):
        
        if self.algo.Securities[symbol].Type == SecurityType.Crypto:
            ## TODO: Explore a better way to do this. Not clear how base_currency.amount should be used
            min_lot_size    = self.algo.securities[symbol].symbol_properties.lot_size
            asset           = self.algo.securities[symbol]
            base_currency   = asset.base_currency
            # quantity        = min(asset.holdings.quantity, base_currency.amount)
            quantity        = abs(asset.holdings.quantity)

            # abs(self.securities[self.symbol].symbol_properties.lot_size - self.securities[self.symbol].holdings.quantity) > 0.000000001
            return abs(quantity) >= self.algo.securities[symbol].symbol_properties.minimum_order_size
            # return abs(quantity - min_lot_size) > min_lot_size
        else:
            return self.algo.Portfolio[symbol].Invested
# region imports
from AlgorithmImports import *
# endregion

# Your New Python File

class TradeManager():

    def __init__(self, algo):
        self.algo = algo

    # Convenience method to liquidate with a message
    def LiquidateWithMsg(self, symbol, exitReason):
        
        pnl         = round(100 * self.algo.Portfolio[symbol].UnrealizedProfitPercent,2)
        biasText    = 'Long' if (self.algo.Portfolio[symbol].IsLong) else 'Short'
        winlossText = 'win' if pnl > 0 else 'loss'        
        orderNote   = f"[{pnl}% {winlossText}] {exitReason} | Exiting {biasText} position" 

        
        # If Crypto, call LiquidateMarketOrder
        if self.algo.Securities[symbol].Type == SecurityType.Crypto:
            self.LiquidateMarketOrder(symbol=symbol, tag=orderNote)
        else:
            self.algo.liquidate(symbol, tag=orderNote)


    ## Liquidate via market order. Necessary for crypto
    def LiquidateMarketOrder(self, symbol, tag):
        crypto = self.algo.securities[symbol]
        base_currency = crypto.base_currency

        # Avoid negative amount after liquidate
        quantity = min(crypto.holdings.quantity, base_currency.amount)

        # Round down to observe the lot size
        lot_size = crypto.symbol_properties.lot_size;
        quantity = (round(quantity / lot_size) - 1) * lot_size

        if self.is_valid_order_size(crypto, quantity):
            # self.algo.debug(f"------------ [START] Market Order: liquidation start")
            self.algo.debug(f" Liquidating:  {quantity} units of {symbol.Value}")
            self.algo.market_order(symbol, -quantity, tag=tag)
            self.algo.debug(f"Market Order liquidation was Successful")
            self.algo.debug(f" Leftover: {crypto.holdings.quantity} units of {symbol.Value}")
            self.algo.debug(f"------------ [END] Market Order liquidation")
            if( abs(crypto.holdings.quantity) > lot_size):
                self.LiquidateMarketOrder(symbol, tag="reomving trailing coins")
        else:
            self.algo.debug(f"ERROR ERRROR ---- ")
            self.algo.debug(f"ERROR ERRROR Invalid order size: {quantity}")

    # Brokerages have different order size rules
    # Binance considers the minimum volume (price x quantity):
    def is_valid_order_size(self, crypto, quantity):
        return abs(crypto.price * quantity) > crypto.symbol_properties.minimum_order_size
    
#region imports
from AlgorithmImports import *
#endregion


class TrailingStopHelper():
    
    def __init__(self, algo, symbol, volaIndicator=None, trailStopCoeff=2, initialStopCoeff=0.5, activationCoeff=1):

    
        # Track state and indicators 
        self.algo                   = algo
        self.symbol                 = symbol 
        self.entryPrice             = 0
        self.ExitMessage            = ""
        self.systemActivated        = False
        self.volaIndicator          = volaIndicator
        self.volaIndicator.Updated += self.OnATRIndicatorUpdated
        self.portfolioBias          = PortfolioBias.LONG
        # Stop Loss States
        self.trailStopCoeff     = trailStopCoeff
        self.initialStopCoeff   = initialStopCoeff
        self.activationCoeff    = activationCoeff
        self.ResetStopLosses()
            

    @property
    def lastPrice(self):
        if (self.symbol in self.algo.Securities \
            and self.algo.Securities[self.symbol] is not None):

            return self.algo.Securities[self.symbol].Price
        
        return 0

    ## -----------------------------------------------
    def OnATRIndicatorUpdated(self, sender, updated):        
        self.PlotCharts()

    
    # Trailing Stop Exit    
    # This method updates the trailing stop
    # ============================================
    def TrailingExitSignalFired(self):
        if( not self.volaIndicator.IsReady ):
            return False   

        # If trailing stop is NOT set, get last price, and set it
        # --------------------------------------------------------
        if( not self.stopsLossActivated ):
            self.highestPrice       = self.lastPrice
            self.trailingStopLoss   = self.lastPrice - (self.volaIndicator.Current.Value * self.trailStopCoeff)

            
            self.stopsLossActivated = True
            
            # Recursively call this function to check for stops
            # again, now that the trailing stop has been activated
            # and the stop loss value has been updated.
            # --------------------------------------------------
            return self.TrailingExitSignalFired()            
            
        # If trailing stop loss is activated, check if price closed below it.
        # If it did, then exit. If not, update the trailing stop loss. 
        else: 
            if self.PriceIsBelowStopLoss():              
                return True

            else:
                # If price has gone up
                if self.lastPrice > self.highestPrice:

                    # If price is above the trail activation price, update trailing stop
                    if self.lastPrice > self.activationPrice:

                        self.highestPrice       = self.lastPrice
                        newTrailingStopLoss     = self.highestPrice -  (self.volaIndicator.Current.Value * self.trailStopCoeff)                           
                        self.trailingStopLoss   = max (self.trailingStopLoss, newTrailingStopLoss, self.activationPrice)
                                        
                    # check again just in case price ends up below the new trailing stop level
                    if self.PriceIsBelowStopLoss():
                        return True

        return False 

    ## Check if price is below trailing stop loss or regular stop loss
    ## ---------------------------------------------------------------
    def PriceIsBelowStopLoss(self):
            
            if self.lastPrice > self.activationPrice:
                if( self.lastPrice < self.trailingStopLoss ):
                    self.ExitMessage = "Trailing Stop Loss"
                    return True
            else:
                if( self.lastPrice < self.initialStopLoss ):
                    self.ExitMessage = "Initial Stop Loss"
                    return True

            self.PlotCharts()

            return False

    ## Logic to run immediately after a new position is opened.
    ## We track entry price and set initial stop loss values
    ## ---------------------------------------------------------
    def Activate(self, entryPrice, portfolioBias=PortfolioBias.LONG):            
    
        self.portfolioBias      = PortfolioBias.LONG
        self.entryPrice         = entryPrice
        self.systemActivated    = True
        self.SetInitialStops()
        return
        
    ## Set initial stop and activation level. Called after new position opened.
    ## ------------------------------------------------------------------------
    def SetInitialStops(self):
        ## TODO: Use onOrderEvent to set this, because the actual price may be different
        self.entryPrice        = self.lastPrice
        
        self.initialStopLoss   = self.entryPrice - (self.volaIndicator.Current.Value * self.initialStopCoeff)  
        self.activationPrice   = self.entryPrice + (self.volaIndicator.Current.Value * self.activationCoeff)  

    
    ## Logic to run immediately after a position is closed
    ## Reset exit message, stop loss values.
    ## ---------------------------------------------------
    def Deactivate(self):            
        self.PlotCharts()
        self.ExitMessage      = "No Exit Message"
        self.systemActivated  = False
        self.ResetStopLosses()
        
    ## Reset stop losses    
    ## -------------------------------------------------
    def ResetStopLosses(self):
        self.stopsLossActivated   = False
        self.initialStopLoss      = 0
        self.activationPrice      = 0
        self.trailingStopLoss     = 0
        

    ## Plot Price, Stop losses & activation levels
    ## -------------------------------------------
    def PlotCharts(self):    
        # return
        self.algo.Plot(f"{self.symbol} Trailing stop", "Price", self.lastPrice)        
        self.algo.Plot(f"{self.symbol} Trailing stop", "Initial Stop", self.initialStopLoss)
        self.algo.Plot(f"{self.symbol} Trailing stop", "Acivation Pt", self.activationPrice)
        self.algo.Plot(f"{self.symbol} Trailing stop", "TrailingStop", self.trailingStopLoss)
                
        return 
from AlgorithmImports import *
import numpy as np

class Trendilo(PythonIndicator):
    """
    Trendilo Indicator for QuantConnect

    This indicator calculates the percentage change of the closing price,
    and then calculates the Arnaud Legoux Moving Average (ALMA) of the 
    percentage change. The ALMA is compared to a root mean square (RMS) 
    band to determine the trend direction.

    Parameters:
    - name (str): The name of the indicator
    - smoothing (int): The lookback period for percentage change calculation 
    - lookback (int): The lookback period for ALMA calculation
    - alma_offset (float): The offset parameter for ALMA calculation
    - alma_sigma (int): The sigma parameter for ALMA calculation
    - band_multiplier (float): The multiplier for the RMS band calculation
    - custom_band_length (int): The length for RMS band calculation
    """

    def __init__(self, smoothing=1, 
                 lookback=50, 
                 alma_offset=0.85, 
                 alma_sigma=6, 
                 band_multiplier=1.0):
        
        # btc has higher volatility -- can use volatility 
        # param adjustment base don volatility
        # consider reducing sigma to 4-5 to catch 
        # with qqq being les volatility c, try reducing to 0.75 or 0.70

        # ---
        # adjust sigma to 
        # start with sigma of 1.5-2
        # reduce offseit slighty to .10

        
        self.Value = 0  # Current value of the indicator
        
        self.smoothing = smoothing  # NOTE: this is really just a lookback period for rate of change.
        self.lookback = lookback
        self.alma_offset = alma_offset
        self.alma_sigma = alma_sigma
        self.band_multiplier = band_multiplier
        self.custom_band_length = lookback
        
        # Initialize ALMA indicator
        self._alma = ArnaudLegouxMovingAverage(period=lookback, sigma=alma_sigma,offset=alma_offset)
        

        # Initialize data structures for calculations
        
        self.price_window = RollingWindow[float](smoothing + 1)  # +1 to calculate change
        self.alma_values = RollingWindow[float](max(lookback, self.custom_band_length))
        self.alma_trend_window = RollingWindow[float](max(lookback, self.custom_band_length))

        # Additional indicator outputs
        self.rms = 0
        self.alma_value = 0
        self.trend_direction = 0
        
        # Set warm-up period
        self.WarmUpPeriod = max(lookback, self.custom_band_length, smoothing)

    @property
    def IsReady(self) -> bool:
        """
        Check if the indicator is ready to produce valid results.
        """
        return self._alma.IsReady and self.price_window.IsReady and self.alma_values.IsReady and self.alma_trend_window.IsReady 

    def Update(self, input_data) -> bool:
        """
        Update the Trendilo indicator with new price data.

        Parameters:
        - input_data (IBaseDataBar): The latest price bar data

        Returns:
        - bool: True if the indicator is ready, False otherwise
        """
        if input_data is None:
            return False

        # Add the latest closing price to our window
        self.price_window.Add(input_data.Close)

        if not self.price_window.IsReady:
            return False

        # Calculate smoothed percentage change
        smoothed_price_change = (self.price_window[0] - self.price_window[self.smoothing]) / self.price_window[self.smoothing] * 100

        # Update ALMA with smoothed percentage change
        self._alma.Update(input_data.EndTime, smoothed_price_change)

        if self._alma.IsReady:
            alma_value = self._alma.Current.value
            self.alma_values.Add(alma_value)

            if (self.alma_values.count) >= self.custom_band_length:
                # Calculate RMS (Root Mean Square)
                squared_sum = sum(av * av for av in list(self.alma_values)[:self.custom_band_length])
                self.rms = self.band_multiplier * np.sqrt(squared_sum / self.custom_band_length)

                # Determine trend direction
                if alma_value > self.rms:
                    self.trend_direction = 1  # Uptrend
                elif alma_value < -self.rms:
                    self.trend_direction = -1  # Downtrend
                else:
                    self.trend_direction = 0  # Sideways

                self.alma_trend_window.Add(self.trend_direction)

                # Set the current value of the indicator
                self.Value = alma_value
                self.alma_value = alma_value

        return self.IsReady
from AlgorithmImports import *
from collections import deque
import numpy as np
from datetime import datetime

class VFI(PythonIndicator):
    """
    The VFI (Volume Flow Indicator) Indicator:

    Purpose:
        - Measures the money flow in and out of an asset based on volume and price movements over a specified period.
        - Utilizes the concept of positive and negative money flow to determine the dominant market direction.

    Calculation:
        1. Compute the typical price for the current period as the average of high, low, and close prices.
        2. Determine the money flow volume as the product of volume and the difference between the current typical price and the previous typical price.
        3. Separate the positive and negative money flows based on whether the current typical price is higher or lower than the previous typical price.
        4. Calculate the VFI as the difference between the positive and negative money flows divided by their sum.
        5. Apply a Simple Moving Average (SMA) to smooth the VFI values over a specified signal period.

    Usage:
        - The VFI can be used to identify bullish or bearish trends based on the net money flow direction.
        - Positive VFI values suggest bullish trends, while negative values suggest bearish trends.

    Parameters:
        - 'period': The lookback period over which to calculate the VFI.
        - 'signal_period': The period over which to apply the SMA to smooth the final VFI values.

    Returns:
        - The smoothed VFI indicator, providing a measure of the money flow direction and strength, useful for trend identification and trading decisions.
    """

    def __init__(self, period, signal_period):
        """
        Initialize the VFI indicator.

        :param period: The lookback period over which to calculate the VFI.
        :param signal_period: The period over which to apply the SMA to smooth the VFI values.
        """
        self.period = period
        self.signal_period = signal_period
        self.sma = SimpleMovingAverage(signal_period)
        self.prev_typical_price = None
        self.money_flows = deque(maxlen=period)
        self.Value = 0
        self.WarmUpPeriod = max(period, signal_period)

    @property
    def IsReady(self) -> bool:
        """
        Check if the indicator is ready.

        :return: True if the indicator is ready, False otherwise.
        """
        return self.sma.IsReady and len(self.money_flows) == self.money_flows.maxlen

    def Reset(self):
        """Reset the indicator to its initial state."""
        self.sma.Reset()
        self.prev_typical_price = None
        self.money_flows.clear()
        self.Value = 0

    def Update(self, input_data):
        """
        Update the VFI indicator with the latest price data.

        :param input_data: The input price data (bar).
        :return: True if the indicator is ready, False otherwise.
        """
        if input_data is None:
            return False

        typical_price = (input_data.High + input_data.Low + input_data.Close) / 3

        if self.prev_typical_price is not None:
            money_flow_volume = input_data.Volume * (typical_price - self.prev_typical_price)

            self.money_flows.append(money_flow_volume)

            positive_flows = sum(mf for mf in self.money_flows if mf > 0)
            negative_flows = -sum(mf for mf in self.money_flows if mf < 0)

            vfi = 0 if (positive_flows + negative_flows) == 0 else (positive_flows - negative_flows) / (positive_flows + negative_flows)
            self.sma.Update(datetime.min, vfi)
            self.Value = self.sma.Current.Value

        self.prev_typical_price = typical_price

        return self.IsReady
from AlgorithmImports import *
from PortfolioManager import PortfolioManager
from TradeManager import TradeManager
from Trendilo import Trendilo  
from TrailingStopHelper import *
from VFI import VFI

class TrendiloTester (QCAlgorithm):
    """

    Trendilo-Based Trading Strategy

    This strategy implements a trend-following approach using the Trendilo indicator,
    which combines percentage change analysis with an Arnaud Legoux Moving Average (ALMA)
    and a Root Mean Square (RMS) band.

    Key Features:
    - Uses Trendilo indicator for trend identification
    - Enters long positions when ALMA crosses above the positive RMS band
    - Exits positions when ALMA drops below the positive RMS band
    - Optimized for Bitcoin (BTC) daily timeframe

    Note: Performance may vary significantly across different assets and timeframes.
    Further optimization and risk management techniques are recommended for live trading.


    Future Considerations for 15/30min scaping (full detail below):
    ---------------------------------------------------------------
    - Use KAMA as the primary trend filter for Trendilo signals
    - Incorporate VWAP for support/resistance and position sizing
    - Use ATR for stop-loss placement and trailing
    - Employ VFI as a final confirmation before entering trades
    """

    def Initialize(self):
        self.InitParameters()
        self.InitBacktest()
        self.InitData()
        self.InitIndicators()
        self.InitManagers()

    ## Init Parameters
    def InitParameters(self):
        self.optimizationMode   = int(self.get_parameter("optimizationMode")) == 1
        self.useTrailingStop    = int(self.get_parameter("useTrailingStop")) == 1
        self.useVFIRising       = int(self.get_parameter("useVFIRising")) == 1
        self.mainTFInMins       = int(self.get_parameter("mainTFInMins"))
        self.useLinRegSlope     = int(self.get_parameter("useLinRegSlope")) == 1
        self.tradeLongs         = int(self.get_parameter("tradeLongs")) == 1
        self.tradeShorts        = int(self.get_parameter("tradeShorts")) == 1
        self.useConsolidator    = False
        
        
        # Track state
        self.stop_price = 0
        self.tp_price   = 0
        self.direction  = PortfolioBias.LONG

        if self.mainTFInMins == 1440:
            self.mainTF             = Resolution.Daily
        
        elif self.mainTFInMins == 60:
            self.mainTF             = Resolution.Hour
        
        else:
            
            self.mainTF             = Resolution.Minute
            self.useConsolidator    = True

            
        if self.optimizationMode:
            self.sigma              = int(self.get_parameter("sigma") )
            self.offset             = float(self.get_parameter("offset") )
            self.lookback           = int(self.get_parameter("lookback"))
        else:        
            if self.mainTF == Resolution.Hour:
                ## Hourly
                self.sigma      = 8
                self.offset     = 0.8
                self.lookback   = 70
                

            elif self.mainTF == Resolution.Daily:
                # Daily params
                self.sigma      = 6
                self.offset     = 0.85
                self.lookback   = 50
                self.mainTF     = Resolution.Daily
                

            elif self.useConsolidator:
                self.sigma      = 8
                self.offset     = 0.8
                self.lookback   = 70 
                
                                
    ## Backtest Init
    def InitBacktest(self):        

        self.SetStartDate(2015, 4, 28)
        # self.SetEndDate(2022, 8, 1)
        # self.SetEndDate(2024, 3, 18)

        # self.SetStartDate(2022, 5, 1)
        # # self.SetEndDate(2023, 11, 30)

        # self.SetStartDate(2020, 6, 1)
        # self.SetEndDate(2022, 4, 1)
        self.SetCash(1000)

    
    ## Subscribe to asset feed, etc
    def InitData(self):
        
        # self.symbol = self.AddEquity("QQQ", self.mainTF).Symbol
        self.symbol = self.AddCrypto("BTCUSD", self.mainTF).Symbol
        self.SetBenchmark(SecurityType.Crypto, "BTCUSD")
        self.SetBrokerageModel(BrokerageName.BINANCE, AccountType.Margin)

        if self.useConsolidator:            
            self.mainTFConsolidator = TradeBarConsolidator(timedelta(minutes = self.mainTFInMins)) 
            self.mainTFConsolidator.DataConsolidated += self.OnDataMainTimeframe
            self.SubscriptionManager.AddConsolidator(self.symbol, self.mainTFConsolidator) 
            

    def InitManagers(self):
        self.FolioMgr = PortfolioManager(self)
        self.TradeMgr = TradeManager(self)
        self.trailingStop  = TrailingStopHelper(self,self.symbol,self.my_atr)

    ## Initialize Indicators
    def InitIndicators(self):
        
        # Main indicators
        # ---------------------

        self.my_atr    = AverageTrueRange(5, MovingAverageType.Wilders)
        self.my_linreg = RegressionChannel(2, 30 )        
        self.my_vfi    = VFI(50, 3)
        self.trendilo  = Trendilo( lookback=self.lookback, alma_offset = self.offset, alma_sigma  = self.sigma)

       # Warm-up period to ensure indicators are ready
        # self.SetWarmUp(60)
        
        # self.warm_up_indicator(self.symbol, self.trendilo)
        # self.warm_up_indicator(self.symbol, self.my_linreg)
        # self.warm_up_indicator(self.symbol, self.my_atr)


    def OnDataMainTimeframe(self, sender, bar):        
        if( self.useConsolidator ):
            self.UpdateIndicatorsWithBar(bar)

            # TODO: Determine if we need to check "self.IsWarmingUp":" here
            self.ExecuteOnSignals()

    ## Called with every data slice
    def OnData(self, data):
        if( not self.useConsolidator ):
            if data.Bars.ContainsKey(self.symbol):
                self.UpdateIndicatorsWithBar(data.Bars[self.symbol])

                if not self.IsWarmingUp:
                    self.ExecuteOnSignals()
        
    def UpdateIndicatorsWithBar(self, tradeBar):
        if(tradeBar is not None): 
            self.my_atr.Update(tradeBar)    
            self.my_linreg.Update(self.Time, tradeBar.Close)    
            self.my_vfi.Update(tradeBar)    
            self.trendilo.Update(tradeBar)
            

    def ExecuteOnSignals(self):
        if( not self.trendilo.IsReady):
            return

        self.PlotCharts()

        if not ( self.FolioMgr.HasHoldings(self.symbol)):    

            ## TODO: use 'If EntrySignalFired(): EnterPosition()
            ## Long Entry
            ## ------------------
            
            ## Default : Simple
            ## If alma is above rms (trend going up)
            ## ------------------------------------------
            if (self.tradeLongs) and (self.trendilo.trend_direction == 1):

            ## If alma crossed above 0
            ## ------------------------------------------
            # if (self.tradeLongs) and (self.trendilo.alma_values[1] < 0 <self.trendilo.alma_values[0]):

            ## If alma just crossed above rms (trend just started going up)
            # trendiloWindow = self.trendilo.alma_trend_window
            # if (self.tradeLongs) and ((trendiloWindow[0] == 1) and (trendiloWindow[1] != 1)):

            ## If alma just crossed above rms and alma rising (trend just started going up)
            # if (self.tradeLongs) and (self.trendilo.trend_direction == 1):

                if ( not self.useLinRegSlope ) or (self.my_linreg.slope.current.value > 0) :
                    self.SetHoldings(self.symbol,1, tag="LONG Entry")
                    self.direction = PortfolioBias.LONG
                    if( self.useTrailingStop ):
                        self.trailingStop.Activate(self.CurrentSlice[self.symbol].Close)

            ## Short Entry
            ## ------------------
            elif (self.tradeShorts) and (self.trendilo.trend_direction == -1):
                if ( not self.useLinRegSlope ) or (self.my_linreg.slope.current.value < 0) :
                    self.SetHoldings(self.symbol,-0.5, tag="SHORT Entry")
                    self.direction = PortfolioBias.SHORT
                    if( self.useTrailingStop ):
                        self.trailingStop.Activate(self.CurrentSlice[self.symbol].Close, PortfolioBias.Short)

        else:

            ## Trail Exit
            ## ------------------
            ## TODO: use 'If ExitSignalFired(): ExitPosition()
            if( self.useTrailingStop ):
                if  self.trailingStop.TrailingExitSignalFired():
                    self.exitMsg = self.trailingStop.ExitMessage
                    self.ExitPosition(self.exitMsg)
                    return

            ## Trendilo Exit
            ## ------------------                    
            else:
                if ( self.direction == PortfolioBias.LONG ) and (self.trendilo.trend_direction != 1):
                    self.ExitPosition("Uptrend Over")
                elif ( self.direction == PortfolioBias.SHORT ) and (self.trendilo.trend_direction != -1):
                    self.ExitPosition("Downtrend Over")

        # # Determine entry and exit signals
        # if( self.FolioMgr.HasHoldings(self.symbol)):   
        #     lot_size = self.securities[self.symbol].symbol_properties.lot_size
        #     self.Debug(f"We have holdings ({self.securities[self.symbol].holdings.quantity}). (Lot size: {lot_size}) Try to exit") 
        #     self.CheckForExits()
        # else:
        #     self.Debug(f"NO holdings. Try to Enter") 
        #     self.CheckForEntry()

    
    def PlotCharts(self):
        self.plot("trendilo", "posrsm",  self.trendilo.rms)
        self.plot("trendilo", "negrsm",  -self.trendilo.rms)
        self.plot("trendilo", "almaval", self.trendilo.alma_value)
        self.plot("trendilo", "almaval", self.trendilo.alma_value)
        

    ## Legacy Code for Entry
    ## ---------------------------
    # def CheckForEntry(self):
    
    #     self.direction = PortfolioBias.Long
    #     self.stop_price = current_price - 2 * atr_value
    #     self.tp_price = current_price + 3 * atr_value

    ## Legacy Code for Exit
    ## ---------------------------
    # def CheckForExits(self):
    #
    #     current_price = self.securities[self.symbol].price        
    #     price_str = f"SL:{self.stop_price} | Price:{current_price} | TP:{self.tp_price }"
    #
    #     if self.direction == PortfolioBias.Long:
    #         if current_price >= self.tp_price or current_price <= self.stop_price:
    #             self.Debug(f"Long Exit Signal Fired {price_str}") 
    #             self.ExitPosition(f"Long Position Exit {price_str}")
    #     elif self.direction == PortfolioBias.Short:
    #         if current_price <= self.tp_price or current_price >= self.stop_price:
    #             self.Debug(f"Short Exit Signal Fired {price_str}") 
    #             self.ExitPosition(f"Short Position Exit {price_str}")
    #     else:
    #         self.Debug(f"No Exit Signal") 

    def ExitPosition(self, reason):
        
        self.TradeMgr.LiquidateWithMsg(self.symbol,reason)
        self.Debug(f"{reason} at {self.Time}. Current Price: {self.Securities[self.symbol].Price}")
        self.direction = None
        self.stop_price = 0
        self.tp_price = 0

'''

Improvement Suggestions
==========================
1. Considerations for KAMA (Kaufman's Adaptive Moving Average):
Use KAMA as a dynamic trend filter
• Enter long positions only when Trendilo ALMA is above KAMA
• Exit positions when Trendilo ALMA crosses below KAMA
Adjust Trendilo entries based on KAMA slope
• More aggressive entries when KAMA slope is steeper
• More conservative entries when KAMA slope is flatter


2. Considerations for VWAP (Volume Weighted Average Price):
Use as a key support/resistance level
• Look for Trendilo signals that align with VWAP crossovers
• Increase position size when price is above VWAP for long trades
Implement VWAP bands (e.g., 1 standard deviation)
• Use upper band as a potential take-profit level
• Use lower band as an additional stop-loss criterion


3. Considerations for KER (Kaufman Efficiency Ratio):
Use KER to adjust Trendilo sensitivity
• Tighten Trendilo RMS bands when KER indicates high efficiency (strong trend)
• Widen Trendilo RMS bands when KER indicates low efficiency (choppy market)
Filter Trendilo signals based on KER threshold
• Only take signals when KER is above a certain threshold (e.g., 0.6)


4. Considerations for ATR (Average True Range):
Implement dynamic position sizing
• Scale position size inversely to ATR (larger positions in lower volatility)
Use ATR for stop-loss placement
• Set initial stop-loss at 1-2 ATR below entry for long positions
• Trail stop-loss using ATR (e.g., 2 ATR below recent high)


5. Considerations for VFI (Volume Flow Indicator):
Use VFI as a volume-based confirmation of Trendilo signals
• Enter long positions only when VFI is positive and rising
• Exit or reduce position size when VFI turns negative
Implement VFI divergence analysis
• Look for bullish divergence (price making lower lows, VFI making higher lows) for potential reversals
'''