Overall Statistics
Total Trades
17
Average Win
0.18%
Average Loss
-0.38%
Compounding Annual Return
-25.239%
Drawdown
3.900%
Expectancy
-0.452
Net Profit
-2.440%
Sharpe Ratio
-2.648
Probabilistic Sharpe Ratio
11.048%
Loss Rate
62%
Win Rate
38%
Profit-Loss Ratio
0.46
Alpha
-0.211
Beta
0.424
Annual Standard Deviation
0.08
Annual Variance
0.006
Information Ratio
-2.357
Tracking Error
0.09
Treynor Ratio
-0.496
Total Fees
$17.00
Estimated Strategy Capacity
$5600000.00
Lowest Capacity Asset
AMZN R735QTJ8XC9X
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Volume Adjusted Relative Srength Trader
// ----------------------------------------------------
// Ikezi Kamanu
// Tristan Johnson
// 
// Reference:
// --------------
// https://www.reddit.com/r/RealDayTrading/comments/omw9rn/screenersscannerswatchlists/
// 
// Entry:
// -------
// Select top X stocks with highest relative strength to SPY)
//
// Stocks with high volume 
//
// Exit:
// -------
// Exit when Trend power goes negative
//
// Todo and/or to consider
// -------------------------
// 1. Make sure our consolidated bars start at the right time.
// 2. Limit to SPY 500 stocks
// 3. Never hold overnight. get out at EoD
// 3. Add relative Spy Strength *on multiple time frames* as additional screening criteria, 
// 4. Consider market regime filters. eg: only short if spy is bearish.
// 5. For exit, try checking for changes in trend direction (eg: going from positive to negative)
// 7. Try a different "Power" (exponent) value for the trend power indicator
// 8. Ensure price is above ichi moku cloud
//
// Consider: 
// -------------------
// 1. Daily Selection: If the Trend over past Ten hour-bars > 0 , add to universe
// 2. Position Entry:  If the Trend over past Thirty minute-bars < 0, buy 
// 3. Position Exit:   When trend strength goes from positive to negative, or stop loss of X%
//
// Code checks:
// 1. Check / refactor the use of tradeBar.Period in the history call, make sure it is always correcct
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
using QuantConnect.Data.UniverseSelection;
using System;
using System.Collections.Generic;
using System.Linq;

namespace QuantConnect.Algorithm.CSharp
{

	
    public class VARSTrader: QCAlgorithm {
        
        public bool forceAMZN;
        public int timeframeInMins;
        public int lookbackInMins;
        public int lookbackInBars;
        
        public int timeframeBInMins;
        public int lookbackBInMins;
        public int lookbackBInBars;
        
        
        public int screenTimeInMins;
        public int entryTimeInMins;
        public int exitCheckInterval;
        
        
        public Dictionary<Symbol, SymbolData> symDataDict;
    	public Resolution resolution;
    	public object SPY;

        public int rankingMetric;
        public int screeningMetric;
        public decimal pctPerHolding;
        public decimal pctTakeProfit;
        public decimal pctStopLoss;
        public decimal atrMultiplier;
        public int maxHoldings;
        public bool useTrendPowerExit;
        public bool useTrailStopExit;
        
        public Symbol AMZN;
        
        public IEnumerable<FineFundamental> myFineUniverse;
        
        // ====================================
        public void InitInternalParams() {
            // Debug
            this.forceAMZN = true;
            // Set Primary Timeframe and lookback
            // ------------------------------ 
            this.timeframeInMins = 60;
            this.lookbackInMins = 600;
            this.lookbackInBars = Convert.ToInt32(this.lookbackInMins / this.timeframeInMins);
            // Set Secondary Timeframe and lookback
            // --------------------------------- 
            this.timeframeBInMins = 5;
            this.lookbackBInMins = 1950;
            this.lookbackBInBars = Convert.ToInt32(this.lookbackBInMins / this.timeframeBInMins);
            // screening timing
            this.screenTimeInMins = 60;
            // entry timing
            this.entryTimeInMins = 120;
            // exit timing
            this.exitCheckInterval = 120;
            // this.timeFrames = new Dictionary<object, object> {};
            // this.timeFrames["5M"] = 5;
            // this.timeFrames["30M"] = 30;
            // this.timeFrames["1H"] = 60;
            // this.timeFrames["1D"] = 195;
        }
        
