Overall Statistics
Total Trades
2142
Average Win
3.06%
Average Loss
-0.61%
Compounding Annual Return
11.071%
Drawdown
52.400%
Expectancy
0.313
Net Profit
414.271%
Sharpe Ratio
0.453
Probabilistic Sharpe Ratio
0.375%
Loss Rate
78%
Win Rate
22%
Profit-Loss Ratio
5.00
Alpha
0.025
Beta
1.08
Annual Standard Deviation
0.23
Annual Variance
0.053
Information Ratio
0.22
Tracking Error
0.142
Treynor Ratio
0.097
Total Fees
$7801.24
Estimated Strategy Capacity
$1600000.00
Lowest Capacity Asset
VTI S551B7YE6N39
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC
{
    using ComposerQC.Model;

    /// <summary>
    /// Base class for strategies that follow a calendar strategy.
    /// </summary>
    public abstract class CalendarStrategy : StrategyBase
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CalendarStrategy"/> class.
        /// </summary>
        /// <param name="algorithm">Executing algorithm.</param>
        public CalendarStrategy(ComposerQCAlgorithm algorithm)
            : base(algorithm)
        {
        }
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC
{
    using System;
    using System.Linq;
    using ComposerQC.Model;
    using QuantConnect;
    using QuantConnect.Algorithm;
    using QuantConnect.Data;
    using QuantConnect.Parameters;

    /// <summary>
    /// ComposerQC trading algorithm base class.
    /// </summary>
    public abstract class ComposerQCAlgorithm : QCAlgorithm
    {
        /// <summary>
        /// Number of bars per day at the chosen data resolution.
        /// </summary>
        public const int BarsPerDay = 390;

        /// <summary>
        /// Symbol used for benchmarking.
        /// </summary>
        public const string BenchmarkTicker = "SPY";

        /// <summary>
        /// Data resolution for fetching history and subscriptions.
        /// </summary>
        public const Resolution DataResolution = Resolution.Minute;

        /// <summary>
        /// ComposerQC version string.
        /// </summary>
        public const string ComposerQCVersion = "1.0.0-alpha.1";

        /// <summary>
        /// Offset from the execution time, in minutes, to consolidate trade bar data.
        /// </summary>
        public const int ConsolidationTimeOffset = 1;

        // Performance plotting fields
        private decimal lastBenchmarkValue;
        private decimal benchmarkPerformance;
        private Symbol benchmarkSymbol = default!;

        /// <summary>
        /// Time of day for trade execution to occur.
        /// </summary>
        [Parameter("ExecutionTime")]
        public TimeSpan ExecutionTime { get; set; }

        /// <summary>
        /// Optional end date for backtesting.
        /// </summary>
        public DateTime? BacktestEndDate { get; set; }

        /// <summary>
        /// If set, start testing on this date instead of the strategy's start date.
        /// </summary>
        public DateTime? BacktestStartDate { get; set; }

        /// <summary>
        /// Time of day for trade bar consolidation to occur.
        /// </summary>
        public TimeSpan ConsolidationTime { get; private set; }

        /// <summary>
        /// Gets the strategy this algorithm should use.
        /// </summary>
        public IStrategy Strategy { get; private set; } = default!;

        /// <summary>
        /// Initializes the algorithm.
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown when the consolidation time is not set.</exception>
        public override void Initialize()
        {
            this.Log($"ComposerQC version {ComposerQCVersion} initializing.");

            if (this.ExecutionTime == default)
            {
                throw new InvalidOperationException($"{nameof(this.ExecutionTime)} is not set.");
            }

            this.ConsolidationTime = this.ExecutionTime.Add(TimeSpan.FromMinutes(-ConsolidationTimeOffset));
            this.Strategy = this.SetupStrategy();
            this.SetStartDate(this.BacktestStartDate ?? this.Strategy.BacktestStartDate);
            this.SetCash(10000);

            if (this.BacktestEndDate != null)
            {
                this.SetEndDate((DateTime)this.BacktestEndDate);
            }

            foreach (var ticker in this.Strategy.Tickers)
            {
                var equity = this.AddEquity(ticker, DataResolution, Market.USA);
                this.Strategy.AddSymbolData(equity);
            }

            // Always add the benchmark
            this.benchmarkSymbol = this.AddEquity(BenchmarkTicker, DataResolution, Market.USA).Symbol;
            this.SetBenchmark(this.benchmarkSymbol);
            this.benchmarkPerformance = this.Portfolio.TotalPortfolioValue;

            // Warm up the indicators
            this.SetWarmUp(this.Strategy.Periods.Max() * BarsPerDay, DataResolution);
        }

        /// <inheritdoc/>
        public override void OnWarmupFinished()
        {
            // Schedule strategy evaluation
            _ = this.Schedule.On(
                this.Strategy?.EvaluationDateRule,
                this.TimeRules.At(this.ExecutionTime),
                this.ExecuteStrategy);

            // Schedule plotting benchmark graph
            if (this.LiveMode)
            {
                _ = this.Schedule.On(
                    this.DateRules.EveryDay(),
                    this.TimeRules.BeforeMarketClose(this.benchmarkSymbol),
                    this.PlotBenchmark);
            }
            else
            {
                _ = this.Schedule.On(
                    this.DateRules.Every(DayOfWeek.Friday),
                    this.TimeRules.BeforeMarketClose(this.benchmarkSymbol),
                    this.PlotBenchmark);
            }

            // Initial execution for algorithm start
            if (!this.Portfolio.Invested)
            {
                _ = this.Schedule.On(
                    this.DateRules.On(this.Time.Year, this.Time.Month, this.Time.Day),
                    this.TimeRules.At(this.ExecutionTime),
                    this.ExecuteStrategy);
            }
        }

        /// <inheritdoc/>
        public override void OnData(Slice slice)
        {
            var benchmark = this.Securities[BenchmarkTicker].Close;

            if (this.lastBenchmarkValue != default)
            {
                this.benchmarkPerformance *= benchmark / this.lastBenchmarkValue;
            }

            this.lastBenchmarkValue = benchmark;
        }

        /// <inheritdoc/>
        public override void OnEndOfAlgorithm() =>
            this.PlotBenchmark();

        /// <summary>
        /// Creates a strategy for the algorithm to execute.
        /// </summary>
        /// <returns>Created <see cref="IStrategy"/>.</returns>
        public abstract IStrategy SetupStrategy();

        /// <summary>
        /// Executes the strategy defined in this algorithm.
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown when a strategy is not created by <see cref="SetupStrategy"/>.</exception>
        private void ExecuteStrategy()
        {
            if (this.Strategy == null)
            {
                throw new InvalidOperationException("No strategy provided.");
            }

            var targets = this.Strategy.Evaluate();
            var targetSymbols = targets.Select(x => x.Symbol).ToList();

            var investedSymbols = this.Portfolio.Values
                .Where(x => x.Invested)
                .Select(x => x.Symbol)
                .ToList();

            var liquidateExistingHoldings = investedSymbols.Any(x => !targetSymbols.Contains(x));

            this.SetHoldings(targets, liquidateExistingHoldings);
        }

        /// <summary>
        /// Plots the benchmark on the strategy equity graph.
        /// </summary>
        private void PlotBenchmark() =>
            this.Plot("Strategy Equity", "Benchmark", this.benchmarkPerformance);
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC.DateRules
{
    using System;
    using System.Collections.Generic;
    using QuantConnect.Scheduling;

    /// <summary>
    /// Base class for ComposerQC date rules.
    /// </summary>
    public abstract class ComposerQCDateRule : IDateRule
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ComposerQCDateRule"/> class.
        /// </summary>
        /// <param name="algorithm">Executing algorithm</param>
        public ComposerQCDateRule(ComposerQCAlgorithm algorithm) =>
            this.Algorithm = algorithm;

        /// <inheritdoc/>
        public abstract string Name { get; }

        /// <summary>
        /// Gets the executing algorithm.
        /// </summary>
        protected ComposerQCAlgorithm Algorithm { get; }

        /// <inheritdoc/>
        public abstract IEnumerable<DateTime> GetDates(DateTime start, DateTime end);
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC.DateRules
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using QuantConnect;

    /// <summary>
    /// Date rule that triggers strategy evaluation every trading day.
    /// </summary>
    public class DailyDateRule : ComposerQCDateRule
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="DailyDateRule"/> class.
        /// </summary>
        /// <param name="algorithm">Executing algorithm.</param>
        public DailyDateRule(ComposerQCAlgorithm algorithm)
            : base(algorithm)
        {
        }

        /// <inheritdoc/>
        public override string Name => "ComposerQC.DailyDateRule";

        /// <inheritdoc/>
        public override IEnumerable<DateTime> GetDates(DateTime start, DateTime end) =>
            this.Algorithm.TradingCalendar
                .GetDaysByType(TradingDayType.BusinessDay, start, end)
                .Select(x => x.Date)
                .ToList();
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC.DateRules
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using QuantConnect;

    /// <summary>
    /// Date rule that triggers strategy evaluation on the first trading day of every month.
    /// </summary>
    public class MonthlyDateRule : ComposerQCDateRule
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MonthlyDateRule"/> class.
        /// </summary>
        /// <param name="algorithm">Executing algorithm.</param>
        public MonthlyDateRule(ComposerQCAlgorithm algorithm)
            : base(algorithm)
        {
        }

        /// <inheritdoc/>
        public override string Name => "ComposerQC.MonthlyDateRule";

        /// <inheritdoc/>
        public override IEnumerable<DateTime> GetDates(DateTime start, DateTime end) =>
            this.Algorithm.TradingCalendar
                .GetDaysByType(TradingDayType.BusinessDay, start, end)
                .Select(x => x.Date)
                .OrderBy(x => x)
                .GroupBy(x => new { x.Year, x.Month })
                .Select(g => g.First())
                .ToList();
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC.DateRules
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using QuantConnect;

    /// <summary>
    /// Date rule that triggers strategy evaluation on the first trading day of every quarter.
    /// </summary>
    public class QuarterlyDateRule : ComposerQCDateRule
    {
        private static readonly List<int> FirstWeeksOfQuarters = new() { 1, 14, 27, 40 };

        /// <summary>
        /// Initializes a new instance of the <see cref="QuarterlyDateRule"/> class.
        /// </summary>
        /// <param name="algorithm">Executing algorithm.</param>
        public QuarterlyDateRule(ComposerQCAlgorithm algorithm)
            : base(algorithm)
        {
        }

        /// <inheritdoc/>
        public override string Name => "ComposerQC.QuarterlyDateRule";

        /// <inheritdoc/>
        public override IEnumerable<DateTime> GetDates(DateTime start, DateTime end) =>
            this.Algorithm.TradingCalendar
                .GetDaysByType(TradingDayType.BusinessDay, start, end)
                .Select(x => x.Date)
                .OrderBy(x => x)
                .GroupBy(x => new { x.Year, Week = ISOWeek.GetWeekOfYear(x) })
                .Select(g => g.First())
                .Where(x => FirstWeeksOfQuarters.Contains(ISOWeek.GetWeekOfYear(x)))
                .ToList();
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC.DateRules
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using QuantConnect;

    /// <summary>
    /// Date rule that triggers strategy evaluation on the first trading day of every week.
    /// </summary>
    public class WeeklyDateRule : ComposerQCDateRule
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="WeeklyDateRule"/> class.
        /// </summary>
        /// <param name="algorithm">Executing algorithm.</param>
        public WeeklyDateRule(ComposerQCAlgorithm algorithm)
            : base(algorithm)
        {
        }

        /// <inheritdoc/>
        public override string Name => "ComposerQC.WeeklyDateRule";

        /// <inheritdoc/>
        public override IEnumerable<DateTime> GetDates(DateTime start, DateTime end) =>
            this.Algorithm.TradingCalendar
                .GetDaysByType(TradingDayType.BusinessDay, start, end)
                .Select(x => x.Date)
                .OrderBy(x => x)
                .GroupBy(x => new { x.Year, Week = ISOWeek.GetWeekOfYear(x) })
                .Select(g => g.First())
                .ToList();
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC.DateRules
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using QuantConnect;

    /// <summary>
    /// Date rule that triggers strategy evaluation on the first trading day of every year.
    /// </summary>
    public class YearlyDateRule : ComposerQCDateRule
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="YearlyDateRule"/> class.
        /// </summary>
        /// <param name="algorithm">Executing algorithm.</param>
        public YearlyDateRule(ComposerQCAlgorithm algorithm)
            : base(algorithm)
        {
        }

        /// <inheritdoc/>
        public override string Name => "ComposerQC.YearlyDateRule";

        /// <inheritdoc/>
        public override IEnumerable<DateTime> GetDates(DateTime start, DateTime end) =>
            this.Algorithm.TradingCalendar
                .GetDaysByType(TradingDayType.BusinessDay, start, end)
                .Select(x => x.Date)
                .OrderBy(x => x)
                .GroupBy(x => new { x.Year, Week = ISOWeek.GetWeekOfYear(x) })
                .Select(g => g.First())
                .Where(x => ISOWeek.GetWeekOfYear(x) == 1)
                .ToList();
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC
{
    /// <summary>
    /// Indicates a filter function to use.
    /// </summary>
    public enum FilterBy
    {
        /// <summary>
        /// Filters by the current price of the tickers.
        /// </summary>
        CurrentPrice,

        /// <summary>
        /// Filters by the cumulative return.
        /// </summary>
        CumulativeReturn,

        /// <summary>
        /// Filters by the standard deviation of price.
        /// </summary>
        StdDevOfPrice,

        /// <summary>
        /// Filters by the standard deviation of return.
        /// </summary>
        StdDevOfReturn,

        /// <summary>
        /// Filters by the maximum drawdown.
        /// </summary>
        MaxDrawdown,

        /// <summary>
        /// Filters by the moving average of price.
        /// </summary>
        MovAvgOfPrice,

        /// <summary>
        /// Filters by the moving average of return.
        /// </summary>
        MovAvgOfReturn,

        /// <summary>
        /// Filters by the exponential moving average of price.
        /// </summary>
        ExpMovAvgOfPrice,

        /// <summary>
        /// Filters by the relatve strength index.
        /// </summary>
        RSI,
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC.Model
{
    using System;
    using System.Collections.Generic;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities.Equity;

    /// <summary>
    /// Strategy that <see cref="ComposerQCAlgorithm"/> can execute.
    /// </summary>
    public interface IStrategy
    {
        /// <summary>
        /// Gets the earliest backtesting date a strategy can use.
        /// </summary>
        DateTime BacktestStartDate { get; }

        /// <summary>
        /// Gets the date rule for strategy evaluation.
        /// </summary>
        IDateRule EvaluationDateRule { get; }

        /// <summary>
        /// Gets the list of all indicator periods this strategy uses.
        /// </summary>
        IEnumerable<int> Periods { get; }

        /// <summary>
        /// Gets the list of all tickers this strategy uses.
        /// </summary>
        IEnumerable<string> Tickers { get; }

        /// <summary>
        /// Adds the specified <paramref name="equity"/> to the strategy's symbol data.
        /// </summary>
        /// <remarks>
        /// Used by <see cref="ComposerQCAlgorithm"/>. Do not invoke directly.
        /// </remarks>
        /// <param name="equity">Equity to add.</param>
        void AddSymbolData(Equity equity);

        /// <summary>
        /// Evaluates the strategy and returns a list of portfolio targets.
        /// </summary>
        /// <returns><see cref="List{T}"/> of <see cref="PortfolioTarget"/>s.</returns>
        List<PortfolioTarget> Evaluate();
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC.Model
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities.Equity;

    /// <summary>
    /// Base class for Composer strategies.
    /// </summary>
    public abstract class StrategyBase : IStrategy
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="StrategyBase"/> class.
        /// </summary>
        /// <param name="algorithm">Executing algorithm.</param>
        public StrategyBase(ComposerQCAlgorithm algorithm) =>
            this.Algorithm = algorithm;

        /// <inheritdoc/>
        public DateTime BacktestStartDate { get; private set; }

        /// <inheritdoc/>
        public IDateRule EvaluationDateRule { get; protected set; } = default!;

        /// <inheritdoc/>
        public abstract IEnumerable<int> Periods { get; }

        /// <inheritdoc/>
        public abstract IEnumerable<string> Tickers { get; }

        /// <summary>
        /// Gets the algorithm this strategy is executing in.
        /// </summary>
        protected ComposerQCAlgorithm Algorithm { get; }

        /// <summary>
        /// Gets a dictionary of all symbol data this strategy has, keyed by ticker.
        /// </summary>
        protected Dictionary<string, SymbolData> SymbolData { get; } = new();

        /// <inheritdoc/>
        public abstract List<PortfolioTarget> Evaluate();

        /// <inheritdoc/>
        public void AddSymbolData(Equity equity)
        {
            var symbolData = new SymbolData(this.Algorithm, equity.Symbol, this.Periods);
            this.SymbolData.Add(equity.Symbol.Value, symbolData);
        }

        /// <summary>
        /// Equally weights a list of tickers.
        /// </summary>
        /// <param name="tickers">List of tickers to equally weight.</param>
        /// <param name="startWeight">Starting weight to balance equities within.</param>
        /// <returns>Equally weighted <see cref="List{T}"/> of <see cref="PortfolioTarget"/>s.</returns>
        protected static List<PortfolioTarget> EqualWeight(IEnumerable<string> tickers, decimal startWeight = 1m)
        {
            var targets = new List<PortfolioTarget>();

            foreach (var ticker in tickers)
            {
                var symbol = Symbol.Create(ticker, SecurityType.Equity, Market.USA);
                targets.Add(new PortfolioTarget(symbol, startWeight / tickers.Count()));
            }

            return targets;
        }

        /// <summary>
        /// Filters a list of <paramref name="tickers"/> according to an indicated
        /// <paramref name="filterFunction"/> and <paramref name="selectFunction"/>.
        /// </summary>
        /// <param name="tickers">Tickers to filter.</param>
        /// <param name="filterFunction">Filter function to apply.</param>
        /// <param name="window">Number of periods for the <paramref name="filterFunction"/> indicator.</param>
        /// <param name="selectFunction">Selection function to apply.</param>
        /// <param name="count">Number of tickers to return from the <paramref name="selectFunction"/>.</param>
        /// <returns>A <see cref="List{T}"/> of filtered equities.</returns>
        /// <exception cref="ArgumentException">Thrown when an unknown filter function is provided.</exception>
        protected List<string> Filter(IEnumerable<string> tickers, FilterBy filterFunction, int window, Select selectFunction, int count)
        {
            var selectedSymbols = this.SymbolData
                .Where(x => tickers.Contains(x.Key))
                .ToList();

            var projectedSymbols = filterFunction switch
            {
                FilterBy.CumulativeReturn =>
                    selectedSymbols.ToDictionary(x => x.Key, x => x.Value.CumulativeReturn(window)),

                FilterBy.CurrentPrice =>
                    selectedSymbols.ToDictionary(x => x.Key, x => x.Value.CurrentPrice()),

                FilterBy.StdDevOfPrice =>
                    selectedSymbols.ToDictionary(x => x.Key, x => x.Value.StdDevOfPrice(window)),

                FilterBy.StdDevOfReturn =>
                    selectedSymbols.ToDictionary(x => x.Key, x => x.Value.StdDevOfReturn(window)),

                FilterBy.MaxDrawdown =>
                    selectedSymbols.ToDictionary(x => x.Key, x => x.Value.MaxDrawdown(window)),

                FilterBy.MovAvgOfPrice =>
                    selectedSymbols.ToDictionary(x => x.Key, x => x.Value.MovAvgOfPrice(window)),

                FilterBy.MovAvgOfReturn =>
                    selectedSymbols.ToDictionary(x => x.Key, x => x.Value.MovAvgOfReturn(window)),

                FilterBy.ExpMovAvgOfPrice =>
                    selectedSymbols.ToDictionary(x => x.Key, x => x.Value.ExpMovAvgOfPrice(window)),

                FilterBy.RSI =>
                    selectedSymbols.ToDictionary(x => x.Key, x => x.Value.RSI(window)),

                _ => throw new ArgumentException($"Unrecognized filter function: {filterFunction}"),
            };

            var returnTickers = selectFunction switch
            {
                Select.Bottom =>
                    projectedSymbols
                        .OrderByDescending(x => x.Value)
                        .TakeLast(count)
                        .Select(x => x.Key)
                        .ToList(),

                Select.Top =>
                    projectedSymbols
                        .OrderByDescending(x => x.Value)
                        .Take(count)
                        .Select(x => x.Key)
                        .ToList(),

                _ => throw new ArgumentException($"Unrecognized select function: {selectFunction}"),
            };

            return returnTickers;
        }

        /// <summary>
        /// Sets the earliest backtest date this strategy can use.
        /// </summary>
        /// <param name="year">Year to start backtest on.</param>
        /// <param name="month">Month to start backtest on.</param>
        /// <param name="day">Day to start backtest on.</param>
        protected void SetBacktestStartDate(int year, int month, int day) =>
            this.BacktestStartDate = new DateTime(year, month, day);
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC.Model
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using QuantConnect;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Market;
    using QuantConnect.Indicators;

    /// <summary>
    /// Contains indicators and subscription logic for the symbols a strategy uses.
    /// </summary>
    public class SymbolData : IDisposable
    {
        private readonly ComposerQCAlgorithm algorithm;
        private readonly TradeBarConsolidator consolidator;
        private readonly RollingWindow<decimal> priceHistory;
        private readonly Symbol symbol;

        /// <summary>
        /// Initializes a new instance of the <see cref="SymbolData"/> class.
        /// </summary>
        /// <param name="algorithm">Executing algorithm.</param>
        /// <param name="symbol"><see cref="Symbol"/> to add data for.</param>
        /// <param name="periods">All periods to generate indicators for.</param>
        internal SymbolData(ComposerQCAlgorithm algorithm, Symbol symbol, IEnumerable<int> periods)
        {
            this.algorithm = algorithm;
            this.symbol = symbol;
            this.consolidator = new TradeBarConsolidator(this.CalculateConsolidationDateTime);
            this.priceHistory = new RollingWindow<decimal>(periods.Max());

            // Create the indicators
            this.CreateIndicators(periods);

            // Attach subscription for price feed
            this.consolidator.DataConsolidated += this.OnDataConsolidated;
            this.algorithm.SubscriptionManager.AddConsolidator(symbol, this.consolidator);
        }

        private Dictionary<int, ExponentialMovingAverage> ExpMovAvgOfPriceIndicators { get; } = new();

        private Dictionary<int, SimpleMovingAverage> MovAvgOfPriceIndicators { get; } = new();

        private Dictionary<int, SimpleMovingAverage> MovAvgOfReturnIndicators { get; } = new();

        private List<RateOfChange> ROCIndicators { get; } = new();

        private Dictionary<int, RelativeStrengthIndex> RSIIndicators { get; } = new();

        private Dictionary<int, StandardDeviation> StdDevOfPriceIndicators { get; } = new();

        private Dictionary<int, StandardDeviation> StdDevOfReturnIndicators { get; } = new();

        /// <summary>
        /// Retrieves the current price of the symbol.
        /// </summary>
        /// <returns>Current price of this symbol.</returns>
        public decimal CurrentPrice() => this.algorithm.Securities[this.symbol].Price;

        /// <summary>
        /// Calculates the cumulative return of the symbol.
        /// </summary>
        /// <param name="periods">Number of periods to calculate the cumulative returns over.</param>
        /// <returns>Cumulative return of this symbol over the given <paramref name="periods"/>.</returns>
        public decimal CumulativeReturn(int periods)
        {
            var returns = this.priceHistory[0] / this.priceHistory[1];

            for (var i = 1; i < periods - 2; i++)
            {
                returns *= this.priceHistory[i] / this.priceHistory[i + 1];
            }

            return returns - 1;
        }

        /// <inheritdoc/>
        public void Dispose()
        {
            this.algorithm.SubscriptionManager.RemoveConsolidator(this.symbol, this.consolidator);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Retrieves the exponential moving average of price for the symbol over the specified <paramref name="periods"/>.
        /// </summary>
        /// <param name="periods">Number of periods to calculate the moving average for.</param>
        /// <returns>Exponential moving average of price for the desired timeframe.</returns>
        public decimal ExpMovAvgOfPrice(int periods) => this.ExpMovAvgOfPriceIndicators[periods].Current.Value;

        /// <summary>
        /// Retrieves the simple moving average of price for the symbol over the specified <paramref name="periods"/>.
        /// </summary>
        /// <param name="periods">Number of periods to calculate the moving average of price for.</param>
        /// <returns>Moving average of price for the desired timeframe.</returns>
        public decimal MovAvgOfPrice(int periods) => this.MovAvgOfPriceIndicators[periods].Current.Value;

        /// <summary>
        /// Retrieves the simple moving average of return for the symbol over the specified <paramref name="periods"/>.
        /// </summary>
        /// <param name="periods">Number of periods to calculate the moving average of returns for.</param>
        /// <returns>Moving average of return for the desired timeframe.</returns>
        public decimal MovAvgOfReturn(int periods) => this.MovAvgOfReturnIndicators[periods].Current.Value;

        /// <summary>
        /// Retrieves the relative strength index for the symbol over the specified <paramref name="periods"/>.
        /// </summary>
        /// <param name="periods">Number of periods to calculate the RSI for.</param>
        /// <returns>Relative strength index for the desired timeframe.</returns>
        public decimal RSI(int periods) => this.RSIIndicators[periods].Current.Value;

        /// <summary>
        /// Retrieves the standard deviation of price for the symbol over the specified <paramref name="periods"/>.
        /// </summary>
        /// <param name="periods">Number of periods to calculate the standard deviation of price for.</param>
        /// <returns>Standard deviation of price for the desired timeframe.</returns>
        public decimal StdDevOfPrice(int periods) => this.StdDevOfPriceIndicators[periods].Current.Value;

        /// <summary>
        /// Retrieves the standard deviation of return for the symbol over the specified <paramref name="periods"/>.
        /// </summary>
        /// <param name="periods">Number of periods to calculate the standard deviation of return for.</param>
        /// <returns>Standard deviation of return for the desired timeframe.</returns>
        public decimal StdDevOfReturn(int periods) => this.StdDevOfReturnIndicators[periods].Current.Value;

        /// <summary>
        /// Calculates the maximum drawdown for the symbol over the specified <paramref name="periods"/>.
        /// </summary>
        /// <param name="periods">Number of periods to calculate the drawdown for.</param>
        /// <returns>Maximum drawdown for the desired timeframe.</returns>
        public decimal MaxDrawdown(int periods)
        {
            var peak = this.priceHistory.Take(periods).Max();
            var trough = this.priceHistory.Take(periods).Min();

            return (trough - peak) / peak;
        }

        /// <summary>
        /// Sets up and registers indicators for the symbol.
        /// </summary>
        /// <param name="periods">All periods to create indicators for.</param>
        private void CreateIndicators(IEnumerable<int> periods)
        {
            foreach (var period in periods)
            {
                this.ExpMovAvgOfPriceIndicators[period] = new ExponentialMovingAverage(period);
                this.algorithm.RegisterIndicator(this.symbol, this.ExpMovAvgOfPriceIndicators[period], this.consolidator);

                this.MovAvgOfPriceIndicators[period] = new SimpleMovingAverage(period);
                this.algorithm.RegisterIndicator(this.symbol, this.MovAvgOfPriceIndicators[period], this.consolidator);

                this.RSIIndicators[period] = new RelativeStrengthIndex(period);
                this.algorithm.RegisterIndicator(this.symbol, this.RSIIndicators[period], this.consolidator);

                this.StdDevOfPriceIndicators[period] = new StandardDeviation(period);
                this.algorithm.RegisterIndicator(this.symbol, this.StdDevOfPriceIndicators[period], this.consolidator);

                var movAvgReturnROC = new RateOfChange(period);
                this.ROCIndicators.Add(movAvgReturnROC);
                this.MovAvgOfReturnIndicators[period] = new SimpleMovingAverage(period).Of(movAvgReturnROC, false);
                this.algorithm.RegisterIndicator(this.symbol, movAvgReturnROC, this.consolidator);

                var stdDevReturnROC = new RateOfChange(period);
                this.ROCIndicators.Add(stdDevReturnROC);
                this.StdDevOfReturnIndicators[period] = new StandardDeviation(period).Of(stdDevReturnROC, false);
                this.algorithm.RegisterIndicator(this.symbol, stdDevReturnROC, this.consolidator);
            }
        }

        /// <summary>
        /// Computes the next bar consolidation date and time from the given <paramref name="input"/>.
        /// </summary>
        /// <param name="input"><see cref="DateTime"/> to compute the next consolidation time from.</param>
        /// <returns>A <see cref="CalendarInfo"/> indicating the next consolidation time.</returns>
        private CalendarInfo CalculateConsolidationDateTime(DateTime input)
        {
            var period = TimeSpan.FromDays(1);
            var start = input.Date + this.algorithm.ConsolidationTime;

            if (start > input)
            {
                start -= period;
            }

            return new CalendarInfo(start, period);
        }

#pragma warning disable CS8632 // Annotation for nullable reference types
        /// <summary>
        /// <see cref="TradeBarConsolidator"/>.DataConsolidated event handler.
        /// </summary>
        /// <param name="sender">Object sending event.</param>
        /// <param name="data">Consolidated <see cref="TradeBar"/> data.</param>
        private void OnDataConsolidated(object? sender, TradeBar data) =>
            this.priceHistory.Add(data.Close);
#pragma warning restore CS8632 // Annotation for nullable reference types
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace ComposerQC
{
    /// <summary>
    /// Indicates a selection method.
    /// </summary>
    public enum Select
    {
        /// <summary>
        /// Select the top equities by filter function.
        /// </summary>
        Top,

        /// <summary>
        /// Select the bottom equities by filter function.
        /// </summary>
        Bottom,
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace QuantConnect.Algorithm.CSharp
{
    using System;
    using ComposerQC;
    using ComposerQC.Model;

    /**
     * The ComposerQCAlgorithm class handles all aspects related to strategy and trade execution.
     */
    public class ComposerQCSampleAlgorithm : ComposerQCAlgorithm
    {
        /// <summary>
        /// This is where we set up the strategy and hand it over for execution.
        /// </summary>
        public override IStrategy SetupStrategy()
        {
            /**
             * Uncomment the line below if you would like to start your backtest
             * at a date LATER than the strategy would.
             */
            //BacktestStartDate = new DateTime(2022, 1, 1);

            /**
             * Uncomment the line below if you would like to end your backtest
             * earlier than the current day.
             */
            //BacktestEndDate = new DateTime(2022, 11, 8);

            // This is where we create and return the strategy for execution.
            return new SampleStrategy(this);
        }
    }
}
// ComposerQC - backtesting companion for Invest Composer.
// Copyright (C) 2022 SolarianKnight.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

namespace QuantConnect
{
    using System.Collections.Generic;
    using ComposerQC;
    using ComposerQC.DateRules;
    using QuantConnect.Algorithm.Framework.Portfolio;

    /**
     * This is a sample strategy that picks the top performer of two leveraged funds
     * when SPY is above its 200 day moving average of price, and holds a 60/40 VTI/BND
     * portfolio when it is not.
     *
     * This is not intended to be a profitable strategy, but is instead intended to
     * illustrate how to write strategies in ComposerQC.
     */
    public class SampleStrategy : CalendarStrategy
    {
        // Set each of the periods your strategy's indicators use here.
        // These must be set ahead of time for the algorithm to warm up history properly.
        public override IEnumerable<int> Periods => new[] { 90, 200 };

        // Set all of the tickers your strategy uses here.
        // This includes tickers you invest in, as well as use for an indicator.
        public override IEnumerable<string> Tickers => new[] { "SPY", "SSO", "QLD", "VTI", "BND" };

        /**
         * This is the class constructor, and acts as an initializer for your strategy.
         *
         * You will set your strategy's earliest possible backtesting date here,
         * and the frequency with which it should be evaluated.
         */
        public SampleStrategy(ComposerQCAlgorithm algorithm) : base(algorithm)
        {
            /**
             * This sets the interval by which the strategy is to be evaluated.
             *
             * You may use Daily, Weekly, Quarterly, Monthly, or Yearly strategies
             * by changing the date rule you assign here.
             */
            this.EvaluationDateRule = new DailyDateRule(algorithm);

            // Set the earliest your strategy can be backtested in Composer here.
            this.SetBacktestStartDate(2007, 4, 10);
        }

        /**
         * This is where you define your strategy. You may use tickers and indicator periods
         * you have specified above. The strategy is expected to return a list of PortfolioTarget
         * objects that the algorithm will invest in.
         */
        public override List<PortfolioTarget> Evaluate()
        {
            // The SymbolData dictionary is indexed by ticker, and provides access to all of your indicators.
            if (SymbolData["SPY"].CurrentPrice() > SymbolData["SPY"].MovAvgOfPrice(200))
            {
                /**
                 * Here we use the Filter() function on an array of tickers to select
                 * the top performer over a 30 day period.
                 *
                 * The Filter() function returns an array of tickers that match the
                 * criteria we indicated, which we assign to the "tickers" variable.
                 */
                var ticker = Filter(new[] { "SSO", "QLD" },
                                    FilterBy.CumulativeReturn, 30,
                                    Select.Top, 1);

                /**
                 * We only have one ticker filtered above, so we give it 100% weight.
                 * If we filtered more than one ticker above, we would equally weight them.
                 */
                return EqualWeight(ticker);
            }
            else
            {
                // Hold a static allocation of 60% VTI/40% BND.
                var targets = new List<PortfolioTarget>
                {
                    new PortfolioTarget("VTI", 0.6m), // Percentage target expressed as a decimal.
                    new PortfolioTarget("BND", 0.4m)  // In C#, the "m" indicates a decimal data type.
                };

                return targets;
            }
        }
    }
}