Overall Statistics
Total Trades
39
Average Win
1.22%
Average Loss
-0.63%
Compounding Annual Return
14.123%
Drawdown
4.600%
Expectancy
0.195
Net Profit
3.909%
Sharpe Ratio
1.045
Probabilistic Sharpe Ratio
49.890%
Loss Rate
59%
Win Rate
41%
Profit-Loss Ratio
1.92
Alpha
0.098
Beta
-0.015
Annual Standard Deviation
0.097
Annual Variance
0.009
Information Ratio
0.673
Tracking Error
0.466
Treynor Ratio
-6.725
Total Fees
$266.26
Estimated Strategy Capacity
$1600000.00
Lowest Capacity Asset
NDAQ T63A9J5FJWX1
using System;

namespace GoldenCross
{
	class Program
	{
		static void Main(string[] args)
		{
			Console.WriteLine("Hello World!");
		}
	}
}
/*
 * Copyright (C) 2022 by Lanikai Studios, Inc. - All Rights Reserved
 *
 * This code is not to be distributed to others, it is confidential information of Lanikai Studios.
 *
 * This code was built from open source Python code written by Aaron Eller - www.excelintrading.com
 */

using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Brokerages;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Orders;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using static Lanikai.GlobalSettings;

namespace Lanikai
{
	/// <summary>
	/// The framework for the Lanikai algo.
	/// </summary>
	public class LanikaiQCAlgorithm : QCAlgorithm
	{

		/// <summary>
		/// The benchmark for comparison. Normally set to SPY.
		/// </summary>
		private Symbol bm;
		
		/// <summary>
		/// Used for benchmark plotting code.
		/// </summary>
		private decimal? bm_first_price;
		
		/// <summary>
		/// Used for benchmark plotting code.
		/// </summary>
		private int bm_plot_counter = 0;

		/// <summary>
		/// The data for each symbol we are following.
		/// </summary>
		private IDictionary<Symbol, SymbolData> symbol_data;

		/// <summary>
		/// true when we need to update the universe.
		/// </summary>
		private bool update_universe;

		/// <summary>
		/// The max number of positions allowed.
		/// </summary>
		private int maxPositions;

		/// <summary>
		/// The maximum percentage of the budget to spend on any one stock.
		/// </summary>
		private float maxPctPerPosition;

		/// <summary>
		/// Long exit when RSI crosses below this value
		/// </summary>
		public decimal RsiExit1Value { get; private set; }

		/// <summary>
		/// Long exit when RSI crosses below this value
		/// </summary>
		public decimal RsiExit2Value { get; private set; }

		// Initialize algorithm.
		public override void Initialize()
		{

			// Set backtest details
			SetBacktestDetails();
			// Add instrument data to the algo
			AddInstrumentData();
			// Schedule functions
			ScheduleFunctions();
			
			// initialize each security with today's prices
			SetSecurityInitializer(Initialize);

			// Warm up the indicators prior to the start date
			// this.SetWarmUp()
			// This doesn't work with universe filters
			// Instead we'll use History() to warm up indicators when SymbolData class objects get created
			var strParam = GetParameter("MAX_POSITIONS");
			maxPositions = strParam == null ? MAX_POSITIONS : Convert.ToInt32(strParam);

			maxPctPerPosition = 1.0f / maxPositions;
			var strRsiExit1Value = GetParameter("RSI_EXIT_1_VALUE");
			RsiExit1Value = strRsiExit1Value == null ? RSI_EXIT_1_VALUE : Convert.ToInt32(strRsiExit1Value);

			var strRsiExit2Value = GetParameter("RSI_EXIT_2_VALUE_ADD");
			RsiExit2Value = strRsiExit2Value == null
				? RSI_EXIT_2_VALUE
				: RsiExit1Value - Convert.ToInt32(strRsiExit2Value);

			if (PRINT_SETTINGS)
			{
				Log($"maxPositions = {maxPositions}, maxPctPerPosition = {maxPctPerPosition}");
				Log($"rsiExit1Value = {RsiExit1Value}.");
				Log($"rsiExit2Value = {RsiExit2Value}.");
				Log($"Stop loss: {SL_PCT * 100}%");
			}
		}

		private void Initialize (Security security) {
			security.SetMarketPrice(GetLastKnownPrice(security));
		}
		
		/// <summary>
		/// End of algorithm run event handler. This method is called at the end of a backtest or
		/// live trading operation. Intended for closing out logs.
		/// </summary>
		public override void OnEndOfAlgorithm()
		{
			// Check if we want to plot the benchmark
			if (PLOT_BENCHMARK_ON_STRATEGY_EQUITY_CHART)
				PlotBenchmarkOnEquityCurve(true);

			if (PRINT_VERBOSE || PRINT_ORDERS)
				Log($"End of backtest at {Time}; recalc indicators={SymbolData.recalcIndicators}");
				
			if (PRINT_ORDERS) {
				Log("Portfolio:");
				var positions = (from symbol in Portfolio.Keys where Portfolio[symbol].Invested select symbol).ToList();
				foreach (var sym in positions)
					Log($"{sym} holding {Portfolio[sym].Quantity} shares at {Portfolio[sym].Price:C}");
			}
			
			foreach (SymbolData symbolData in symbol_data.Values)
				if (VERBOSE_SYMBOL.Contains(symbolData.Symbol.Value)) {
					Log($"{symbolData.Symbol.Value} EMA fast: {SymbolData.RollingWindowToString(symbolData.window_ema_fast)}");
					Log($"{symbolData.Symbol.Value} EMA slow: {SymbolData.RollingWindowToString(symbolData.window_ema_slow)}");
				}
		}

		/// <summary>
		/// Set the backtest details.
		/// </summary>
		public void SetBacktestDetails()
		{

			SetStartDate(START_DATE.Year, START_DATE.Month, START_DATE.Day);
			SetEndDate(END_DATE.Year, END_DATE.Month, END_DATE.Day);
			SetCash(CASH);
			SetTimeZone(TIMEZONE.Id);
			// Setup trading framework
			// Transaction and submit/execution rules will use IB models
			// brokerages: https://github.com/QuantConnect/Lean/blob/master/Common/Brokerages/BrokerageName.cs
			// account types: AccountType.Margin, AccountType.Cash
			SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash);
			// Configure all universe securities
			// This sets the data normalization mode
			// You can also set custom fee, slippage, fill, and buying power models
			// see if SubscriptionManager.SubscriptionDataConfigService has SetDataNormalizationMode() if the root class is cast to Equity
			this.SetSecurityInitializer(new MySecurityInitializer());
			// Adjust the cash buffer from the default 2.5% to custom setting
			Settings.FreePortfolioValuePercentage = (decimal)FREE_PORTFOLIO_VALUE_PCT;
		}

		// Add instrument data to the algo.
		public void AddInstrumentData()
		{

			// Set data resolution based on input
			Resolution resolution;
			switch (DATA_RESOLUTION)
			{
				case "SECOND":
					resolution = Resolution.Second;
					break;
				case "MINUTE":
					resolution = Resolution.Minute;
					break;
				case "HOUR":
					resolution = Resolution.Hour;
					break;
				default:
					throw new ApplicationException($"Must set resolution to SECOND, MINUTE, or HOUR. Is set to {DATA_RESOLUTION}.");
			}

			// Define the desired universe
			this.AddUniverse(this.CoarseSelectionFunction, this.FineSelectionFunction);
			// Set universe data properties desired
			UniverseSettings.Resolution = resolution;
			UniverseSettings.ExtendedMarketHours = false;
			UniverseSettings.DataNormalizationMode = DataNormalizationMode.Adjusted;
			UniverseSettings.MinimumTimeInUniverse = new TimeSpan(MIN_TIME_IN_UNIVERSE, 0, 0, 0);
			// Add data for the benchmark and set benchmark
			// Always use minute data for the benchmark
			bm = AddEquity(BENCHMARK, Resolution.Minute).Symbol;
			SetBenchmark(BENCHMARK);
			// Create a dictionary to hold SymbolData class objects
			symbol_data = new Dictionary<Symbol, SymbolData>();
			// Create a variable to tell the algo when to update the universe
			update_universe = true;
		}

		/// <summary>
		/// Scheduling the functions required by the algo.
		/// </summary>
		public void ScheduleFunctions()
		{

			// For live trading, universe selection occurs approximately 04:00-07:00am EST on Tue-Sat. 
			// For backtesting, universe selection occurs at 00:00am EST (midnight)
			// We need to update the self.update_universe variable
			//  before both of these scenarios are triggered
			// Desired order of events:
			// 1. Update the self.update_universe variable True 
			//    end of week/month, 5 min after market close 
			// 2. Coarse/Fine universe filters run and update universe
			//    run everyday at either 00:00 or 04:00 EST
			// Update self.update_universe variable True when desired
			IDateRule date_rules;
			switch (UNIVERSE_FREQUENCY)
			{
				case "daily":
					date_rules = DateRules.EveryDay(this.bm);
					break;
				case "weekly":
					// Want to schedule at end of the week, so actual update on start 
					//  of the next week
					date_rules = DateRules.WeekEnd(this.bm);
					break;
				case "monthly":
					// Want to schedule at end of the month, so actual update on 
					//  first day of the next month
					date_rules = DateRules.MonthEnd(this.bm);
					break;
				default:
					throw new ApplicationException($"UNIVERSE_FREQUENCY needs to be set to daily, weekly, or monthly. Is set to {UNIVERSE_FREQUENCY}.");
			}

			// Timing is after the market closes
			Schedule.On(date_rules, TimeRules.BeforeMarketClose(this.bm, -5), this.UpdateUniverse);
			// Calling -5 minutes "BeforeMarketClose" schedules the function 5 
			// minutes "after market close"
			// Calling -5 minutes "AfterMarketOpen" schedules the function 5 
			// minutes "before market open"
			// Now the coarse/fine universe filters will run automatically either at
			//  00:00(midnight) EST for backtesting or
			//  04:00am EST for live trading
			// Check for new signals SIGNAL_CHECK_MINUTES before the market open
			Schedule.On(DateRules.EveryDay(this.bm), TimeRules.AfterMarketOpen(this.bm, -SIGNAL_CHECK_MINUTES),
				this.CheckForSignals);
			// Check if end of day exit is desired
			if (EOD_EXIT)
			{
				Utils.Trap();
				// Schedule function to liquidate the portfolio EOD_EXIT_MINUTES
				//  before the market close
				Schedule.On(DateRules.EveryDay(this.bm), TimeRules.BeforeMarketClose(this.bm, EOD_EXIT_MINUTES),
					this.LiquidatePortfolio);
			}

			// Check if we want to plot the benchmark on the equity curve
			if (PLOT_BENCHMARK_ON_STRATEGY_EQUITY_CHART)
				// Schedule benchmark end of day event 5 minutes after the close
				Schedule.On(DateRules.EveryDay(this.bm), TimeRules.BeforeMarketClose(this.bm, -5),
					this.BenchmarkOnEndOfDay);
			else Utils.Trap();
		}