        // =====================================
        public override void Initialize() {
            this.InitInternalParams();
            this.InitExternalParams();
            this.EnableAutomaticIndicatorWarmUp = true;
            this.SetStartDate(2020, 1, 1);
            this.SetEndDate(2020, 2, 1);
            this.SetCash(100000);
            // self.SetStartDate(2021, 4, 22)
            // self.SetEndDate(2021, 4, 30)
            this.resolution = Resolution.Minute;
            this.SPY = this.AddEquity("SPY", this.resolution).Symbol;
            // this.myFineUniverse = new IEnumerable<FineFundamental>();
            this.UniverseSettings.Resolution = Resolution.Minute;
            // self.SPYSMA        = self.SMA(self.SPY, 200, Resolution.Daily)
            // self.SPYMomPct = MomentumPercent('SPYMomPct', period = 10)
            // self.SetSecurityInitializer(lambda x: x.SetMarketPrice(self.GetLastKnownPrice(x)))
            // Try filtering SP500
            // https://www.quantconnect.com/forum/discussion/7406/filtering-the-qc500-universe/p1
            // ----------------------------------------------------------------------------------
            // self.AddUniverse(self.CoarseSelectionFunction)
            this.AddUniverse(this.CoarseSelectionFunction, this.FineSelectionFunction);
            this.symDataDict = new Dictionary<Symbol, SymbolData> {
            };
            this.ScheduleRoutines();
        }
        
        // =====================================
        public void InitExternalParams() {
            // ---------------------------            
            // Populate External Params
            // ---------------------------
            this.rankingMetric     = Convert.ToInt32(this.GetParameter("rankingMetric"));
            this.screeningMetric   = Convert.ToInt32(this.GetParameter("screeningMetric"));
            this.pctPerHolding     = Convert.ToDecimal(this.GetParameter("pctPerHolding")) / 100;
            this.pctTakeProfit     = Convert.ToDecimal(this.GetParameter("pctTakeProfit")) / 100;
            this.pctStopLoss       = Convert.ToDecimal(this.GetParameter("pctStopLoss")) / 100;
            this.atrMultiplier     = Convert.ToDecimal(this.GetParameter("atrMultiplier"));
            this.maxHoldings       = Convert.ToInt32(this.GetParameter("maxHoldings"));
            this.useTrendPowerExit = Convert.ToInt32(this.GetParameter("useTrendPowerExit")) == 1;
            this.useTrailStopExit  = Convert.ToInt32(this.GetParameter("useTrailStopExit")) == 1;
        }
        
        // =====================================
        public void ScheduleRoutines() {
            // # Schedule Screening routine run every day, X mins after market open
            // # ------------------------------------------------------------------
            this.Schedule.On(this.DateRules.EveryDay(), this.TimeRules.AfterMarketOpen("SPY", this.screenTimeInMins - 1), this.ScreenUniverseStocks);
            // # Schedule Entry routine to run every day, X mins after market open
            // # ------------------------------------------------------------------
            this.Schedule.On(this.DateRules.EveryDay(), this.TimeRules.AfterMarketOpen("SPY", this.entryTimeInMins - 1), this.ProcessEntrySignals);
            // Schedule Position Manager routine to run every X mins
            // ------------------------------------------------------------------
            this.Schedule.On(this.DateRules.EveryDay(), this.TimeRules.Every(TimeSpan.FromMinutes(this.exitCheckInterval)), this.ManageOpenPositions);
            // Schedule Exit routine to run every X mins
            // ------------------------------------------------------------------
            this.Schedule.On(this.DateRules.EveryDay(), this.TimeRules.Every(TimeSpan.FromMinutes(this.exitCheckInterval)), this.ProcessExits);
        }
        
