| Overall Statistics |
|
Total Trades 996 Average Win 0.40% Average Loss -0.44% Compounding Annual Return 11.138% Drawdown 16.700% Expectancy 0.478 Net Profit 187.754% Sharpe Ratio 0.88 Probabilistic Sharpe Ratio 28.989% Loss Rate 22% Win Rate 78% Profit-Loss Ratio 0.89 Alpha 0.103 Beta -0.045 Annual Standard Deviation 0.111 Annual Variance 0.012 Information Ratio -0.142 Tracking Error 0.197 Treynor Ratio -2.145 Total Fees $1150.26 |
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Data;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using System;
using System.Collections.Generic;
using System.Linq;
namespace QuantConnect.Algorithm.CSharp
{
public class MagicFormula : QCAlgorithm
{
private DateTime _lastRebalance;
private bool _rebalance = true;
private bool _isLiquidating = false;
private DateTime _liquidationDate;
private DateTime _purchaseDate;
public override void Initialize()
{
SetStartDate(2011, 1, 1);
SetEndDate(2021, 1, 1);
SetCash(100000);
SetExecution(new ImmediateExecutionModel());
SetAlpha(new ConstantAlphaModel(InsightType.Price, InsightDirection.Up, TimeSpan.FromDays(365)));
UniverseSettings.Resolution = Resolution.Daily;
SetPortfolioConstruction(new ConstantMaximumEqualWeightPortfolio());
AddUniverseSelection(new FineFundamentalUniverseSelectionModel(SelectCoarse, SelectFine));
_liquidationDate = new DateTime(Time.Year, 12, 10);
_purchaseDate = new DateTime(Time.Year + 1, 1, 10);
Schedule.On(DateRules.EveryDay(), TimeRules.At(3, 30, 1), Update);
}
public IEnumerable<Symbol> SelectCoarse(IEnumerable<CoarseFundamental> coarse)
{
if (_isLiquidating)
return Enumerable.Empty<Symbol>();
if (!_rebalance)
return Universe.Unchanged;
return from x in coarse
where x.HasFundamentalData
where x.Price > 5
where x.DollarVolume > 300000
select x.Symbol;
}
public IEnumerable<Symbol> SelectFine(IEnumerable<FineFundamental> fine)
{
if (_isLiquidating)
return Enumerable.Empty<Symbol>();
if (!_rebalance)
return Universe.Unchanged;
var candidates = from x in fine
let ebit = x.FinancialStatements.IncomeStatement.EBIT
let workingCapital = x.FinancialStatements.BalanceSheet.WorkingCapital
let netAssets = x.FinancialStatements.BalanceSheet.NetTangibleAssets
where workingCapital != 0 || netAssets != 0
let roc = ebit / (workingCapital + netAssets)
where x.OperationRatios.ROIC.OneYear > 0.25m
//where roc > 2.0m
where x.CompanyProfile.HeadquarterCountry == "USA"
where x.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.Utilities
&& x.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.FinancialServices
where x.MarketCap > 0 && x.FinancialStatements.BalanceSheet.TotalDebt != 0
//let earningsYield = ebit /
//(x.MarketCap + x.FinancialStatements.BalanceSheet.TotalDebt -
//x.FinancialStatements.BalanceSheet.CashAndCashEquivalents)
let earningsYield = x.ValuationRatios.EarningYield
where earningsYield > 0.05m
orderby earningsYield
select x;
_rebalance = false;
_lastRebalance = Time;
var newSymbols = from x in candidates.Take(10)
where !ActiveSecurities.ContainsKey(x.Symbol)
select x.Symbol;
var previousSecurities = ActiveSecurities.Values.ToList();
var allSymbols = (from x in previousSecurities
select x.Symbol).ToList();
allSymbols.AddRange(newSymbols);
return allSymbols;
}
private bool ShouldLiquidate()
{
return (Time > _liquidationDate && !_isLiquidating);
}
private void ReinvestOnNewYear()
{
_isLiquidating = false;
_rebalance = true;
_liquidationDate = new DateTime(Time.Year, _liquidationDate.Month, _liquidationDate.Day);
_purchaseDate = new DateTime(Time.Year + 1, _purchaseDate.Month, _purchaseDate.Day);
Debug("Starting investing for the year: " + Time.Year);
}
public void Update()
{
if (ShouldLiquidate())
{
Debug("Liquidating For End Of Year: " + Time);
_isLiquidating = true;
Schedule.On(DateRules.On(_purchaseDate), TimeRules.At(11, 0), () =>
{
ReinvestOnNewYear();
});
return;
}
if (Time > _lastRebalance.AddMonths(3) && !_isLiquidating)
{
Debug(Time + ": Adding more stocks!");
_rebalance = true;
return;
}
}
public class ConstantMaximumEqualWeightPortfolio : PortfolioConstructionModel
{
private int _maximumPositions;
public ConstantMaximumEqualWeightPortfolio(int maximumPositions = 30) : base(Time => null)
{
_maximumPositions = maximumPositions;
}
protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
{
var result = new Dictionary<Insight, double>();
var percentPerPosition = 1m / _maximumPositions;
foreach (var insight in activeInsights)
{
result[insight] =
(double)((int)(insight.Direction) * percentPerPosition);
}
return result;
}
protected override bool ShouldCreateTargetForInsight(Insight insight)
{
return !Algorithm.Portfolio[insight.Symbol].Invested;
}
}
}
}