| Overall Statistics |
|
Total Trades 1358 Average Win 0.32% Average Loss -0.05% Compounding Annual Return 7.660% Drawdown 11.000% Expectancy 0.351 Net Profit 18.058% Sharpe Ratio 0.853 Loss Rate 83% Win Rate 17% Profit-Loss Ratio 7.06 Alpha 0.085 Beta -0.292 Annual Standard Deviation 0.074 Annual Variance 0.005 Information Ratio -0.066 Tracking Error 0.173 Treynor Ratio -0.216 Total Fees $2073.73 |
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(2015, 1, 1);
SetEndDate(2017, 4, 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 int Rank1, Rank2, Rank3;
public SymbolRanking(Symbol s)
{
Symbol = s;
}
public double GetScore()
{
return Rank1 * 0.2 + Rank2 * 0.4 + Rank3 * 0.4;
}
}
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.Reverse()
.Take(NumFine).Select(x => x.Symbol).ToArray();
_short = sortedRankings
.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 _long)
SetHoldings(symbol, -0.5 / NumFine);
_rebalance = true;
}
}
}