        // =====================
        public void ManageOpenPositions() {
            if (this.Securities["SPY"].Exchange.DateTimeIsOpen(this.Time)) {
                foreach (KeyValuePair<Symbol, SymbolData> symData in this.symDataDict) {
            		Symbol symbol = symData.Key;
                    if (this.symDataDict[symbol].IndicatorsAreSeededAndRegistered) {
                        this.symDataDict[symbol].ManageOpenPosition();
                    }
                }
            }
        }
        
        // =====================
        public void ProcessExits() {
            if (this.Securities["SPY"].Exchange.DateTimeIsOpen(this.Time)) {
                this.ManageOpenPositions();
                this.ProcessExitSignals();
            }
        }
        
        // =====================================
        public void OnData(Slice dataSlice) {
            // Call the OnData methods for each symbol we are tracking

            foreach (KeyValuePair<Symbol, SymbolData> symData in this.symDataDict) {
            	Symbol symbol = symData.Key;
                if (dataSlice.ContainsKey(symbol) && dataSlice[symbol] != null) {
                    this.symDataDict[symbol].OnData(dataSlice[symbol]);
                }
            }
        }
        
        // Check for entries
        // ====================================================
        public void ProcessEntrySignals() {
            // If the market isn't open, don't process any signals
            // ----------------------------------------------------     
            if (!this.Securities["SPY"].Exchange.DateTimeIsOpen(this.Time)) {
                return;
            }
            //# self.Debug(f"{self.Time} :::: PROCESS ENTRY SIGNALS ")    
            var dataSlice = this.CurrentSlice;
            IEnumerable<SymbolData> topTrendersToBuy = new List<SymbolData>();
            List<SymbolData> topTrendersSymData = new List<SymbolData>();
            // IEnumerable<SymbolData>
            
            var numOfCurrentHoldings = (from x in this.Portfolio
                where x.Value.Invested
                select x.Key).ToList().Count;
            // IF we have less than our max holdings, add more, up to max. 
            // -----------------------------------------------------------
            if (numOfCurrentHoldings < this.maxHoldings) {
                var numOfHoldingsToAdd = this.maxHoldings - numOfCurrentHoldings;
                // Simplified code. Expanding for debugging sake
                // -------------------------------------------------
                // trendersSymData    = [self.symDataDict[symb] for symb in self.symDataDict \
                //                     if (dataSlice.ContainsKey(symb) and self.symDataDict[symb].EntrySignalFired())]
                // Expanded Code
                // ------------------------------
                var trendersSymData = new List<SymbolData>();
                foreach (KeyValuePair<Symbol, SymbolData> symData in this.symDataDict) {
            		Symbol symbol = symData.Key;
                    if (dataSlice.ContainsKey(symbol)) {
                        if (this.symDataDict[symbol].EntrySignalFired()) {
                            trendersSymData.Add(this.symDataDict[symbol]);
                        }
                    }
                }
                topTrendersSymData = trendersSymData.OrderByDescending(symbolData => symbolData.RankingMetric).ToList();
                topTrendersToBuy = topTrendersSymData.Take(numOfHoldingsToAdd);
                
                foreach (SymbolData symbolData in topTrendersToBuy) {
                    var orderMsg = "Metric = {round(symbolData.RankingMetric * 100,3)}";
                    this.SetHoldings(symbolData.symbol, this.pctPerHolding, false, orderMsg);
                    //# self.Debug(f"{self.Time} \t\t Bought {symbolData.symbol}")    
                    // else:
                    //# self.Debug(f"{self.Time} \t\t Already @ Max Holdings")    
                    // remove stocks from universe that we didnt enter, or we are not invested in
                    // Todo: consider doing this only at end of day, in case we want to try enterint
                    // later in the day. 
                    // ---------------------------------------------------------------------------
                }
            }
            foreach (var kvp in this.Securities) {
                var symbol = kvp.Key;
                if (!this.Portfolio[symbol].Invested && !(from SymbolData x in topTrendersToBuy
                    select x.symbol).ToList().Contains(symbol)) {
                    this.RemoveSecurity(symbol);
                    // if symbol != self.SPY:
                    //# self.Debug(f"{self.Time} \t\t Removed Security - {symbol}")    
                }
            }
            return;
        }
        