		/// <summary>
		/// Event called when re-balancing is desired.
		/// </summary>
		public void UpdateUniverse()
		{
			Utils.Trap();
			// Update variable to trigger the universe to be updated
			update_universe = true;
		}

		// 
		//         Perform coarse filters on universe.
		//         Called once per day.
		//         Returns all stocks meeting the desired criteria.
		//         
		//         Attributes available:
		//          .AdjustedPrice
		//          .DollarVolume
		//          .HasFundamentalData
		//          .Price -> always the raw price!
		//          .Volume
		//         
		/// <summary>
		/// Perform coarse filters on universe. Called once per day.
		///         
		///         Attributes available:
		///          .AdjustedPrice
		///          .DollarVolume
		///          .HasFundamentalData
		///          .Price -> always the raw price!
		///          .Volume
		/// </summary>
		/// <param name="coarse">Summary information for each stock.</param>
		/// <returns>All stocks meeting the desired criteria.</returns>
		public IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
		{

			// # Testing - catch specific symbol
			// for x in coarse:
			//     # if str(x.Symbol).split(" ")[0] in ['AAPL']:
			//     if x.Symbol.ID.Symbol in ['AAPL']:
			//         # Stop and debug below
			//         print(x)

			// Check if the universe doesn't need to be updated
			if (!update_universe)
				// Return unchanged universe
				return Universe.Unchanged;

			// Otherwise update the universe based on the desired filters
			// Filter all securities with appropriate price and volume
			var filteredCoarse = (from x in coarse
								  where x.Price >= MIN_PRICE && x.Price <= MAX_PRICE && x.Volume >= MIN_DAILY_VOLUME &&
								  x.DollarVolume >= MIN_DAILY_DOLLAR_VOLUME
								  select x).ToList();
			// Check if fundamental data is required
			if (REQUIRE_FUNDAMENTAL_DATA)
				filteredCoarse = (from x in filteredCoarse where x.HasFundamentalData select x).ToList();

			// Return the symbol objects
			List<Symbol> universe = (from x in filteredCoarse select x.Symbol).ToList();

			// Print universe details when desired
			if (PRINT_COARSE)
				Log($"Coarse filter returned {universe.Count} stocks.");

			return universe;
		}

		/// <summary>
		/// Perform fine filters on universe. Called once per day.
		///         
		///         Attributes available:
		///          .AssetClassification
		///          .CompanyProfile
		///          .CompanyReference
		///          .EarningRatios
		///          .EarningReports
		///          .FinancialStatements
		///          .MarketCap
		///          .OperationRatios
		///          .Price -> always the raw price!
		///          .ValuationRatios
		/// </summary>
		/// <param name="fine">Summary information for each stock.</param>
		/// <returns>All stocks meeting the desired criteria.</returns>
		public IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine)
		{

			// # Testing - catch specific symbol
			// for x in coarse:
			//     # if str(x.Symbol).split(" ")[0] in ['AAPL']:
			//     if x.Symbol.ID.Symbol in ['AAPL']:
			//         # Stop and debug below
			//         print(x)
			// Check if the universe doesn't need to be updated

			if (!update_universe)
				// Return unchanged universe
				return Universe.Unchanged;

			// Otherwise update the universe based on the desired filters
			// Filter by allowed exchange and market cap
			var symbols = (from x in fine
						   where ALLOWED_EXCHANGE.Contains(x.SecurityReference.ExchangeId) && x.MarketCap >= MIN_MARKET_CAP &&
								 x.MarketCap <= MAX_MARKET_CAP
						   select x).ToList();
			// Filter stocks based on primary share class
			if (PRIMARY_SHARES)
				symbols = (from x in symbols where x.SecurityReference.IsPrimaryShare select x).ToList();

			// Filter stocks based on disallowed sectors
			if (SECTORS_NOT_ALLOWED.Count > 0)
			{
				Utils.Trap();
				symbols = (from x in symbols
						   where !SECTORS_NOT_ALLOWED.Contains(x.AssetClassification.MorningstarSectorCode)
						   select x).ToList();
			}

			// Filter stocks based on disallowed industry groups
			if (GROUPS_NOT_ALLOWED.Length > 0)
			{
				Utils.Trap();
				symbols = (from x in symbols
						   where !GROUPS_NOT_ALLOWED.Contains(x.AssetClassification.MorningstarIndustryGroupCode)
						   select x).ToList();
			}

			// Filter stocks based on disallowed industries
			if (INDUSTRIES_NOT_ALLOWED.Length > 0)
			{
				Utils.Trap();
				symbols = (from x in symbols
						   where !INDUSTRIES_NOT_ALLOWED.Contains(x.AssetClassification.MorningstarIndustryCode)
						   select x).ToList();
			}

			// Return the symbol objects we want in our universe.
			IList<Symbol> universe = (from x in symbols select x.Symbol).ToList();
			if (PRINT_FINE)
				Log($"Fine filter returned {universe.Count} stocks.");

			this.update_universe = false;
			return universe;
		}

		// Event handler for changes to our universe.
		/// <summary>
		/// Event fired each time the we add/remove securities from the data feed.
		/// </summary>
		/// <param name="changes">Security additions/removals for this time step.</param>
		public override void OnSecuritiesChanged(SecurityChanges changes)
		{

			// Loop through securities added to the universe
			foreach (var security in changes.AddedSecurities)
			{
				// Skip if BENCHMARK - we cannot trade this!
				if (security.Symbol.ID.Symbol == BENCHMARK)
					continue;

				// Create a new symbol_data object for the security
				symbol_data.Add(security.Symbol, new SymbolData(this, security.Symbol));
			}

			// Loop through securities removed from the universe
			foreach (var security in changes.RemovedSecurities)
			{
				// Liquidate removed securities
				if (security.Invested)
				{
					Utils.Trap();
					if (PRINT_VERBOSE)
						Log($"{security.Symbol.ID.Symbol} removed from the universe, so closing open position.");
					Liquidate(security.Symbol);
				}

				// Remove from symbol_data dictionary
				if (symbol_data.ContainsKey(security.Symbol))
				{
					Utils.Trap();
					// Remove desired bar consolidator for this security
					TradeBarConsolidator consolidator = symbol_data[security.Symbol].Consolidator;
					SubscriptionManager.RemoveConsolidator(security.Symbol, consolidator);

					// Remove symbol from symbol data
					if (PRINT_VERBOSE)
						Log($"{security.Symbol.ID.Symbol} removed from symbol_data.");
					symbol_data.Remove(security.Symbol);
				}
			}
		}

		/// <summary>
		/// Liquidate the entire portfolio. Also cancel any pending orders.
		/// </summary>
		public void LiquidatePortfolio()
		{
			Utils.Trap();
			if (Portfolio.Invested && PRINT_ORDERS)
				Log("Time for end of day exit. Liquidating the portfolio.");

			Liquidate();
		}
		
		/// <summary>
		/// Event called when signal checks are desired.
		/// </summary>
		public void CheckForSignals()
		{
			
			if (PRINT_VERBOSE)
				Log($"CheckForSignals() Time={Time}");
				
			// Check for exit signals
			CheckForExits();
			// Check for entry signals
			CheckForEntries();
		}

		/// <summary>
		/// Check for exit signals.
		/// </summary>
		public void CheckForExits()
		{
			// Get list of current positions
			var positions = (from symbol in Portfolio.Keys where Portfolio[symbol].Invested select symbol).ToList();
			// Loop through positions
			foreach (var sym in positions)
				// Check for long position
				if (Portfolio[sym].Quantity > 0)
					// long - Check for long exit signal
					symbol_data[sym].LongExitSignalChecks();
		}
		
		// Check for entry signals.
		public void CheckForEntries()
		{

			// Get list of current positions
			List<Symbol> positions = (from symbol in Portfolio.Keys where Portfolio[symbol].Invested select symbol).ToList();
			// Get number of new entries allowed
			var newEntryNum = maxPositions - positions.Count;
			// Return if no new positions are allowed
			if (newEntryNum == 0)
				return;

			// Create a list of long symbol
			var longSymbols = new List<SymbolData>();
			// Loop through the SymbolData class objects
			foreach (SymbolData symbolData in symbol_data.Values)
			{
				// Skip if already invested
				if (Securities[symbolData.Symbol].Invested)
					continue;

				// Check if indicators are ready - can be false for new stocks & sparsely traded ones
				if (symbolData.IndicatorsReady)
					// Check for long entry signal
					if (symbolData.LongEntrySignal)
						longSymbols.Add(symbolData);
			}

			// Check if the number of new entries exceeds limit
			if (longSymbols.Count > newEntryNum)
			{
				if (PRINT_ORDERS)
					Log($"{positions.Count} existing + {longSymbols.Count} additional orders > {maxPositions} maximum positions");

				// Sort the entry_tuples list of tuples by largest pct_change
				//  pct_change is the second element of the tuple
				//  reverse=True for descending order (highest to lowest)
				longSymbols = longSymbols.OrderByDescending(sd => sd.FastSlowPctDifference).ToList();
				// Only keep the top newEntryNum
				longSymbols = longSymbols.Take(newEntryNum).ToList();
			}

			// Print entry signal summary when desired
			if (PRINT_ENTRIES && longSymbols.Count > 0)
			{
				string symbols = string.Join(", ", longSymbols.Select(sd => sd.Symbol.Value));
				Log($"{longSymbols.Count} LONG entry signal(s): {symbols}");
			}

			// Place entry orders
			foreach (SymbolData symbolData in longSymbols)
				SetHoldings(symbolData.Symbol, maxPctPerPosition, tag: "initial buy");
		}

		/// <summary>
		/// Order fill event handler. On an order fill update the resulting information is passed to this method.
		/// </summary>
		/// <param name="orderEvent">Order event details containing details of the evemts.</param>
		public override void OnOrderEvent(OrderEvent orderEvent)
		{

			// Skip if not filled
			if (orderEvent.Status != OrderStatus.Filled)
				return;

			// Get the order's symbol
			if (PRINT_VERBOSE)
				Log($"{orderEvent.Symbol.Value} OnOrderEvent({orderEvent})");

			// this can come in after we removed a security
			if (!symbol_data.ContainsKey(orderEvent.Symbol))
			{
				Utils.Trap();
				if (PRINT_VERBOSE)
					Log($"{orderEvent.Symbol.Value} not in self.symbol_data");
				return;
			}

			// Call on_order_event for the symbol's SymbolData class
			this.symbol_data[orderEvent.Symbol].OnOrderEvent(orderEvent);
		}

		/// <summary>
		/// Event handler for end of trading day for the benchmark.
		/// </summary>
		public void BenchmarkOnEndOfDay()
		{
			PlotBenchmarkOnEquityCurve();
		}
		
		/// <summary>
		/// Plot the benchmark buy & hold value on the strategy equity chart.
		/// </summary>
		/// <param name="force_plot"></param>
		public void PlotBenchmarkOnEquityCurve(bool force_plot = false)
		{
			// Initially set percent change to zero
			float pct_change = 0;
			// Get today's daily prices 
			// history algo on QC github shows different formats that can be used:
			// https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/HistoryAlgorithm.py
			IEnumerable<Slice> history = this.History(new List<Symbol> { bm }, new TimeSpan(1, 0, 0, 0), Resolution.Daily);
			
			var listHistory = history.ToList();
			decimal? price;
			if (listHistory.Count > 0)
			{
				TradeBar tradebar = listHistory[0].Bars[bm];
				
				// We have not created the first price variable yet
				// Get today's open and save as the first price
				if (bm_first_price == null) {
					bm_first_price = tradebar.Open;
					Log($"Benchmark first price = {bm_first_price}");
				}

				// Get today's closing price
				price = tradebar.Close;
				// Calculate the percent change since the first price
				pct_change = (float) ((price - bm_first_price) / bm_first_price);
			}
			else
				price = null;

			// Calculate today's ending value if we have the % change from the start
			if (pct_change != 0)
			{
				var bm_value = Math.Round(CASH * (decimal)(1 + pct_change), 2);
				// Plot every PLOT_EVERY_DAYS days
				bm_plot_counter += 1;

				if ((this.bm_plot_counter >= PLOT_EVERY_DAYS) || force_plot)
				{
					// Plot the benchmark's value to the Strategy Equity chart
					// Plot function requires passing the chart name, series name, 
					// then the value to plot
					this.Plot("Strategy Equity", "Benchmark", bm_value);
					// Plot the account leverage
					var account_leverage = Portfolio.TotalHoldingsValue / Portfolio.TotalPortfolioValue;
					Plot("Leverage", "Leverge", account_leverage);
					// Reset counter to 0
					bm_plot_counter = 0;
					// Log benchmark's ending price for reference
					if (force_plot)
					{
						Log($"Benchmark's first price = {bm_first_price}");
						Log($"Benchmark's final price = {price}");
						Log($"Benchmark buy & hold value = {bm_value}");
					}
				}
			}
		}

		/// <summary>
		/// Define models to be used for securities as they are added to the algorithm's universe.
		/// </summary>
		private class MySecurityInitializer : ISecurityInitializer
		{
			public void Initialize(Security security)
			{
				// Define the data normalization mode
				security.SetDataNormalizationMode(DataNormalizationMode.Adjusted);
				// Define the fee model to use for the security
				// security.SetFeeModel()
				// Define the slippage model to use for the security
				// security.SetSlippageModel()
				// Define the fill model to use for the security
				// security.SetFillModel()
				// Define the buying power model to use for the security
			}
		}
	}
}
/*
 * Copyright (C) 2022 by Lanikai Studios, Inc. - All Rights Reserved
 *
 * This code is not to be distributed to others, it is confidential information of Lanikai Studios.
 */

