Overall Statistics |
Total Trades 1142 Average Win 0.26% Average Loss -0.04% Compounding Annual Return 12.234% Drawdown 2.800% Expectancy 0.766 Net Profit 20.091% Sharpe Ratio 1.323 Loss Rate 75% Win Rate 25% Profit-Loss Ratio 5.96 Alpha 0.107 Beta -0.083 Annual Standard Deviation 0.074 Annual Variance 0.005 Information Ratio -0.178 Tracking Error 0.132 Treynor Ratio -1.168 Total Fees $1224.54 |
using QuantConnect.Data.Fundamental; using QuantConnect.Data.UniverseSelection; using System.Collections.Generic; using System.Linq; namespace QuantConnect.Algorithm.CSharp { public sealed class FactorLongShortStrategy : QCAlgorithm { private const int NumCoarse = 250; private const int NumFine = 20; //actually this is num long positions (same number of shorts) private const decimal MaxLeverage = 2; //IB overnight private bool _rebalance = true; private bool _firstMonth = true; private Symbol[] _long; private Symbol[] _short; public override void Initialize() { SetStartDate(2016, 1, 1); SetEndDate(2017, 8, 1); SetCash(100000); ActualInitialization(); } private void ActualInitialization() { //SetBrokerageModel(Brokerages.BrokerageName.InteractiveBrokersBrokerage); var res = Resolution.Daily; var spy = AddSecurity(SecurityType.Equity, "SPY", res, true, MaxLeverage, false); Schedule.On(DateRules.MonthStart(spy.Symbol), TimeRules.AfterMarketOpen(spy.Symbol, 5), Rebalance); UniverseSettings.Leverage = MaxLeverage; UniverseSettings.Resolution = res; AddUniverse(CoarseSelection, FineSelection); } private IEnumerable<Symbol> CoarseSelection(IEnumerable<CoarseFundamental> coarse) { if (!_rebalance) return new List<Symbol>(); var universe = coarse .Where(x => x.HasFundamentalData) .Where(x => x.Price > 5) .OrderByDescending(x => x.DollarVolume) ; return universe.Select(x => x.Symbol).Take(NumCoarse); } private sealed class SymbolRanking { public readonly Symbol Symbol; public decimal Rank1, Rank2, Rank3; public SymbolRanking(Symbol s) { Symbol = s; } public decimal GetScore() { return Rank1 * 0.2m + Rank2 * 0.4m + Rank3 * 0.4m; } } private IEnumerable<Symbol> FineSelection(IEnumerable<FineFundamental> fine) { if (!_rebalance) return new List<Symbol>(); _rebalance = false; var filteredFine = fine .Where(x => x.OperationRatios.OperationMargin.Value != 0) .Where(x => x.ValuationRatios.PriceChange1M != 0) .Where(x => x.ValuationRatios.BookValuePerShare != 0) ; Log("Remained to select: " + filteredFine.Count()); var sortedByFactor1 = filteredFine.OrderByDescending( x => x.OperationRatios.OperationMargin.Value); var sortedByFactor2 = filteredFine.OrderByDescending( x => x.ValuationRatios.PriceChange1M); var sortedByFactor3 = filteredFine.OrderByDescending( x => x.ValuationRatios.BookValuePerShare); var rankings = new Dictionary<Symbol, SymbolRanking>(); foreach (var f in fine) rankings[f.Symbol] = new SymbolRanking(f.Symbol); int index = 0; foreach (var f in sortedByFactor1) rankings[f.Symbol].Rank1 = index++; index = 0; foreach (var f in sortedByFactor2) rankings[f.Symbol].Rank2 = index++; index = 0; foreach (var f in sortedByFactor3) rankings[f.Symbol].Rank3 = index++; var sortedRankings = rankings.Values.OrderBy(r => r.GetScore()); if (sortedRankings.Count() < NumFine * 2) { //TODO: this case should probably be handled with more than a warning... Log("Warning: Not enough symbols from fine filtered list"); } _long = sortedRankings .Take(NumFine).Select(x => x.Symbol).ToArray(); _short = sortedRankings.Reverse() .Take(NumFine).Select(x => x.Symbol).ToArray(); return _long.Union(_short); } private void Rebalance() { if (_firstMonth) { _firstMonth = false; return; } var longShort = _long.Union(_short).ToArray(); foreach (var security in Portfolio.Values) if (security.Invested && !longShort.Contains(security.Symbol)) Liquidate(security.Symbol); foreach (var symbol in _long) SetHoldings(symbol, 0.5 / NumFine); foreach (var symbol in _short) SetHoldings(symbol, -0.5 / NumFine); _rebalance = true; } } }