| Overall Statistics |
|
Total Trades 1277 Average Win 0.90% Average Loss -0.67% Compounding Annual Return 262.335% Drawdown 26.900% Expectancy 0.192 Net Profit 114.257% Sharpe Ratio 3.321 Probabilistic Sharpe Ratio 79.718% Loss Rate 49% Win Rate 51% Profit-Loss Ratio 1.34 Alpha 1.825 Beta 0.268 Annual Standard Deviation 0.554 Annual Variance 0.307 Information Ratio 3.189 Tracking Error 0.559 Treynor Ratio 6.866 Total Fees $4406.12 Estimated Strategy Capacity $9800000.00 Lowest Capacity Asset NTRB XS9YHLR0G02T |
//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.Market;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Algorithm.CSharp
{
public class Flock : QCAlgorithm
{
List<StockData> HighDollarVolumeStocks = new List<StockData>();
int TotalHighDollarVolumeStocks = 50;
int TotalStocksToHold = 5;
//Resolution Resolution = Resolution.Daily;
int Period = 5;
//20220130 - idk - so we have 1% leverage? or does this mean we use 99% of the available funds in the portfolio?
decimal Leverage = 0.99m;
decimal Threshold = 0.0m;
public DateTime LastTime = new DateTime();
public override void Initialize()
{
UniverseSettings.Resolution = Resolution.Minute;
SetStartDate(2021, 06, 27);
SetCash(100000);
AddUniverse(coarse =>
{
return (from stock in coarse
where stock.Symbol.ToString().Substring(0, 3) != "GME"
where stock.Symbol.ToString().Substring(0, 3) != "AMC"
where stock.Symbol.ToString().Substring(0, 4) != "UVXY"
//where stock.HasFundamentalData == true
orderby stock.DollarVolume descending
select stock.Symbol).Take(TotalHighDollarVolumeStocks);
});
}
//this sub is never called in the Algo - probably runs at the end of every day though
//https://www.quantconnect.com/forum/discussion/315/api-update-onendofday-string-symbol/p1
public override void OnEndOfDay()
{
foreach (StockData stockData in HighDollarVolumeStocks)
{
//if count of minute candles modulus 390 min == 0 then (do this every 6.5hrs, the stock exchange is open 6.5hrs a day)
// group the candles to a daily candle
if (stockData.MinuteCandles.Count % 390 == 0)
{
stockData.DailyCandles = Group(stockData.MinuteCandles, 390);
}
}
}
public void OnData(TradeBars data)
{
//loops through HighDollarVolumeStocks
foreach (StockData stockData in HighDollarVolumeStocks)
{
//checks if data contains the key stockData.Symbol
if (data.ContainsKey(stockData.Symbol))
{
TradeBar bar = data[stockData.Symbol];
stockData.MinuteCandles.Add(bar);
//20220130 - idk - Period is one of the input Parameters
// - so every 5th day it is removing the the stocks MinuteCandles?
if (stockData.MinuteCandles.Count > Period * 390)
{
stockData.MinuteCandles.RemoveAt(0);
}
}
}
//20220130 - idk - strange way to end the Algo - this is checkng if Day is 'Now'
if (Time.Day == LastTime.Day)
{
return;
}
//20220130 - so we not at today's day so continue
LastTime = Time;
//20220130 - idk - loop through each HighDollarVolumeStocks StockData called stockData1 (we do this everyminute?)
foreach (StockData stockData1 in HighDollarVolumeStocks)
{
//20220130 - list of the DailyCandles for a stock
List<TradeBar> candles1 = stockData1.DailyCandles;
//20220130 - idk - an arry of the close prices - what is (x => x.Close) doing? Maybe this is an
// array of all the closes in all the candles for this stock
decimal[] prices1 = candles1.Select(x => x.Close).ToArray();
decimal averagePrice1 = prices1.Average();
//Get the normalizes price
decimal[] normalizedPrices1 = prices1.Select(x => x / averagePrice1).ToArray();
decimal sumRatios = 0;
//20220130 - idk - looping through the HighDollarVolumeStocks again (a loop within a loop)
// This is actually comparing the current stockData1 to all the other stocks in HighDollarVolumeStocks
foreach (StockData stockData2 in HighDollarVolumeStocks)
{
if (stockData1 != stockData2)
{
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();
//20220130 - idk - making an array with the difference between this stock and all other stocks
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)
{
//20220130 - idk - setting ratio - how would differenceRange == 0 if we comparing differenct stocks
decimal ratio = currentDifference / differenceRange;
sumRatios += ratio;
}
}
}
stockData1.AverageRatio = sumRatios / (HighDollarVolumeStocks.Count - 1);
}
//20220130 - idk -getting list of stocks ordered by AverageRatio - desc order of how different the stock is to the group
List<StockData> stocksToHold = HighDollarVolumeStocks.OrderByDescending(x => Math.Abs(x.AverageRatio)).Take(TotalStocksToHold).ToList();
//20220130 - idk - is the stock is not in the sorted list remove it from the universe
foreach (var security in Portfolio.Values)
{
if (Portfolio[security.Symbol].Invested)
{
if (stocksToHold.Exists(x => x.Symbol == security.Symbol) == false)
{
Liquidate(security.Symbol);
}
}
}
//20220130 - idk - if the stockData show is lower then the group buy it
// and if the stocData is higher then the group sell it
//20220130 - idk - how is TotalStocksToHold being used to limit it to TotalStocksToHold
//20220130 - idk - shouldn't this be done once a day?
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 List<TradeBar> MinuteCandles = new List<TradeBar>();
public List<TradeBar> DailyCandles = new List<TradeBar>();
public decimal AverageRatio;
}
//20220130 - idk - when we remove a security remove it from HighDollarVolumeStocks
// when we add a security add it and all its lists to StockData and HighDollarVolumeStocks
public override void OnSecuritiesChanged(SecurityChanges changes)
{
foreach (var security in changes.RemovedSecurities)
{
StockData stockData = HighDollarVolumeStocks.Find(x => x.Symbol == security.Symbol);
if (stockData != null)
{
HighDollarVolumeStocks.Remove(stockData);
}
}
foreach (var security in changes.AddedSecurities)
{
StockData stockData = new StockData();
stockData.Symbol = security.Symbol;
stockData.MinuteCandles = History(stockData.Symbol, Period * 390, Resolution.Minute).ToList();
stockData.DailyCandles = History(stockData.Symbol, Period, Resolution.Daily).ToList();
HighDollarVolumeStocks.Add(stockData);
}
}
//20220130 - idk - this is the list function used
public static List<TradeBar> Group(List<TradeBar> candles, int binWidth)
{
List<TradeBar> outCandles = new List<TradeBar>();
for (int start = 0; start < candles.Count; start += binWidth)
{
List<TradeBar> group = candles.GetRange(start, binWidth);
TradeBar newCandle = new TradeBar();
newCandle.Time = group.First().Time;
newCandle.Open = group.First().Open;
newCandle.Close = group.Last().Close;
newCandle.High = group.Select(x => x.High).Max();
newCandle.Low = group.Select(x => x.Low).Min();
newCandle.Volume = group.Select(x => x.Volume).Sum();
outCandles.Add(newCandle);
}
return outCandles;
}
}
}