using System;
using System.Diagnostics;
using QuantConnect.Data.Fundamental;

namespace Lanikai
{
	/// <summary>
	/// Basic utilities for the project.
	///
	/// Created by: David Thielen
	/// Version: 1.0
	/// </summary>
	public static class Utils
	{
		/// <summary>
		/// Break into the debugger if running in DEBUG mode.
		/// </summary>
		public static void Trap()
		{
//#if DEBUG
			// Debugger.Break();
			string stack = Stack;
			stack = null;
//#endif
		}

		/// <summary>
		/// Break into the debugger if running in DEBUG mode and the parameter is true..
		/// </summary>
		/// <param name="breakIfTrue">Break into the debugger if true, do nothing if false.</param>
		public static void Trap(bool breakIfTrue)
		{
//#if DEBUG
			if (breakIfTrue) {
				// Debugger.Break();
				string stack = Stack;
				stack = null;
			}
//#endif
		}

		private static string Stack
		{
			get
			{
				string stack = Environment.StackTrace;
				int start = stack.IndexOf("Trap");
				if (start == -1)
					return stack.Trim();
				start = stack.IndexOf('\n', start) + 1;
				int end = stack.IndexOf("QuantConnect");
				if (end == -1)
					return stack.Substring(start).Trim().Replace('\n', ' ');
				end = stack.LastIndexOf('\n', end);
				if (end == -1)
					return stack.Substring(start).Trim().Replace('\n', ' ');

				return stack.Substring(start, end - start).Trim().Replace('\n', ' ');
			}
		}
	}
}
/*
 * Copyright (C) 2022 by Lanikai Studios, Inc. - All Rights Reserved
 *
 * This code is not to be distributed to others, it is confidential information of Lanikai Studios.
 *
 * This code was built from open source Python code written by Aaron Eller - www.excelintrading.com
 */

using System;
using System.Collections.Generic;
using System.Linq;

namespace Lanikai
{

     /// <summary>
    /// Moving Average Cross Universe Strategy
    /// Version 1.1.0
    ///
    /// Revision Notes:
    ///   1.0.0 (01/17/2021) - Initial.Started from "Universe Strategy_v103.py". Also
    /// copied SymbolData logic from
    ///                         "Moving Average Crossover_v107.py".
    ///   1.1.0 (01/26/2021) - converted to C#
    ///
    /// References:
    ///  -QC(Lean) Class List https://lean-api-docs.netlify.app/annotated.html
    ///  -OrderTicket properties https://lean-api-docs.netlify.app/classQuantConnect_1_1Orders_1_1OrderTicket.html
    ///  -QC Universe https://www.quantconnect.com/docs/algorithm-reference/universes
    ///  -QC Universe Settings https://www.quantconnect.com/docs/algorithm-reference/universes#Universes-Universe-Settings
    ///  -QC Universe Fundamentals https://www.quantconnect.com/docs/data-library/fundamentals
    ///  -Speeding up QC Universe https://www.quantconnect.com/forum/discussion/7875/speeding-up-universe-selection/p1
    /// </summary>
    public static class GlobalSettings {

        ////////////////////////////////////////////////////
        // BACKTEST INPUTS

        /// <summary>Everything runs on New York time (not UTC). Therefore the market open/close is constant</summary>
        public static readonly TimeZoneInfo TIMEZONE;
        
        /// <summary>Backtest and Optimize start on this day (exclusive - because QC needs to run to overnight to pupulate equity pricing).</summary>
        private static readonly DateTime StartDate = new DateTime(2020, 1, 1);
        public static readonly DateTimeOffset START_DATE;

        /// <summary>Backtest and Optimize end on this day (inclusive).</summary>
        private static readonly DateTime EndDate = new DateTime(2020, 4, 15);
        public static readonly DateTimeOffset END_DATE;

        /// <summary>Starting portfolio value</summary>
        public static decimal CASH = 500000;

        ////////////////////////////////////////////////////
        // DATA INPUTS

        /// <summary>
        /// Define the data resolution to be fed to the algorithm
        /// Must be "SECOND", "MINUTE", or "HOUR"
        /// </summary>
        public static string DATA_RESOLUTION = "HOUR";

        /// <summary>
        /// How often to update the universe?
        /// Options: 'daily', 'weekly', or 'monthly'
        /// </summary>
        public static string UNIVERSE_FREQUENCY = "monthly";

        ////////////////////////////////////////////////////
        // COARSE UNIVERSE SELECTION INPUTS

        /// <summary>if True, stocks only / no etfs e.g.</summary>
        public static bool REQUIRE_FUNDAMENTAL_DATA = true;

        /// <summary>Selected stocks must be this price per share, or more. Set to 0 to disable.</summary>
        public static decimal MIN_PRICE = 10.0m;

        /// <summary> Selected stocks must be this price per share, or less. Set to 1e6 to disable.</summary>
        public static decimal MAX_PRICE = 1000000.0m;

        /// <summary>Daily volume of trades in this stock must be this amount, or more. Set to 0 to disable.</summary>
        public static int MIN_DAILY_VOLUME = 0;

        /// <summary>Daily total price of trades in this stock must be this amount, or more. Set to 1e6 to disable.</summary>
        public static decimal MIN_DAILY_DOLLAR_VOLUME = 10000000.0m;




        ////////////////////////////////////////////////////
        // FINE UNIVERSE SELECTION INPUTS

        /// <summary>Minimum market cap. e6=million/e9=billion/e12=trillion</summary>
        public static decimal MIN_MARKET_CAP = 10E9m;

        /// <summary>Maximum market cap. e6=million/e9=billion/e12=trillion</summary>
        public static decimal MAX_MARKET_CAP = 10E12m;

        // Turn on/off specific exchanges allowed
        /// <summary>Archipelago Electronic Communications Network.</summary>
        public static bool ARCX = false;
        /// <summary>American Stock Exchange</summary>
        public static bool ASE = false;
        /// <summary>Better Alternative Trading System</summary>
        public static bool BATS = false;
        /// <summary>Nasdaq Stock Exchange</summary>
        public static bool NAS = true;
        /// <summary>New York Stock Exchange</summary>
        public static bool NYS = true;

        /// <summary>Only allow a stock's primary shares</summary>
        public static bool PRIMARY_SHARES = true;

