| Overall Statistics |
|
Total Trades 5332 Average Win 1.53% Average Loss -1.20% Compounding Annual Return 329.969% Drawdown 61.100% Expectancy 0.127 Net Profit 2602.799% Sharpe Ratio 3.463 Probabilistic Sharpe Ratio 83.536% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 1.27 Alpha 3.228 Beta 0.437 Annual Standard Deviation 0.957 Annual Variance 0.916 Information Ratio 3.25 Tracking Error 0.96 Treynor Ratio 7.577 Total Fees $341253.48 Estimated Strategy Capacity $3500000.00 Lowest Capacity Asset LLY R735QTJ8XC9X |
//Copyright HardingSoftware.com. Granted to the public domain.
//Use entirely at your own risk.
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Securities;
namespace QuantConnect.Algorithm.CSharp
{
public class Flock : QCAlgorithm
{
List<StockData> HighDollarVolumeStocks = new List<StockData>();
int TotalHighDollarVolumeStocks = 250;
int TotalStocksToHold = 5;
Resolution Resolution = Resolution.Daily;
int Period = 5;
decimal Leverage = 0.99m;
decimal Threshold = 0.0m;
public override void Initialize()
{
UniverseSettings.Resolution = Resolution.Minute;
SetStartDate(2019, 9, 27);
SetCash(100000);
AddUniverse(coarse =>
{
return (from stock in coarse
where stock.Symbol.Value.Substring(0, stock.Symbol.Value.Length) != "GME"
where stock.Symbol.Value.Substring(0, stock.Symbol.Value.Length) != "AMC"
where stock.Symbol.Value.Substring(0, stock.Symbol.Value.Length) != "UVXY"
//where stock.HasFundamentalData == false
orderby stock.DollarVolume descending
select stock.Symbol).Take(TotalHighDollarVolumeStocks);
});
}
public void OnConsolidatedDaily(TradeBar bar)
{
StockData stockData = HighDollarVolumeStocks.Find(x => x.Symbol == bar.Symbol);
stockData.DailyCandles.Add(bar);
if (stockData.DailyCandles.Count > Period)
{
stockData.DailyCandles.RemoveAt(0);
}
}
public void OnData(TradeBars data)
{
if (this.Time.Hour == 9 && this.Time.Minute == 32)
Rebalance(data);
}
public void Rebalance(TradeBars data)
{
foreach (StockData stockData1 in HighDollarVolumeStocks)
{
if (stockData1.DailyCandles.Count == 0)
continue;
List<TradeBar> candles1 = stockData1.DailyCandles;
decimal[] prices1 = candles1.Select(x => x.Close).ToArray();
decimal averagePrice1 = prices1.Average();
decimal[] normalizedPrices1 = prices1.Select(x => x / averagePrice1).ToArray();
decimal sumRatios = 0;
foreach (StockData stockData2 in HighDollarVolumeStocks)
{
if (stockData1 != stockData2 && stockData2.DailyCandles.Count > 0)
{
List<TradeBar> candles2 = stockData2.DailyCandles;
decimal[] prices2 = candles2.Select(x => x.Close).ToArray();
decimal averagePrice2 = prices2.Average();
decimal[] normalizedPrices2 = prices2.Select(x => x / averagePrice2).ToArray();
decimal[] differences = normalizedPrices1.Zip(normalizedPrices2, (x, y) => x - y).ToArray();
decimal maxDifference = differences.Max();
decimal minDifference = differences.Min();
decimal differenceRange = maxDifference - minDifference;
decimal currentDifference = normalizedPrices1.Last() - normalizedPrices2.Last();
if (differenceRange != 0)
{
decimal ratio = currentDifference / differenceRange;
sumRatios += ratio;
}
}
}
stockData1.AverageRatio = sumRatios / (HighDollarVolumeStocks.Count - 1);
}
// We only want to get tradeable stocks
List<StockData> tradeableStocks = HighDollarVolumeStocks.Where(x => x.Security.IsTradable && data.ContainsKey(x.Symbol)).ToList();
List<StockData> stocksToHold = tradeableStocks.OrderByDescending(x => Math.Abs(x.AverageRatio)).Take(TotalStocksToHold).ToList();
foreach (var security in Portfolio.Values)
{
if (Portfolio[security.Symbol].Invested)
{
if (stocksToHold.Exists(x => x.Symbol == security.Symbol) == false)
{
Liquidate(security.Symbol);
}
}
}
foreach (StockData stockData in stocksToHold)
{
if (stockData.AverageRatio < -Threshold && Portfolio[stockData.Symbol].Quantity <= 0)
{
SetHoldings(stockData.Symbol, Leverage / (decimal)TotalStocksToHold);
}
else if (stockData.AverageRatio > Threshold && Portfolio[stockData.Symbol].Quantity >= 0)
{
SetHoldings(stockData.Symbol, -Leverage / (decimal)TotalStocksToHold);
}
}
}
public class StockData
{
public Symbol Symbol;
public Security Security;
public List<TradeBar> DailyCandles = new List<TradeBar>();
public List<TradeBar> MinuteCandles = new List<TradeBar>();
public decimal AverageRatio;
public QCAlgorithm _algo;
public IDataConsolidator Consolidator1d;
public StockData(Symbol symbol, QCAlgorithm algo)
{
_algo = algo;
Symbol = symbol;
Consolidator1d = algo.Consolidate(symbol, Resolution.Daily, ((Flock)algo).OnConsolidatedDaily);
}
}
public override void OnSecuritiesChanged(SecurityChanges changes)
{
foreach (var security in changes.RemovedSecurities)
{
StockData stockData = HighDollarVolumeStocks.Find(x => x.Symbol == security.Symbol);
if (stockData != null)
{
SubscriptionManager.RemoveConsolidator(stockData.Symbol, stockData.Consolidator1d);
HighDollarVolumeStocks.Remove(stockData);
}
}
foreach (var security in changes.AddedSecurities)
{
StockData stockData = new StockData(security.Symbol, this);
stockData.Symbol = security.Symbol;
stockData.Security = security;
// Get daily data for calculations
stockData.DailyCandles = History(stockData.Symbol, Period, Resolution).ToList();
// Warmup Minute data so we don't get errors
stockData.MinuteCandles = History(stockData.Symbol, 390, Resolution.Minute).ToList();
HighDollarVolumeStocks.Add(stockData);
}
}
}
}