| Overall Statistics |
|
Total Trades 145 Average Win 8.60% Average Loss -3.89% Compounding Annual Return 300.415% Drawdown 49.200% Expectancy 0.440 Net Profit 181.900% Sharpe Ratio 3.281 Probabilistic Sharpe Ratio 68.336% Loss Rate 55% Win Rate 45% Profit-Loss Ratio 2.21 Alpha 3.255 Beta -0.3 Annual Standard Deviation 1.002 Annual Variance 1.005 Information Ratio 3.282 Tracking Error 1.036 Treynor Ratio -10.975 Total Fees $324.29 Estimated Strategy Capacity $1100000.00 Lowest Capacity Asset TYH U8JOSZGR4OKL |
// 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,
}
}
#region imports
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Drawing;
using QuantConnect;
using QuantConnect.Algorithm.Framework;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Risk;
using QuantConnect.Parameters;
using QuantConnect.Benchmarks;
using QuantConnect.Brokerages;
using QuantConnect.Util;
using QuantConnect.Interfaces;
using QuantConnect.Algorithm;
using QuantConnect.Indicators;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Custom;
using QuantConnect.DataSource;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Notifications;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using QuantConnect.Securities.Equity;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.Option;
using QuantConnect.Securities.Forex;
using QuantConnect.Securities.Crypto;
using QuantConnect.Securities.Interfaces;
using QuantConnect.Storage;
using QuantConnect.Data.Custom.AlphaStreams;
using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion
namespace QuantConnect.Algorithm.CSharp
{
using ComposerQC;
using ComposerQC.Model;
public class CreativeFluorescentPinkWhale : ComposerQCAlgorithm
{
public override IStrategy SetupStrategy() => new MinimumBBStrategy(this);
}
}#region imports
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Drawing;
using QuantConnect;
using QuantConnect.Algorithm.Framework;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Risk;
using QuantConnect.Parameters;
using QuantConnect.Benchmarks;
using QuantConnect.Brokerages;
using QuantConnect.Util;
using QuantConnect.Interfaces;
using QuantConnect.Algorithm;
using QuantConnect.Indicators;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Custom;
using QuantConnect.DataSource;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Notifications;
using QuantConnect.Orders;
using QuantConnect.Orders.Fees;
using QuantConnect.Orders.Fills;
using QuantConnect.Orders.Slippage;
using QuantConnect.Scheduling;
using QuantConnect.Securities;
using QuantConnect.Securities.Equity;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.Option;
using QuantConnect.Securities.Forex;
using QuantConnect.Securities.Crypto;
using QuantConnect.Securities.Interfaces;
using QuantConnect.Storage;
using QuantConnect.Data.Custom.AlphaStreams;
using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion
namespace QuantConnect
{
using ComposerQC;
using ComposerQC.DateRules;
public class MinimumBBStrategy : CalendarStrategy
{
public override IEnumerable<int> Periods => new[] { 5, 6, 7, 10, 13 };
public override IEnumerable<string> Tickers => new string[]
{
"BIL", "IEF", "SPY", "UVXY", "VIXY", "SOXL", "SHY", "HIBL", "SOXS", "SQQQ", "TECL"
};
public MinimumBBStrategy(ComposerQCAlgorithm algorithm) : base(algorithm)
{
this.EvaluationDateRule = new DailyDateRule(algorithm);
this.SetBacktestStartDate(2022, 2, 11);
}
public override List<PortfolioTarget> Evaluate()
{
if (SymbolData["BIL"].RSI(5) < SymbolData["IEF"].RSI(7))
{
if (SymbolData["SPY"].RSI(6) > 75m)
{
var ticker = Filter(new[] { "UVXY", "VIXY" },
FilterBy.RSI, 13,
Select.Bottom, 1);
return EqualWeight(ticker);
}
else
{
return EqualWeight(new[] { "SOXL" });
}
}
else
{
// Extremely oversold S&P (low RSI). Double check with bond mkt before going long
if (SymbolData["SHY"].RSI(10) < SymbolData["HIBL"].RSI(10))
{
var ticker = Filter(new[] { "SOXS", "SQQQ" },
FilterBy.RSI, 7,
Select.Bottom, 1);
return EqualWeight(ticker);
}
else
{
var ticker = Filter(new[] { "SOXL", "TECL" },
FilterBy.RSI, 7,
Select.Bottom, 1);
return EqualWeight(ticker);
}
}
}
}
}