        // Turn on/off specific sectors allowed
        public static bool BASIC_MATERIALS = true;
        public static bool CONSUMER_CYCLICAL = true;
        public static bool FINANCIAL_SERVICES = true;
        public static bool REAL_ESTATE = true;
        public static bool CONSUMER_DEFENSIVE = true;
        public static bool HEALTHCARE = true;
        public static bool UTILITIES = true;
        public static bool COMMUNICATION_SERVICES = true;
        public static bool ENERGY = true;
        public static bool INDUSTRIALS = true;
        public static bool TECHNOLOGY = true;

        /// <summary>
        /// Set Morningstar Industry Groups not allowed
        /// Use Industry Group Code from:
        /// https://www.quantconnect.com/docs/data-library/fundamentals#Fundamentals-Asset-Classification
        /// example: MorningstarIndustryGroupCode.Banks (10320)
        /// </summary>
        public static int[] GROUPS_NOT_ALLOWED = new int [0];

        /// <summary>
        /// Set Morningstar Industries not allowed
        /// Use Industry Codes from:
        /// https://www.quantconnect.com/docs/data-library/fundamentals#Fundamentals-Asset-Classification
        /// example: MorningstarIndustryCode.Gambling (10218038)
        /// </summary>
        public static int[] INDUSTRIES_NOT_ALLOWED = new int[0];

        /// <summary>
        /// Set the minimum number of days to leave a stock in a universe.
        /// This helps with making the universe output more stable.
        /// </summary>
        public static int MIN_TIME_IN_UNIVERSE = 65;

        /// <summary>
        /// Set the minimum number of days with historical data
        /// </summary>
        public static int MIN_TRADING_DAYS = 200;

        ////////////////////////////////////////////////////
        // ENTRY SIGNAL INPUTS

        /// <summary>
        /// The fast EMA period (number of trading days).
        /// </summary>
        public static int EMA_FAST_PERIOD = 50;

        /// <summary>
        /// The slow EMA period (number of trading days).
        /// </summary>
        public static int EMA_SLOW_PERIOD = 200;

        /// <summary>
        /// How many minutes prior to the open to check for new signals?
        /// Ideally this is called AFTER the universe filters run!
        /// </summary>
        public static int SIGNAL_CHECK_MINUTES = 30;

        ////////////////////////////////////////////////////
        // POSITION SIZING INPUTS

        /// <summary>
        /// Set the percentage of the portfolio to free up to avoid buying power issues.
        /// decimal percent, e.g. 0.025=2.5% (default).
        /// </summary>
        public static float FREE_PORTFOLIO_VALUE_PCT = 0.025f;

        /// <summary>
        /// Set the max number of positions allowed
        /// </summary>
        public static int MAX_POSITIONS = 4;

        /// <summary>
        /// Calculate max % of portfolio per position
        /// </summary>
        public static float MAX_PCT_PER_POSITION = 1.0f / MAX_POSITIONS;

        ////////////////////////////////////////////////////
        // EXIT SIGNAL INPUTS - stop loss

        /// <summary>
        /// Turn on/off stop loss
        /// </summary>
        public static bool STOP_LOSS = true;

        /// <summary>
        /// Set stop loss percentage as a decimal percent, e.g. 0.02 == 2.0%
        /// </summary>
        public static float SL_PCT = 0.05f;

        /// <summary>
        /// Turn on/off trailing stop. Starts and trails based on SL_PCT
        /// </summary>
        public static bool TRAILING_STOP = true;

        /// <summary>
        /// Turn on/off end of day exit.
        /// </summary>
        public static bool EOD_EXIT = false;

        /// <summary>
        /// When end of exit exit is desired, how many minutes prior to the market close should positions be liquidated?
        /// </summary>
        public static int EOD_EXIT_MINUTES = 15;

        ////////////////////////////////////////////////////
        // EXIT SIGNAL INPUTS - Death Cross

        /// <summary>
        /// Turn on/off exiting on fast EMA crossing under the slow EMA
        /// </summary>
        public static bool EMA_CROSSUNDER_EXIT = true;

        ////////////////////////////////////////////////////
        // EXIT SIGNAL INPUTS - RSI

        /// <summary>
        /// Set the RSI period (trading days).
        /// </summary>
        public static int RSI_PERIOD = 14;

        /// <summary>
        /// Turn on/off RSI exit
        /// </summary>
        public static bool RSI_EXIT = true;

        /// <summary>
        /// Long exit when RSI crosses below this value
        /// </summary>
        public static decimal RSI_EXIT_1_VALUE = 62m;

        /// <summary>
        /// decimal percent of initial position, 0.50=50.0%
        /// </summary>
        public static float RSI_EXIT_1_PCT = 0.5f;

        /// <summary>
        /// Long exit when RSI crosses below this value
        /// </summary>
        public static decimal RSI_EXIT_2_VALUE = 52m;

        ////////////////////////////////////////////////////
        // EXIT SIGNAL INPUTS - days held

        /// <summary>
        /// Turn on/off days held exit
        /// </summary>
        public static bool DAYS_HELD_EXIT = false;

        /// <summary>
        /// How many days held until selling exit #1
        /// </summary>
        public static int DAYS_HELD_EXIT_1_VALUE = 2;

        /// <summary>
        /// What percentage of the initial order to sell on this exit. decimal percent, e.g. 0.25=25.0%
        /// </summary>
        public static float DAYS_HELD_EXIT_1_PCT = 0.25f;

        /// <summary>
        /// How many days held until selling exit #2
        /// </summary>
        public static int DAYS_HELD_EXIT_2_VALUE = 4;

        /// <summary>
        /// What percentage of the initial order to sell on this exit. decimal percent, e.g. 0.25=25.0%
        /// </summary>
        public static float DAYS_HELD_EXIT_2_PCT = 0.25f;

        /// <summary>
        /// How many days held until selling exit #3
        /// </summary>
        public static int DAYS_HELD_EXIT_3_VALUE = 6;

        /// <summary>
        /// What percentage of the initial order to sell on this exit. decimal percent, e.g. 0.25=25.0%
        /// </summary>
        public static float DAYS_HELD_EXIT_3_PCT = 0.25f;

        /// <summary>
        /// How many days held until selling exit #4
        /// </summary>
        public static int DAYS_HELD_EXIT_4_VALUE = 8;

        ////////////////////////////////////////////////////
        // EXIT SIGNAL INPUTS - percent increase

        /// <summary>
        /// Turn on/off profit target
        /// </summary>
        public static bool PROFIT_TARGET = false;

        /// <summary>
        /// Set profit target percentage as a decimal percent, e.g. 0.05=5.0%
        /// </summary>
        public static float PT1_PCT = 0.05f;

        /// <summary>
        /// Set profit target order percentage as a decimal percent, e.g. 0.50=50.0%
        /// </summary>
        public static float PT1_ORDER_PCT = 0.5f;

        /// <summary>
        /// Set profit target percentage as a decimal percent, e.g. 0.07=7.0%
        /// </summary>
        public static float PT2_PCT = 0.07f;

        /// <summary>
        /// Set profit target order percentage as a decimal percent, e.g. 0.25=25.0%
        /// </summary>
        public static float PT2_ORDER_PCT = 0.25f;

        /// <summary>
        /// Set profit target percentage as a decimal percent, e.g. 0.09=9.0%
        /// </summary>
        public static float PT3_PCT = 0.09f;


        ////////////////////////////////////////////////////
        // ROLLING WINDOWS, BENCHMARK DETAILS

        /// <summary>
        /// How many trading days to keep in the rolling windows. Set to how far back want to look at historical data.
        /// </summary>
        public static int INDICATOR_WINDOW_LENGTH = 10;

        /// <summary>
        /// Turn on/off using the custom benchmark plot on the strategy equity chart
        /// </summary>
        public static bool PLOT_BENCHMARK_ON_STRATEGY_EQUITY_CHART = true;

        /// <summary>
        /// Define benchmark equity. Currently set to not be able to trade the benchmark!
        /// Also used for scheduling functions, <b>so make sure it has same trading hours as instruments traded.</b>
        /// </summary>
        public static string BENCHMARK = "SPY";


        ////////////////////////////////////////////////////
        // LOGGING DETAILS (minimize logging due to QC limits!)

        /// <summary>
        /// print summary of coarse universe selection
        /// </summary>
        public static bool PRINT_COARSE = false;

        /// <summary>
        /// print summary of fine universe selection
        /// </summary>
        public static bool PRINT_FINE = false;

        /// <summary>
        /// print summary of daily entry signals
        /// </summary>
        public static bool PRINT_ENTRIES = true;

        /// <summary>
        /// print exit signals triggered
        /// </summary>
        public static bool PRINT_EXITS = false;

        /// <summary>
        /// print new orders
        /// </summary>
        public static bool PRINT_ORDERS = true;

        /// <summary>
        /// print changes in stop loss, etc. orders
        /// </summary>
        public static bool PRINT_UPDATES = false;

        /// <summary>
        /// print settings for the run
        /// </summary>
        public static bool PRINT_SETTINGS = false;

    	/// <summary>
        /// print equity data initialization
        /// </summary>
        public static bool PRINT_EQUITY_INIT = false;

        /// <summary>
        /// print verbose info
        /// </summary>
        public static bool PRINT_VERBOSE = true;
        
        /// <summary>
        /// print verbose info for only these stocks.
        /// </summary>
        public static string[] VERBOSE_SYMBOL = {"BKR"};


        ////////////////////////////////////////////////////
        // END OF ALL USER INPUTS
        // VALIDATE USER INPUTS - DO NOT CHANGE BELOW!!!
        ////////////////////////////////////////////////////

        /// <summary>
        /// List of the allowed exchanges
        /// </summary>
        public static List<string> ALLOWED_EXCHANGE;

        /// <summary>
        /// List of the sectors NOT allowed
        /// </summary>
        public static List<int> SECTORS_NOT_ALLOWED;

        private static readonly string[] resolutions = {"SECOND", "MINUTE", "HOUR"};
        private static string[] frequencies = { "daily", "weekly", "monthly" };

        /// <summary>
        /// The number of market warmup days required.
        /// </summary>
        public static int MARKET_WARMUP_DAYS;

        /// <summary>
        /// The number of calendar days required to match MARKET_WARMUP_DAYS. This number will be >= to MARKET_WARMUP_DAYS
        /// as it uses rough additions to account for holidays.
        /// </summary>
        public static int CALENDAR_WARMUP_DAYS;
        
