| 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);
}
}
}