        // ============================
        public void ProcessExitSignals() {
            foreach (var symbol in this.CurrentSlice.Keys) {
                if (this.symDataDict.ContainsKey(symbol)) {
                    var symbolData = this.symDataDict[symbol];
                    if (this.Portfolio[symbol].Invested) {
                        if (symbolData.ExitSignalFired()) {
                        	Plot("AMZN", "EXIT", symbolData.MomPct.Current.Value);
                            this.Liquidate(symbol, symbolData.LastOrderMsg);
                            this.RemoveSecurity(symbol);
                        }
                    }
                }
            }
        }
        
        // =====================================
        private IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental>  universe) {
            IEnumerable<CoarseFundamental> coarseuniverse = (from x in universe
                where x.HasFundamentalData && x.Price > 50
                select x);
            coarseuniverse = coarseuniverse.OrderByDescending(x => x.DollarVolume).Take(200);
            return (from x in coarseuniverse select x.Symbol);
        }
        
        // ==========================================================================
        // Fine selection
        // ---------------
        // Select stocks with positive EV/EBITDA ratio and high market cap (> 2Billion)
        // Market cap is calculated using the BasicAverageShares in earnings reports.
        // Market cap = Shares Outstanding * Price = Shares Outstanding * (Earnings Per Share * PE ratio)
        // Price per share was extracted via a single history request. 
        //
        // Then we ranked the universe by EV/EBITDA and select the top 100
        // ==========================================================================
        private IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental>  universe) {
            // Filter companies with certain security types, partnership types, etc
            // ----------------------------------------------------------------------
            this.myFineUniverse = (from x in universe
                where (x.SecurityReference.IsPrimaryShare) && (x.SecurityReference.SecurityType == "ST00000001") && (x.SecurityReference.IsDepositaryReceipt) && (x.CompanyReference.IsLimitedPartnership)
                select x).ToList();
            // Filter companies with positive EV-EBITDA ratio, and high market cap
            // ----------------------------------------------------------------------        
            try {
                this.myFineUniverse = (from   x in this.myFineUniverse
					                   where (x.ValuationRatios.EVToEBITDA > 0.0M) && (x.EarningReports.BasicAverageShares.ThreeMonths > 0.0M) && 
					                    	 (x.EarningReports.BasicAverageShares.ThreeMonths * (x.EarningReports.BasicEPS.TwelveMonths * x.ValuationRatios.PERatio) > 2000000000.0M)
					                   select x);
            } catch {
                this.myFineUniverse = (from x in this.myFineUniverse
                    where x.ValuationRatios.EVToEBITDA > 0.0M && x.EarningReports.BasicAverageShares.ThreeMonths > 0.0M
                    select x).ToList();
            }
            return new List<Symbol>();
        }
        
        // Add screened stocks that meet our criteria
        // this should be backed / confirmed by volume
        // If the stocks met screening crtieria
        // we can subscribe to their data feed using addEquity
        // =====================================================
        public void ScreenUniverseStocks() {
            var screenedStocks = this.GetScreenedStocks();
            foreach (var symbol in screenedStocks) {
                if (!this.Securities.ContainsKey(symbol) || !this.ActiveSecurities.ContainsKey(symbol)) {
                    this.AddEquity(symbol, this.resolution);
                }
            }
        }
        
        // =====================================
        public IEnumerable<Symbol> GetScreenedStocks() {
            List<Symbol> selected = new List<Symbol>();
            // -------------
            // Debug: Force universe to be just AMZN, for Performance comparison
            // --------------------------------------------------------------
            if (this.forceAMZN) {
            	
                this.AMZN = QuantConnect.Symbol.Create("AMZN", SecurityType.Equity, Market.USA);
                FineFundamental tmpObject = new FineFundamental();
                tmpObject.Symbol = this.AMZN;
                this.myFineUniverse = new List<FineFundamental> {
                    tmpObject
                };
            }
            foreach (var element in this.myFineUniverse) {
                Symbol symbol = element.Symbol;
                // Store data for this symbol, seed it with some history     
                // -----------------------------------------------------            
                if (!this.symDataDict.ContainsKey(symbol)) {
                    this.AddNewSymbolDataToDict(symbol);
                }
                SymbolData symbolData = this.symDataDict[symbol];
                this.SeedSymbolData(symbolData);
                // If indicators are ready and criteria is met, it makes the cut.     
                // --------------------------------------------------------------       
                if (symbolData.IndicatorsAreSeeded && this.symDataDict[symbol].ScreeningCriteriaMet()) {
                    selected.Add(symbol);
                }
            }
            return selected;
        }
        
        // ===================================
        public void AddNewSymbolDataToDict(Symbol symbol) {
            SymbolData tmpSymbolData = new SymbolData(symbol, this);
            this.symDataDict[symbol] = tmpSymbolData;
        }
        
        // ===================================
        public void SeedSymbolData(SymbolData symbolData) {
            // Add an extra bar by increasing lookback + bar length
            var history = this.History(symbolData.symbol, this.lookbackInMins + this.timeframeInMins, this.resolution);
            symbolData.SeedIndicators(history);
        }
        
        // ===================================
        public override void OnSecuritiesChanged(SecurityChanges changes) {
            Symbol symbol;
            foreach (Security security in changes.AddedSecurities) {
                symbol = security.Symbol;
                if (symbol == this.SPY) {
                    continue;
                }
                this.symDataDict[symbol].OnSecurityAdded(security);
            }
            // Investigate if this could possiby be triggered prematurely
            // ie: The security may not be in universe, but we are watching for signal
            // ----------------------------------------------------------------------
            foreach (Security security in changes.RemovedSecurities) {
                symbol = security.Symbol;
                if (this.symDataDict.ContainsKey(symbol)) {
                    this.symDataDict[symbol].OnSecurityRemoved();
                    this.symDataDict.Remove(symbol);
                }
            }
        }
    }

}
using QuantConnect.Indicators;
using QuantConnect.Algorithm.CSharp;