        /// <summary>
        /// Calculate the frequency of days that we can create a new plot.
        /// </summary>
    	public static int PLOT_EVERY_DAYS;

        /// <summary>
        /// Initialize global lists, validate settings
        /// </summary>
        static GlobalSettings()
        {
            // first try the Linux name
			TIMEZONE = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
            // if that fails - the Windows name
            if (TIMEZONE == null)
				TIMEZONE = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

			START_DATE = new DateTimeOffset(StartDate, TIMEZONE.GetUtcOffset(StartDate));
			END_DATE = new DateTimeOffset(EndDate, TIMEZONE.GetUtcOffset(EndDate));

			ALLOWED_EXCHANGE = new List<string>();
            if (ARCX)
                ALLOWED_EXCHANGE.Add("ARCX");
            if (ASE)
	            ALLOWED_EXCHANGE.Add("ASE"); 
            if (BATS)
	            ALLOWED_EXCHANGE.Add("BATS");
            if (NAS)
	            ALLOWED_EXCHANGE.Add("NAS");
            if (NYS)
	            ALLOWED_EXCHANGE.Add("NYS");

            SECTORS_NOT_ALLOWED = new List<int>();
            if (! BASIC_MATERIALS)
                SECTORS_NOT_ALLOWED.Add(101);
            if (! CONSUMER_CYCLICAL)
	            SECTORS_NOT_ALLOWED.Add(102);
            if (! FINANCIAL_SERVICES)
	            SECTORS_NOT_ALLOWED.Add(103);
            if (! REAL_ESTATE)
	            SECTORS_NOT_ALLOWED.Add(104);
            if (! CONSUMER_DEFENSIVE)
	            SECTORS_NOT_ALLOWED.Add(205);
            if (! HEALTHCARE)
	            SECTORS_NOT_ALLOWED.Add(206);
            if (! UTILITIES)
	            SECTORS_NOT_ALLOWED.Add(207);
            if (! COMMUNICATION_SERVICES)
	            SECTORS_NOT_ALLOWED.Add(308);
            if (! ENERGY)
	            SECTORS_NOT_ALLOWED.Add(309);
            if (! INDUSTRIALS)
	            SECTORS_NOT_ALLOWED.Add(310);
            if (! TECHNOLOGY)
	            SECTORS_NOT_ALLOWED.Add(311);

            DATA_RESOLUTION = DATA_RESOLUTION.ToUpper();
            if (! resolutions.Contains(DATA_RESOLUTION))
                throw new ApplicationException($"DATA_RESOLUTION = {DATA_RESOLUTION} is an invalid value.");
            UNIVERSE_FREQUENCY = UNIVERSE_FREQUENCY.ToLower();
            if (!frequencies.Contains(UNIVERSE_FREQUENCY))
	            throw new ApplicationException($"UNIVERSE_FREQUENCY = {UNIVERSE_FREQUENCY} is an invalid value.");

            // For the benchmark plotting
            int PLOT_LIMIT = 4000;
            int BT_DAYS = (END_DATE - START_DATE).Days;
            BT_DAYS = (int)Math.Ceiling(((float)BT_DAYS * 252f) / 365f);
            PLOT_EVERY_DAYS = Math.Max(1, (int) Math.Ceiling((float)BT_DAYS / (float)PLOT_LIMIT));

            // Calculate the number of days in the backtest

            // Calculate the period to warm up the data
            int BARS_PER_DAY = 1;
            // Get the minimum number of bars required to fill all indicators
            int MIN_BARS = Math.Max(Math.Max(EMA_FAST_PERIOD, EMA_SLOW_PERIOD), RSI_PERIOD) + INDICATOR_WINDOW_LENGTH;
            
            MARKET_WARMUP_DAYS = (int) Math.Ceiling((float)MIN_BARS / (float)BARS_PER_DAY);
            // Assume 252 market days per calendar year (or 365 calendar days), add a 10% and +2 buffer for holidays
            CALENDAR_WARMUP_DAYS = (int) ((MARKET_WARMUP_DAYS * 1.1f) * 365f / 252f + 2f);
        }
    }
}
/*
 * Copyright (C) 2022 by Lanikai Studios, Inc. - All Rights Reserved
 *
 * This code is not to be distributed to others, it is confidential information of Lanikai Studios.
 *
 * This code was built from open source Python code written by Aaron Eller - www.excelintrading.com
 */


using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using MathNet.Numerics;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using QuantConnect.Orders;
using QuantConnect.Securities;
using static Lanikai.GlobalSettings;

namespace Lanikai
{

	/// <summary>
	/// Class to store data for a specific symbol.
	/// </summary>
	public class SymbolData
	{
		
		/// <summary>The parent algorithm that owns this object.</summary>
		private readonly LanikaiQCAlgorithm algo;
		
		public static bool recalcIndicators = false;

		/// <summary>
		/// The symbol this object operates on.
		/// The underlying text symbol is symbol.ID.Symbol
		/// </summary>
		public Symbol Symbol { get; }

		/// <summary>The desired daily bar consolidator for the symbol.</summary>
		public TradeBarConsolidator Consolidator { get; private set; }

		/// <summary>The fast EMA indicator. Used to find when we hit the Golden Cross.</summary>
		private ExponentialMovingAverage ema_fast;

		/// <summary>The slow EMA indicator. Used to find when we hit the Golden Cross.</summary>
		private ExponentialMovingAverage ema_slow;

		/// <summary>The RSI indicator. Used optionally to determine when to sell.</summary>
		private RelativeStrengthIndex rsi;

		// bugbug - correct naming of vars.

		/// <summary>A boolean for each day true if the fast EMA isn > the slow EMA, false otherwise.
		/// Used to determine when to buy.</summary>
		private RollingWindow<bool> fast_ema_gt_slow_ema;

		/// <summary>Create a rolling window of the last closing prices.
		/// Optionally used to take profits based on the curve of recent closes.</summary>
		private RollingWindow<decimal> window_closes;

		/// <summary>Create a rolling window of the recent fast EMA values.
		/// Optionally used to take profits based on the curve of recent EMA values.</summary>
		public RollingWindow<decimal> window_ema_fast;

		/// <summary>Create a rolling window of the recent slow EMA values.
		/// Optionally used to take profits based on the curve of recent EMA values.</summary>
		public RollingWindow<decimal> window_ema_slow;

		/// <summary>
		/// Create a rolling window of the recent RSI values.
		/// Optionally used to take profits based on the curve of recent RSI values.
		/// RSI has a value of 0 ... 100. Moving up is bullish, moving down bearish.
		/// </summary>
		private RollingWindow<double> window_rsi;

		/// <summary>Create a rolling window of the recent trade bars (1 per day).
		/// Optionally used to take profits based on the curve of recent trade bar values.</summary>
		private RollingWindow<TradeBar> window_bar;

		/// <summary>All of the indicators and rolling windows.
		/// This is weird as there is no common base class, but both do have an IsReady property.</summary>
		private List<object> indicators;

		/// <summary>The exchange hours for this security.</summary>
		private SecurityExchangeHours exchangeHours;

		/// <summary>The time the market opens for this stock when the market is open for the full day.</summary>
		private DateTime mktOpenTime;

		/// <summary>The time the market closes for this stock when the market is open for the full day.</summary>
		private DateTime mktCloseTime;

		private decimal? cost_basis;

		///<summary> The low price today. Used for adjusting the stop loss.</summary>
		private decimal? todaysLow;

		private OrderTicket sl_order;

		private decimal? sl_price;

		private OrderTicket pt_order1;

		private OrderTicket pt_order2;

		private OrderTicket pt_order3;
		
		/// <summary>When selling part (profit taking) this is set to that percentage to only do that once. Set to 0 when first purchasing.</summary>
		private float percentSold;

		private int days_held;

		public SymbolData(LanikaiQCAlgorithm algo, Symbol symbol)
		{

			// Save the references
			this.algo = algo;
			this.Symbol = symbol;

			// Get the symbol's exchange market info
			GetExchangeInfo();

			// Initialize strategy specific variables
			InitializeStrategyVariables();

			// Add the bars and indicators required
			AddBarsIndicators();
		}

		/// <summary>
		/// Get the security's exchange info.
		/// </summary>
		public void GetExchangeInfo()
		{

			exchangeHours = algo.Securities[Symbol].Exchange.Hours;

			// using a date we know the market was open the standard hours
			// every market day should be these times OR LESS.
			DateTime jan4 = new DateTime(2021, 1, 4);
			DateTimeOffset date = new DateTimeOffset(jan4, TIMEZONE.GetUtcOffset(jan4));

			// Save the full day market open and close times
			mktOpenTime = exchangeHours.GetNextMarketOpen(date.DateTime, false);
			mktCloseTime = exchangeHours.GetNextMarketClose(date.DateTime, false);
		}

		/// <summary>
		/// Initialize the strategy variables.
		/// </summary>
		/// <returns></returns>
		public void InitializeStrategyVariables()
		{

			// Initialize order variables
			ResetOrderVariables();
		}

		/// <summary>
		/// Reset order variables for the strategy.
		/// </summary>
		public void ResetOrderVariables()
		{

			cost_basis = null;
			todaysLow = null;
			sl_order = null;
			sl_price = null;
			pt_order1 = null;
			pt_order2 = null;
			pt_order3 = null;
			days_held = 0;
		}

		/// <summary>
		/// Set up the consolidators, indicators, & rolling windows.
		/// </summary>
		public void AddBarsIndicators()
		{

			// Create the desired daily bar consolidator for the symbol
			Consolidator = new TradeBarConsolidator(DailyUSEquityCalendar);

			// Create an event handler to be called on each new consolidated bar
			Consolidator.DataConsolidated += OnDataConsolidated;

			// Link the consolidator with our symbol and add it to the algo manager
			algo.SubscriptionManager.AddConsolidator(Symbol, Consolidator);

			// Create indicators to be based on the desired consolidated bars
			ema_fast = new ExponentialMovingAverage(EMA_FAST_PERIOD);
			ema_slow = new ExponentialMovingAverage(EMA_SLOW_PERIOD);
			rsi = new RelativeStrengthIndex(RSI_PERIOD);

			// Create rolling windows of whether the fast EMA is > or < slow EMA
			// format: RollingWindow[object type](length)
			fast_ema_gt_slow_ema = new RollingWindow<bool>(2);

			// Create a rolling window of the last closing prices
			// used to make sure there is enough data to start trading
			window_closes = new RollingWindow<decimal>(MIN_TRADING_DAYS);

			// Create rolling windows for desired data
			window_ema_fast = new RollingWindow<decimal>(INDICATOR_WINDOW_LENGTH);
			window_ema_slow = new RollingWindow<decimal>(INDICATOR_WINDOW_LENGTH);
			window_rsi = new RollingWindow<double>(INDICATOR_WINDOW_LENGTH);
			window_bar = new RollingWindow<TradeBar>(INDICATOR_WINDOW_LENGTH);

			// Keep a list of all indicators & rolling windows - for indicators.IsReady property
			indicators = new List<object>
			{
				ema_fast,
				ema_slow,
				rsi,
				fast_ema_gt_slow_ema,
				window_closes,
				window_ema_fast,
				window_ema_slow,
				window_rsi,
				window_bar
			};

			// Warm up the indicators with historical data
			WarmupIndicators();
		}
		
