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 '''