Overall Statistics |
Total Trades 303 Average Win 1.42% Average Loss -0.12% Compounding Annual Return 25.927% Drawdown 5.900% Expectancy 2.161 Net Profit 44.147% Sharpe Ratio 1.552 Loss Rate 75% Win Rate 25% Profit-Loss Ratio 11.82 Alpha 0.196 Beta 0.006 Annual Standard Deviation 0.127 Annual Variance 0.016 Information Ratio 0.469 Tracking Error 0.162 Treynor Ratio 31.344 Total Fees $7914.27 |
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 = 5; //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(1000000); 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; } } }