		private CalendarInfo DailyUSEquityCalendar(DateTime dateTime) {
        
	        // Set up daily consolidator calendar info for the US equity market.
    	    // This should return a start datetime object that is timezone unaware
        	// with a valid date/time for the desired securities' exchange's time zone.
        
	        // Create a datetime.datetime object to represent the market open
    	    DateTime start = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day,
        			mktOpenTime.Hour, mktOpenTime.Minute,
        			0, 0, dateTime.Kind);
            
 	        // Get today's end time from the SecurityExchangeHours Class object
    	    // exchange_class = self.algo.Securities[self.symbol].Exchange
        	// exchange_hrs_class = self.algo.Securities[self.symbol].Exchange.Hours
        	//  which is saved as self.exchange_hours
	        DateTime end = exchangeHours.GetNextMarketClose(start, false);
            
    	    // Return the start datetime and the consolidation period
        	return new CalendarInfo(start, end-start);
		}

		/// <summary>
		/// Warm up indicators using historical data.
		/// </summary>
		public void WarmupIndicators()
		{

			// Get historical data
			IEnumerable<Slice> history = algo.History(new List<Symbol> {Symbol}, MARKET_WARMUP_DAYS, Resolution.Daily);

			int num = 0;
			foreach (Slice slice in history)
			{

				// bar must exist
				TradeBar bar = slice.Bars[Symbol];
				if (bar == null)
					continue;

				UpdateIndicators(bar);
				num++;
			}
			
			if (PRINT_EQUITY_INIT && ((! IndicatorsReady) || VERBOSE_SYMBOL.Contains(Symbol.Value)))
				algo.Log($"{Symbol.Value} indicators " + (IndicatorsReady ? "" : "not ") + $"ready {this}, history.Count = {num}");
		}
		
		/// <summary>
		/// Event handler that fires when a new piece of data is produced. 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="bar">The newly consolidated data.</param>
		private void OnDataConsolidated(object sender, TradeBar bar)
		{
			// Manually update all of the indicators
			UpdateIndicators(bar);
		}

		/// <summary>
		/// true if all of the indicators used are ready (warmed up).
		/// Once true, should never revert to false.
		/// </summary>
		public bool IndicatorsReady
		{
			get
			{
				// Return False if any indicator is not ready
				foreach (var indicator in indicators)
				{
					// can be an indicator or a rolling window - so use reflection
					PropertyInfo prop = indicator.GetType().GetProperty("IsReady");
					if ((prop != null) && !((bool) prop.GetValue(indicator)))
						return false;
				}

				// Otherwise all indicators are ready, so return True
				return true;
			}
		}

		/// <summary>
		/// true if there is an EMA cross-over today.
		/// </summary>
		public bool EmaCrossOver
		{

			get
			{
				// Need latest value true (fast > slow ema) 
				// and previous one false (fast <= slow ema)
				return fast_ema_gt_slow_ema[0] && !fast_ema_gt_slow_ema[1];
			}
		}

		/// <summary>
		/// true if there is an EMA cross-under today.
		/// </summary>
		public bool EmaCrossUnder
		{

			get
			{
				// Need latest value false (fast < slow ema) 
				// and previous one true (fast >= slow ema)
				return (!fast_ema_gt_slow_ema[0]) && fast_ema_gt_slow_ema[1];
			}
		}

		/// <summary>
		/// The percent difference between the fast and slow EMAs.
		/// </summary>
		public decimal FastSlowPctDifference
		{
			get
			{
				return (ema_fast.Current.Value - ema_slow.Current.Value) / ema_slow.Current.Value;
			}
		}

		/// <summary>
		/// The current quantity held in the portfolio.
		/// </summary>
		public decimal CurrentQuantity
		{

			get
			{
				return algo.Portfolio[Symbol].Quantity;
			}
		}

		/// <summary>
		/// true if there is a valid long entry signal.
		/// </summary>
		public bool LongEntrySignal
		{
			get {

				if (EmaCrossOver && (! RSILiquidate))
				{
					if (PRINT_ENTRIES)
						algo.Log($"{Symbol.Value} LONG ENTRY SIGNAL: EMA CROSSOVER; EMA fast: {RollingWindowToString(window_ema_fast)}; EMA slow: {RollingWindowToString(window_ema_slow)}; RSI: {RSIRollingWindowToString()}");
					return true;
				}

			return false;
			}
		}

		/// <summary>
		/// Check if there are any valid long exit signals.
		/// </summary>
		public void LongExitSignalChecks()
		{

			// make a switch
			// Trigger on an EMA cross-under
			if (EMA_CROSSUNDER_EXIT && EmaCrossUnder)
			{
				if (PRINT_EXITS)
					algo.Log($"{Symbol.Value} LONG EXIT SIGNAL: EMA CROSSUNDER");
				algo.Log($"***** fast_ema_gt_slow_ema[0] = {fast_ema_gt_slow_ema[0]}; EndTime={window_bar[0].EndTime}");
				algo.Log($"***** fast_ema_gt_slow_ema[1] = {fast_ema_gt_slow_ema[1]}; EndTime={window_bar[1].EndTime}");
				algo.Log($"***** RSI={RSIRollingWindowToString()}");
				algo.Log($"***** EMA fast={ToString(ema_fast)}");
				algo.Log($"***** EMA slow={ToString(ema_slow)}");

				algo.Liquidate(Symbol);
				return;
			}

			// Check for RSI exits
			if (RSI_EXIT)
			{
				// if the RSI is falling, then we exit if fallen below the exit values
				if (RSISlope <= 0) {
					if (PRINT_VERBOSE)
						algo.Log($"{Symbol.Value} RSI is falling {RSIRollingWindowToString()}");

					// Check for RSI below the RSI_EXIT_1_VALUE
					if (rsi.Current.Value < algo.RsiExit1Value && percentSold < RSI_EXIT_1_PCT)
					{
						percentSold = RSI_EXIT_1_PCT;
						if (PRINT_EXITS)
							algo.Log($"{Symbol.Value} LONG EXIT SIGNAL: RSI ({rsi.Current.Value.ToString("F2")}) < {algo.RsiExit1Value}. Now closing {RSI_EXIT_1_PCT * 100.0}% of the position.");
						var exitQty = (int) (-CurrentQuantity * (decimal) RSI_EXIT_1_PCT);
						algo.MarketOrder(Symbol, exitQty, tag: $"sell {RSI_EXIT_1_PCT * 100}%, dropped below RSI_EXIT_1_VALUE");
					}

					// Check for RSI below the RSI_EXIT_2_VALUE
					if (rsi.Current.Value < algo.RsiExit2Value && percentSold < 1)
					{
						percentSold = 1;
						if (PRINT_EXITS)
							algo.Log($"{Symbol.Value} LONG EXIT SIGNAL: RSI ({rsi.Current.Value.ToString("F2")}) < {algo.RsiExit2Value}. Now closing 100% of the position.");
						algo.Liquidate(Symbol, tag: $"liquidate, dropped below RSI_EXIT_2_VALUE");
					}
				} else
				if (PRINT_VERBOSE)
					algo.Log($"{Symbol.Value} RSI is climbing {RSIRollingWindowToString()}");
			}

			// Check for days held exits

			if (DAYS_HELD_EXIT)
			{
				Utils.Trap();

				if (days_held >= DAYS_HELD_EXIT_1_VALUE && percentSold < DAYS_HELD_EXIT_1_PCT)
				{
					Utils.Trap();
					percentSold = DAYS_HELD_EXIT_1_PCT;
					if (PRINT_EXITS)
						algo.Log(
							$"{Symbol.Value} LONG EXIT SIGNAL: Days held ({days_held}) = {DAYS_HELD_EXIT_1_VALUE}. Now closing {DAYS_HELD_EXIT_1_PCT * 100.0}% of the position.");
					var exitQty = (int) (-CurrentQuantity * (decimal) DAYS_HELD_EXIT_1_PCT);
					algo.MarketOrder(Symbol, exitQty);
				}


				if (days_held >= DAYS_HELD_EXIT_2_VALUE && percentSold < DAYS_HELD_EXIT_2_PCT)
				{
					Utils.Trap();
					percentSold = DAYS_HELD_EXIT_2_PCT;
					if (PRINT_EXITS)
						algo.Log(
							$"{Symbol.Value} LONG EXIT SIGNAL: Days held ({days_held}) = {DAYS_HELD_EXIT_2_VALUE}. Now closing {DAYS_HELD_EXIT_2_PCT * 100.0}% of the position.");
					var exitQty = (int) (-CurrentQuantity * (decimal) DAYS_HELD_EXIT_2_PCT);
					algo.MarketOrder(Symbol, exitQty);
				}


				if (days_held == DAYS_HELD_EXIT_3_VALUE && percentSold < 1)
				{
					Utils.Trap();
					percentSold = 1;
					if (PRINT_EXITS)
						algo.Log(
							$"{Symbol.Value} LONG EXIT SIGNAL: Days held ({days_held}) = {DAYS_HELD_EXIT_3_VALUE}. Now closing 100% of the position.");
					algo.Liquidate(Symbol);
				}
			}
		}
		