namespace QuantConnect
{

    
    // using AverageTrueRange = QuantConnect.Indicators.AverageTrueRange;
    
    // using AugenPriceSpike = QuantConnect.Indicators.AugenPriceSpike;
    
    // using IndicatorDataPoint = QuantConnect.Indicators.IndicatorDataPoint;
    
    public class SymbolData {
	
	    public VARSTrader algo;
	    public Symbol symbol;
		public decimal lastPrice; 
		public object stopLossOrder;
		public object takeProfitOrder;
		public bool IndicatorsAreSeeded;
		public bool IndicatorsAreRegistered;
		public string LastOrderMsg;
		public decimal TrailingStopLoss;
		public decimal LastClose;
		
		public TradeBarConsolidator seedDataConsolidator;
		public MomentumPercent MomPct;
		public SimpleMovingAverage SMA;
		public AverageTrueRange ATR;
		public AugenPriceSpike APS;

        public SymbolData(Symbol theSymbol, VARSTrader algo) {
            // Algo / Symbol / Price / Order reference
            // ----------------------------------------
            this.algo	= algo;
            this.symbol = theSymbol;
            this.lastPrice = 0;
            this.stopLossOrder = null;
            this.takeProfitOrder = null;
            this.IndicatorsAreSeeded = false;
            this.IndicatorsAreRegistered = false;
            this.LastOrderMsg = "No Order Msg";
            this.TrailingStopLoss = 0;
            this.LastClose = 0;
            // Initialize our indicators
            // ----------------------------------------
            // self.TrendPower         = TrendPower('MomPct', period = self.algo.lookbackInBars, power = 1.5)
            this.MomPct = new MomentumPercent(this.algo.lookbackInBars);
            this.SMA = new SimpleMovingAverage(this.algo.lookbackInBars);
            this.ATR = new AverageTrueRange(period: 5);
            this.APS = new AugenPriceSpike(period: 20);
            // if we are NOT using our custom consolidator, initialize the QC consolidator
            this.AddQCSeedDataConsolidator();
        }
        
