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