		/// <summary>
		/// Manually update all of the symbol's indicators.
		/// </summary>
		/// <param name="bar">The trade bar for this symbol for the period just ended.</param>
		public void UpdateIndicators(TradeBar bar)
		{
			
			// need this first - in case recalcIndicators == true
			window_bar.Add(bar);

			// Update the EMAs and RSI
			// need to recalc if the indicators are not discarding the oldest value when Update() is called
			if (recalcIndicators) {
				// no need if warming up AND window_bar is not fully populated.
				// if window_bar is not fully populated (new stock) then can't do this.
				if ((! algo.IsWarmingUp) && window_bar.IsReady) {
					ema_fast.Reset();
					for (int index=EMA_FAST_PERIOD-1; index>=0; index--) {
						TradeBar _bar = window_bar[index];
						ema_fast.Update(_bar.EndTime, _bar.Close);
					}
					ema_slow.Reset();
					for (int index=EMA_SLOW_PERIOD-1; index>=0; index--) {
						TradeBar _bar = window_bar[index];
						ema_slow.Update(_bar.EndTime, _bar.Close);
					}
					rsi.Reset();
					for (int index=RSI_PERIOD-1; index>=0; index--) {
						TradeBar _bar = window_bar[index];
						rsi.Update(_bar.EndTime, _bar.Close);
					}
				}
			} 
			
			// this works if the indicators internally discard oldest value when adding newest
			else {
				ema_fast.Update(bar.EndTime, bar.Close);
				ema_slow.Update(bar.EndTime, bar.Close);
				rsi.Update(bar.EndTime, bar.Close);
			}

			// Update rolling windows
			window_ema_fast.Add(ema_fast.Current.Value);
			window_ema_slow.Add(ema_slow.Current.Value);

			fast_ema_gt_slow_ema.Add(ema_fast.Current.Value > ema_slow.Current.Value);

			window_rsi.Add((float)rsi.Current.Value);

			window_closes.Add(bar.Close);

			// Update the trades best price if trailing stop is used
			if (TRAILING_STOP)
				UpdateTradeBestPrice(bar);

			// Increment days held counter if there is a position
			if (CurrentQuantity != 0)
				days_held += 1;
		}

		/// <summary>
		/// Update the trade's best price if there is an open position.
		/// </summary>
		/// <param name="bar">The trade bar for this symbol for the period just ended.</param>
		public void UpdateTradeBestPrice(TradeBar bar)
		{

			// Check if there is an open position
			if (CurrentQuantity <= 0)
				return;

			// Update the trade best price when appropriate
			if (todaysLow == null)
			{
				Utils.Trap();
				// Should be set, so raise error to debug
				// throw new ApplicationException("what's going on here - bugbug");
				algo.Log("***** what's going on here?");
				return;
			}

			// we only move the stop loss up.
			if (bar.Low > todaysLow)
			{
				todaysLow = bar.Low;

				// Get the current stop loss price
				decimal? slPrice = StopPrice;

				// Check for increase in stop loss price
				if ((slPrice != null) && slPrice > this.sl_price)
				{
					// Update the stop loss order's price
					UpdateOrderStopPrice(sl_order, slPrice.Value);
					sl_price = slPrice;
				}
			}
		}

		/// <summary>
		/// The current stop loss price. This is SL_PCT below the lowest price from today's trades.
		/// The caller of this property needs to determine if this is a higher value than the present
		/// stop loss and only set to this if it's an increase (or decrease for shorting).
		/// </summary>
		public decimal? StopPrice
		{
			get
			{
				if (todaysLow == null)
					return null;

				// long
				if (CurrentQuantity > 0)
					return Math.Round(todaysLow.Value * (decimal) (1 - SL_PCT), 2);

				// short
				if (CurrentQuantity < 0)
					return Math.Round(todaysLow.Value * (decimal) (1 + SL_PCT), 2);

				Utils.Trap();
				return null;
			}
		}

		/// <summary>
		/// Cancel any open exit orders.
		/// </summary>
		public void CancelExitOrders()
		{

			// Cancel open profit target order #1, if one
			if (pt_order1 != null)
			{
				Utils.Trap();
				if (PRINT_ORDERS)
					algo.Log($"Cancelling {Symbol.Value} open profit target #1 order {pt_order1}.");
				try
				{
					pt_order1.Cancel();
					pt_order1 = null;
				}
				catch
				{
					algo.Log($"Error trying to cancel {Symbol.Value} profit target #1 order {pt_order1}.");
				}
			}

			// Cancel open profit target order #2, if one
			if (pt_order2 != null)
			{
				Utils.Trap();
				if (PRINT_ORDERS)
					algo.Log($"Cancelling {Symbol.Value} open profit target #2 order {pt_order2}.");
				try
				{
					pt_order2.Cancel();
					pt_order2 = null;
				}
				catch
				{
					algo.Log($"Error trying to cancel {Symbol.Value} profit target #2 order {pt_order2}.");
				}
			}

			// Cancel open profit target order #3, if one
			if (pt_order3 != null)
			{
				Utils.Trap();
				if (PRINT_ORDERS)
					algo.Log($"Cancelling {Symbol.Value} open profit target #3 order {pt_order3}.");
				try
				{
					pt_order3.Cancel();
					pt_order3 = null;
				}
				catch
				{
					algo.Log($"Error trying to cancel {Symbol.Value} profit target #3 order {pt_order3}.");
				}

				// Cancel open stop order, if one
				if (sl_order != null)
				{
					Utils.Trap();
					if (PRINT_ORDERS)
						algo.Log($"Cancelling {Symbol.Value} open stop order {sl_order}.");

					try
					{
						sl_order.Cancel();
						sl_order = null;
					}
					catch
					{
						algo.Log($"Error trying to cancel {Symbol.Value} stop loss order {sl_order}.");
					}
				}
			}

			// Reset order variables
			ResetOrderVariables();
		}

		/// <summary>
		/// Get the profit target exit quantities for orders #1, #2, #3.
		/// </summary>
		/// <param name="initial">true if first time calculating the order quantity. false if updating the quantity.</param>
		/// <returns>The profit taking order quantities for profit taking level 1, level 2, & level 3.</returns>
		public Tuple<int, int, int> GetPtExitQuantities(bool initial = false)
		{

			// need as float to multiply times the percentages.
			float exitQty = (float) -CurrentQuantity;

			// Initialize each to 0
			int pt1ExitQty = 0;
			int pt2ExitQty = 0;
			int pt3ExitQty = 0;

			// Get PT1, PT2, PT3 exit quantities
			if ((initial && PROFIT_TARGET) || (pt_order1 != null))
				pt1ExitQty = Convert.ToInt32(exitQty * PT1_ORDER_PCT);

			if ((initial && PROFIT_TARGET) || (pt_order2 != null))
				pt2ExitQty = Convert.ToInt32(exitQty * PT2_ORDER_PCT);

			if ((initial && PROFIT_TARGET) || (pt_order3 != null))
				pt3ExitQty = (int) (exitQty - (pt1ExitQty + pt2ExitQty));

			// Return the quantities
			return Tuple.Create(pt1ExitQty, pt2ExitQty, pt3ExitQty);
		}

		/// <summary>
		/// Update any open exit orders.
		/// </summary>
		public void UpdateExitOrders()
		{

			// Get the desired profit target exit order quantities
			var exitQuantities = GetPtExitQuantities();

			// Update open profit target order #1, if one
			if (pt_order1 != null)
			{
				Utils.Trap();
				if (PRINT_ORDERS)
					algo.Log($"Updating {Symbol.Value} open profit target #1 order qty to {exitQuantities.Item1}.");
				UpdateOrderQty(pt_order1, exitQuantities.Item1, $"updating profit taking quantity to {exitQuantities.Item1}");
			}

			// Update open profit target order #2, if one
			if (pt_order2 != null)
			{
				Utils.Trap();
				if (PRINT_ORDERS)
					algo.Log($"Updating {Symbol.Value} open profit target #2 order qty to {exitQuantities.Item2}.");
				UpdateOrderQty(pt_order2, exitQuantities.Item2, $"updating profit taking quantity to {exitQuantities.Item2}");

			}

			// Update open profit target order #3, if one
			if (pt_order3 != null)
			{
				Utils.Trap();
				if (PRINT_ORDERS)
					algo.Log($"Updating {Symbol.Value} open profit target #3 order qty to {exitQuantities.Item3}.");
				UpdateOrderQty(pt_order3, exitQuantities.Item3, $"updating profit taking quantity to {exitQuantities.Item3}");
			}

			// Update open stop loss order, if one
			if (sl_order != null)
			{
				if (PRINT_ORDERS)
					algo.Log($"Updating {Symbol.Value} open stop loss order qty to {-CurrentQuantity}.");
				UpdateOrderQty(sl_order, -CurrentQuantity, $"updating stop loss quantity to {-CurrentQuantity}");
			}
		}

		/// <summary>
		/// Update the desired order ticket's qty.
		/// </summary>
		/// <param name="ticket">The ticket to update.</param>
		/// <param name="qty">The quantity to change it to.</param>
		public void UpdateOrderQty(OrderTicket ticket, decimal qty, string tag)
		{

			if (PRINT_UPDATES)
				algo.Log($"{Symbol.Value} {tag}");
			ticket.UpdateQuantity(qty, tag: tag);
		}

		/// <summary>
		/// Update the desired order ticket's stop price.
		/// </summary>
		/// <param name="ticket">The ticket to update.</param>
		/// <param name="price">The new price for the ticket.</param>
		public void UpdateOrderStopPrice(OrderTicket ticket, decimal price)
		{

			if (PRINT_UPDATES)
				algo.Log($"{Symbol.Value} updating stop price to {price}");
			ticket.UpdateStopPrice(price, tag: $"updating stop price to {price}");
		}

		/// <summary>
		/// Update the desired order ticket's limit price.
		/// </summary>
		/// <param name="ticket">The ticket to update.</param>
		/// <param name="price">The new price for the ticket.</param>
		public void UpdateOrderLimitPrice(OrderTicket ticket, decimal price)
		{

			Utils.Trap();
			if (PRINT_UPDATES)
				algo.Log($"{Symbol.Value} updating limit price to {price}");
			ticket.UpdateLimitPrice(price, tag: $"updating limit price to {price}");
		}

		/// <summary>
		/// Place the desired stop loss order. This assumes that StopPrice will not return a null.
		/// </summary>
		public void PlaceStopLossOrder()
		{

			// Save and place the stop loss order
			sl_price = StopPrice;
			sl_order = algo.StopMarketOrder(Symbol, (int) -CurrentQuantity, sl_price.Value, tag: $"initial stop price of {sl_price.Value}");
			if (PRINT_ORDERS)
				algo.Log($"{Symbol.Value} stop order placed at {sl_price.Value}");

		}