        // Set up consolidators for historical seed bars
        // https://www.quantconnect.com/forum/discussion/10414/open-to-feedback-and-or-questions-on-this-simple-options-algo/p1/comment-29630
        // ===================================
        public void AddQCSeedDataConsolidator() {
            this.seedDataConsolidator = new TradeBarConsolidator(TimeSpan.FromMinutes(this.algo.timeframeInMins));
            this.seedDataConsolidator.DataConsolidated += this.SeedDataConsolidatorHandler;
        }
        
        // # ========================================
        public void SeedDataConsolidatorHandler(object sender, TradeBar tradeBar) {
            this.UpdateIndicatorsWithBars(tradeBar);
        }
        
        // ===================================
        public void SeedIndicators(IEnumerable<TradeBar> history_raw) {
        	
            List<TradeBar> history = new List<TradeBar>();
            foreach (TradeBar bar in history_raw)
                history.Add(bar);
            if (history.Count == 0)
            {
            	this.IndicatorsAreSeeded = false;
                return;
            }
            else
            {
                foreach (var tradeBar in history)
                {
                	this.seedDataConsolidator.Update(tradeBar);
                }
                this.IndicatorsAreSeeded = true;
            }
            
        	
        	/*
            // Loop over the history data and seed the indicator
            // -------------------------------------------------------------
            if (history.empty || !history.columns.Contains("close")) {
                this.IndicatorsAreSeeded = false;
                return;
            }
            // Loop through bars and update indicators
            // # ----------------------------------------------------
            foreach (var _tup_1 in history.iterrows()) {
                var index = _tup_1.Item1;
                var bar = _tup_1.Item2;
                var tradeBar = new TradeBar();
                tradeBar.Close = bar.close;
                tradeBar.Open = bar.open;
                tradeBar.High = bar.high;
                tradeBar.Low = bar.low;
                tradeBar.Volume = bar.volume;

                // if using bars that have NOT been Consolidated 
                // pass the minute trade bar to the QC consolidator
                // which will form the appropriate bars accordingly
                // --------------------------------------------------------
                tradeBar.Period = TimeSpan.FromMinutes(1);
                tradeBar.Time = index[1];
                this.seedDataConsolidator.Update(tradeBar);
            }
            
            this.IndicatorsAreSeeded = true;
            */
        }
        
        // ========================================
        // Once the security has been added, this means it successfully
        // used it's seed data to verify an entry signa.
        // Now we need to remove the seed data consolidators, and subscribe to data
        // todo: investigate why Consolidators arent orking as expected    
        // ============================================
        public void OnSecurityAdded(object security) {
            // self.RemoveSeedDataConsolidator()
            this.RegisterIndicators();
        }
        
        // =========================================================
        // Once the security has been removed, UnSubscribe from data
        // ---------------
        // Todo: Investigate if this could possiby be triggered prematurely
        //       eg: if a stock is removed from the universe but we're still 
        //       interested in it, will the securitryRemoved handler get called?
        // ========================================================
        public void OnSecurityRemoved() {
            // self.RemoveSeedDataConsolidator()
            return;
        }
        
        // ========================================================
        public void RegisterIndicators() {
            this.algo.RegisterIndicator(this.symbol, this.ATR, TimeSpan.FromMinutes(this.algo.timeframeInMins));
            this.algo.RegisterIndicator(this.symbol, this.APS, TimeSpan.FromMinutes(this.algo.timeframeInMins));
            this.algo.RegisterIndicator(this.symbol, this.SMA, TimeSpan.FromMinutes(this.algo.timeframeInMins));
            this.algo.RegisterIndicator(this.symbol, this.MomPct, TimeSpan.FromMinutes(this.algo.timeframeInMins));
            this.IndicatorsAreRegistered = true;
            algo.Debug("RUNS");
        }
        
        // =====================================    
        public void UpdateIndicatorsWithBars(TradeBar tradeBar) {
            if (tradeBar != null && this.MomPct != null && this.ATR != null) {
                // self.MomPct.Update(tradeBar)
                this.ATR.Update(tradeBar);
                this.MomPct.Update(new IndicatorDataPoint(tradeBar.EndTime, tradeBar.Close));
                this.SMA.Update(new IndicatorDataPoint(tradeBar.EndTime, tradeBar.Close));
                this.APS.Update(new IndicatorDataPoint(tradeBar.EndTime, tradeBar.Close));
            }
        }
        