		/// <summary>
		/// Place the desired profit target orders.
		/// </summary>
		public void PlaceProfitTakingOrders()
		{

			// Get the desired profit target exit order quantities
			var exitQuantities = GetPtExitQuantities(initial: true);

			if (PROFIT_TARGET && cost_basis != null && CurrentQuantity != 0)
			{
				Utils.Trap();
				// Check for placing a profit target order #1

				// Calculate the profit target price
				decimal ptPrice = 0;
				// long
				if (CurrentQuantity > 0)
					ptPrice = Math.Round(cost_basis.Value * (decimal) (1 + PT1_PCT), 2);
				// short
				else if (CurrentQuantity < 0)
					ptPrice = Math.Round(cost_basis.Value * (decimal) (1 - PT1_PCT), 2);

				// Place and save the stop loss order
				pt_order1 = algo.LimitOrder(Symbol, exitQuantities.Item1, ptPrice);
				if (PRINT_ORDERS)
					algo.Log($"{Symbol.Value} profit target #1 order: {exitQuantities.Item1} at {ptPrice}");

				// Check for placing a profit target order #2

				// Calculate the profit target price
				ptPrice = 0;
				// long
				if (CurrentQuantity > 0)
					ptPrice = Math.Round(cost_basis.Value * (decimal) (1 + PT2_PCT), 2);
				// short
				else if (CurrentQuantity < 0)
					ptPrice = Math.Round(cost_basis.Value * (decimal) (1 - PT2_PCT), 2);

				// Place and save the stop loss order
				pt_order2 = algo.LimitOrder(Symbol, exitQuantities.Item2, ptPrice);
				if (PRINT_ORDERS)
					algo.Log($"{Symbol.Value} profit target #2 order: {exitQuantities.Item2} at {ptPrice}");

				// Check for placing a profit target order #3

				// Calculate the profit target price
				ptPrice = 0;
				// long
				if (CurrentQuantity > 0)
					ptPrice = Math.Round(cost_basis.Value * (decimal) (1 + PT3_PCT), 2);
				// short
				else if (CurrentQuantity < 0)
					ptPrice = Math.Round(cost_basis.Value * (decimal) (1 - PT3_PCT), 2);

				// Place and save the stop loss order
				pt_order3 = algo.LimitOrder(Symbol, exitQuantities.Item3, ptPrice);
				if (PRINT_ORDERS)
					algo.Log($"{Symbol.Value} profit target #3 order: {exitQuantities.Item3} at {ptPrice}");
			}
		}

		/// <summary>
		/// Order fill event handler. On an order fill update the resulting information is passed to this method.
		/// </summary>
		/// <param name="orderEvent">Order event details containing details of the evemts</param>
		public void OnOrderEvent(OrderEvent orderEvent)
		{

			// Get the order details
			var order = algo.Transactions.GetOrderById(orderEvent.OrderId);
			var order_qty = Convert.ToInt32(order.Quantity);
			var avg_fill = orderEvent.FillPrice;

			// Get current qty of symbol
			var qty = CurrentQuantity;

			// Check for entry order - Entry order filled
			if (order_qty == qty)
			{
				// new purchase so reset
				percentSold = 0;
				
				if (PRINT_ORDERS)
					algo.Log($"{Symbol.Value} entry order filled: {order_qty} at {avg_fill}");

				// Save the cost basis and trade best price
				cost_basis = avg_fill;
				// use fill until next call where we can get Low.
				todaysLow = avg_fill;

				// Place a stop loss order when desired
				if (STOP_LOSS || TRAILING_STOP)
					PlaceStopLossOrder();
				else
					Utils.Trap();

				// Place profit target orders when desired
				PlaceProfitTakingOrders();

				// Done with event, so return
				return;
			}

			// Check for stop order
			if (sl_order != null)
			{
				// Check for matching order ids
				if (orderEvent.OrderId == sl_order.OrderId)
				{
					// Stop order filled
					if (PRINT_ORDERS)
						algo.Log($"{Symbol.Value} stop order filled: {order_qty} at {avg_fill}");

					// Cancel open exit orders
					CancelExitOrders();

					// Done with event, so return
					return;
				}
			}

			// Check for profit target order #1
			if (pt_order1 != null)
			{
				Utils.Trap();
				// Check for matching order ids - Profit target order filled
				if (orderEvent.OrderId == pt_order1.OrderId)
				{
					Utils.Trap();
					if (PRINT_ORDERS)
						algo.Log($"{Symbol.Value} profit target order #1 filled: {order_qty} at {avg_fill}");

					// Check if the position is still open
					if (qty != 0)
					{
						Utils.Trap();
						// Update open exit orders
						UpdateExitOrders();
					}
					else
					{
						Utils.Trap();
						// Cancel open exit orders
						CancelExitOrders();
					}

					// Set order to None
					pt_order1 = null;

					// Done with event, so return
					return;
				}

				Utils.Trap();
			}

			// Check for profit target order #2
			if (pt_order2 != null)
			{
				Utils.Trap();
				// Check for matching order ids - Profit target order filled
				if (orderEvent.OrderId == pt_order2.OrderId)
				{
					Utils.Trap();
					if (PRINT_ORDERS)
						algo.Log("${Symbol.Value} profit target order #2 filled: {order_qty} at {avg_fill}");

					// Check if the position is still open
					if (qty != 0)
					{
						Utils.Trap();
						// Update open exit orders
						UpdateExitOrders();
					}
					else
					{
						Utils.Trap();
						// Cancel open exit orders
						CancelExitOrders();
					}

					// Set order to None
					pt_order2 = null;

					// Done with event, so return
					return;
				}
			}

			// Check for profit target order #3
			if (pt_order3 != null)
			{
				Utils.Trap();
				// Check for matching order ids - Profit target order filled
				if (orderEvent.OrderId == pt_order3.OrderId)
				{
					Utils.Trap();
					if (PRINT_ORDERS)
						algo.Log($"{Symbol.Value} profit target order #3 filled: {order_qty} at {avg_fill}");

					// Check if the position is still open
					if (qty != 0)
					{
						Utils.Trap();
						// Update open exit orders
						UpdateExitOrders();
					}
					else
					{
						Utils.Trap();
						// Cancel open exit orders
						CancelExitOrders();
					}

					// Set order to None
					pt_order3 = null;

					// Done with event, so return
					return;
				}
			}

			// Check for full exit order - Exit order filled
			if (qty == 0)
			{
				// liquidated so reset
				percentSold = 0;
				
				if (PRINT_ORDERS)
					algo.Log($"{Symbol.Value} full exit order filled: {order_qty} at {avg_fill}");

				// Cancel open exit orders
				CancelExitOrders();

				// Done with event, so return
				return;
			}

			// Check for pyramid entry order (qty and order_qty have the same signs)
			if (qty * order_qty > 0)
			{
				Utils.Trap();
				// This strategy doesn't have pyramid entries, so raise error

				// throw new ApplicationException("pyramid entry order");

				algo.Log($"***** pyramid entry order for {Symbol.Value}, qty={qty}, order_qty={order_qty}");

				algo.Liquidate(this.Symbol);
				return;
			}

			// Otherwise a partial exit order - Partial exit order filled
			if (PRINT_ORDERS)
				algo.Log($"{Symbol.Value} partial exit order filled: {order_qty} at {avg_fill}");

			// Update open exit orders
			UpdateExitOrders();
		}
		
		///<summary>
		/// Returns the slope of the RSI over the last 5 days.
		///</summary>
		public double RSISlope {
			get {
				double [] xData = {1, 2, 3, 4, 5};
				double [] yData = window_rsi.Take(5).Reverse().ToArray();
				Tuple<double, double> line = Fit.Line(xData, yData);
				return line.Item2;
			}
		}
		
		///<summary>
		/// Returns true if the RSI values are set to liquidate (falling and below RSI_EXIT_2).
		///</summary>
		public bool RSILiquidate {
			get {
				return RSISlope <= 0 && rsi.Current.Value < algo.RsiExit2Value;
			}
		}
		
		public override string ToString()
		{
			return $"Symbol:{Symbol.Value}; EMAFast:{ToString(ema_fast)}; EMASlow:{ToString(ema_slow)}; RSI:{ToString(rsi)}; FastGTSlow={ToString(fast_ema_gt_slow_ema)}; " +
			$" winCloses:{ToString(window_closes)}; winEmaFast:{ToString(window_ema_fast)}; winEmaSlow:{ToString(window_ema_slow)}; winRsi:{ToString(window_rsi)}; winBar:{ToString(window_bar)}";
		}
		
		private string ToString(Indicator indicator) {
			return $"[IsReady:{indicator.IsReady}; Value:{indicator}";
		}
		
		private string ToString<T>(RollingWindow<T> window) {
			return $"[IsReady:{window.IsReady}; Count:{window.Count}/{window.Size}" + (window.Count == 0 ? "" : $"[0]: {window[0]}") + "]";
		}
		
		private string ToString(RollingWindow<decimal> window) {
			return $"[IsReady:{window.IsReady}; Count:{window.Count}/{window.Size}" + (window.Count == 0 ? "" : $"[0]: {window[0].ToString("C")}") + "]";
		}

		private string ToString(RollingWindow<double> window) {
			return $"[IsReady:{window.IsReady}; Count:{window.Count}/{window.Size}" + (window.Count == 0 ? "" : $"[0]: {window[0].ToString("F2")}") + "]";
		}
		
		public static string RollingWindowToString(RollingWindow<decimal> window) {
			StringBuilder buf = new StringBuilder("[");
			for (int index=INDICATOR_WINDOW_LENGTH-1; index>=0; index--)
				buf.Append($"{window[index]:C}, ");
			buf.Remove(buf.Length - 2, 2);
			buf.Append("]");
			return buf.ToString();
		}
		
		private string RSIRollingWindowToString() {
			StringBuilder buf = new StringBuilder("[");
			for (int index=INDICATOR_WINDOW_LENGTH-1; index>=0; index--)
				buf.Append($"{window_rsi[index]:F2}, ");
			buf.Remove(buf.Length - 2, 2);
			buf.Append($" = {RSISlope:F4}");
			buf.Append("]");
			return buf.ToString();
		}
	}
}