        public bool IndicatorsAreSeededAndRegistered {
            get {
                return this.IndicatorsAreSeeded && this.IndicatorsAreRegistered;
            }
        }
        
        
        
        public object RankingMetric {
            get {
                if (this.IndicatorsAreSeeded) {
                    if (this.algo.rankingMetric == 1) {
                        return this.MomPct.Current.Value;
                    }
                    if (this.algo.rankingMetric == 2) {
                        return this.APS.Current.Value;
                    }
                    return null;
                } else {
                    return null;
                }
            }
            
        }
        
        // =====================================            
        // =====================================            
        // =====================================    
        // ==========================================
        // Screening crtieria should include volume
        // ==========================================
        public bool ScreeningCriteriaMet() {
            if (this.algo.forceAMZN) {
                return true;
            }
            if (this.IndicatorsAreSeeded) {
                if (this.algo.screeningMetric == 1) {
                    return this.MomPct.Current.Value > 0;
                }
                if (this.algo.screeningMetric == 2) {
                    if (this.MomPct.Current.Value > 0 && this.APS.Current.Value > 0) {
                        return true;
                    } else {
                        return false;
                    }
                }
            } else {
                return false;
            }
            return false;
        }
        
        // =====================================
        public bool EntrySignalFired() {
            if (this.IndicatorsAreSeeded) {
                return this.MomPct.Current.Value > 0;
                // return (self.SMA.Current.Value < self.LastClose)
                // =====================================
            }
            return false;
        }
        
        public bool ExitSignalFired() {
            decimal pctProfit = 0.0M;
            
            pctProfit = this.algo.Portfolio[this.symbol].UnrealizedProfitPercent;
            var profitLabel = Decimal.Round(pctProfit * 100, 4);
            
            // Exit: % StopLoss Exit
            // ----------------------
            if (this.algo.pctStopLoss > 0 && pctProfit <= -this.algo.pctStopLoss) {
                this.LastOrderMsg = $"Pct Loss Exit @ {profitLabel}% Profit";
                return true;
            }
            // Exit: % Take Profit Exit
            // ----------------------
            if (this.algo.pctTakeProfit > 0 && pctProfit >= this.algo.pctTakeProfit) {
                this.LastOrderMsg = $"Pct Profit Exit @ {profitLabel}% Profit";
                return true;
            }
            // Exit: Trailing Stop-Loss Exit (Breakeven or above)
            // ----------------------------------------------
            if (this.algo.useTrailStopExit && pctProfit > 0 && this.LastClose < this.TrailingStopLoss) {
                this.LastOrderMsg = $"Trailing SL @ {profitLabel}% Profit ({LastClose} < {TrailingStopLoss})";
                return true;
            }
            // Exit: Negative Trend Power
            // ----------------------------------------------
            // tmp SMA check
            // if self.algo.useTrendPowerExit and (self.SMA.IsReady) and (self.SMA.Current.Value > self.LastClose):
            if (this.algo.useTrendPowerExit && this.MomPct.IsReady && this.MomPct.Current.Value < 0) {
                this.LastOrderMsg = $"Trend Power Exit @ {profitLabel}% Profit power={MomPct.Current.Value}";
                return true;
            }
            return false;
        }
        
        // =====================================
        public void OnData(TradeBar DataSliceBar) {
            this.LastClose = DataSliceBar.Close;
        }
        
        // =====================================
        public void ManageOpenPosition() {
            if (this.algo.useTrailStopExit) {
                this.UpdateStopLoss();
            }
        }
        
        // Todo: Add logic to activate stop loss after a certain value 
        //       (maybe after breakeven, when UnrealizedProfitPercent >= 0)
        //       Try Different values for multiplier
        // =====================================
        public void UpdateStopLoss() {
            var newStopLoss = this.LastClose - this.ATR.Current.Value * this.algo.atrMultiplier;
            this.TrailingStopLoss = Math.Max(this.TrailingStopLoss, newStopLoss);
        }
    }
    
}