| Overall Statistics |
|
Total Trades 150 Average Win 1.67% Average Loss -2.00% Compounding Annual Return -27.100% Drawdown 27.100% Expectancy -0.218 Net Profit -26.973% Sharpe Ratio -1.023 Loss Rate 57% Win Rate 43% Profit-Loss Ratio 0.83 Alpha -0.368 Beta 0.769 Annual Standard Deviation 0.271 Annual Variance 0.073 Information Ratio -1.532 Tracking Error 0.258 Treynor Ratio -0.36 Total Fees $352.38 |
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
// Select stocka with:
// price > 10 dollars
// fast EMA above slow EMA
// EBITDAMargin.OneYear > 0.1 AND RevenueGrowth.OneYear > 0.1 AND DilutedContEPSGrowth.OneYear > 0.1 AND USA
// Order by DilutedContEPSGrowth
/// </summary>
public class FundamentalAndMaAlgorithm : QCAlgorithm
{
private readonly decimal minGrossMargin = 0.4m;
private readonly decimal minRevenueGrowth = 0.15m;
private readonly decimal minDilutedContEPSGrowth = 0.2m;
// minimal stock price in dollars
decimal minStockPrice = 10;
// EMA tolerance to prevent bouncing
const decimal Tolerance = 0.02m;
int numberOfStocks = 1;
static int daysInSlowEma = 50;
static int daysInFastEma = 10;
private readonly int takeByDollarVolume = 200;
// moving averages by stock symbol
private readonly ConcurrentDictionary<Symbol, MovingAverages> averages = new ConcurrentDictionary<Symbol, MovingAverages>();
List<Symbol> selectedByFundamentals = new List<Symbol>();
private class MovingAverages
{
public readonly ExponentialMovingAverage Fast;
public readonly ExponentialMovingAverage Slow;
public MovingAverages()
{
Fast = new ExponentialMovingAverage(daysInFastEma);
Slow = new ExponentialMovingAverage(daysInSlowEma);
}
// computes an object score of how much large the fast is than the slow
public decimal ScaledDelta
{
get { return (Fast - Slow) / ((Fast + Slow) / 2m); }
}
// updates the EMA50 and EMA100 indicators, returning true when they're both ready
public bool Update(DateTime time, decimal value)
{
return Fast.Update(time, value) && Slow.Update(time, value);
}
}
Symbol benchmark = QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA);
Symbol bonds = QuantConnect.Symbol.Create("TLT", SecurityType.Equity, Market.USA);
public override void Initialize()
{
UniverseSettings.Resolution = Resolution.Daily;
SetStartDate(2017, 4, 1);
SetEndDate(2018, 4, 1);
SetCash(50000);
AddUniverse(CoarseSelectionFunction, FineSelectionFunction);
AddEquity(bonds.Value, Resolution.Daily);
this.SetBenchmark(benchmark);
this.Log("orderby DilutedContEPSGrowth");
this.Log("minGrossMargin=" + minGrossMargin);
this.Log("minRevenueGrowth=" + minRevenueGrowth);
this.Log("minDilutedContEPSGrowth=" + minDilutedContEPSGrowth);
this.Log("minStockPrice=" + minStockPrice);
this.Log("Tolerance=" + Tolerance);
this.Log("numberOfStocks=" + numberOfStocks);
this.Log("daysInSlowEma=" + daysInSlowEma);
this.Log("daysInFastEma=" + daysInFastEma);
this.Log("takeByDollarVolume=" + takeByDollarVolume);
}
public IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
{
var selectedByEma = SelectByEma(coarse);
return selectedByEma.OrderByDescending(stock => stock.DollarVolume).Take(this.takeByDollarVolume).Select(stock => stock.Symbol);
}
int numberOfBonds = 0;
public IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine)
{
this.numberOfBonds = 0;
this.selectedByFundamentals.Clear();
this.selectedByFundamentals.AddRange(SelectByFundamentals(fine).Select(stock => stock.Symbol));
if (selectedByFundamentals.Count() < this.numberOfStocks)
{
// nothing to invest in, invest in bonds
this.numberOfBonds = this.numberOfStocks - selectedByFundamentals.Count();
selectedByFundamentals.Add(this.bonds);
}
return selectedByFundamentals;
}
private IEnumerable<FineFundamental> SelectByFundamentals(IEnumerable<FineFundamental> selectedByMa)
{
return (from stock in selectedByMa
where stock.CompanyReference.CountryId == "USA"
where !stock.CompanyReference.IsLimitedPartnership
where !stock.CompanyReference.IsREIT
where stock.OperationRatios.GrossMargin.OneYear > this.minGrossMargin
where stock.OperationRatios.RevenueGrowth.OneYear > this.minRevenueGrowth
where stock.EarningRatios.DilutedContEPSGrowth.OneYear > this.minDilutedContEPSGrowth
where stock.OperationRatios.GrossMargin5YrAvg > this.minGrossMargin
where stock.OperationRatios.GrossProfitAnnual5YrGrowth > this.minDilutedContEPSGrowth
orderby stock.EarningRatios.DilutedContEPSGrowth.OneYear descending
select stock).Take(this.numberOfStocks);
}
// select stocks with fundamental data and price > 10 dollars
// fast EMA is above slow EMA
private List<CoarseFundamental> SelectByEma(IEnumerable<CoarseFundamental> coarse)
{
List<CoarseFundamental> selected = new List<CoarseFundamental>();
// select stocks with fast EMA above slow EMA
foreach (CoarseFundamental stock in coarse)
{
if (stock.Price > this.minStockPrice)
{
if (stock.HasFundamentalData)
{
// create or update averages for the stock
MovingAverages average = null;
if (this.averages.ContainsKey(stock.Symbol))
{
this.averages.TryGetValue(stock.Symbol, out average);
}
else
{
average = new MovingAverages();
this.averages.TryAdd(stock.Symbol, average);
}
// if indicators are ready
if (average.Update(stock.EndTime, stock.Price))
{
// if fast EMA is above slow EMA
if (stock.Price > average.Slow * (1 + Tolerance))
{
selected.Add(stock);
}
}
}
}
}
return selected;
}
public void OnData(TradeBars data)
{
LiquidateNotSelected();
InvestSelected();
}
private void InvestSelected()
{
// invest in securities that are in lists
if (this.Portfolio.Cash > 0m)
{
if (this.selectedByFundamentals.Count() > 0)
{
foreach (Symbol security in this.selectedByFundamentals)
{
// do not trade benchmark and bonds here
if (!security.Equals(this.benchmark) && !security.Equals(this.bonds))
{
if (this.Portfolio.Securities[security].IsTradable)
{
decimal holdings = 1m / this.numberOfStocks;
this.Log("Buy:" + security.Value + " " + holdings);
this.SetHoldings(security, holdings);
}
}
}
if (this.numberOfBonds > 0)
{
decimal holdings = (decimal)numberOfBonds / this.numberOfStocks;
this.Log("Buy bonds:" + this.bonds.Value + " " + holdings);
// this.SetHoldings(this.bonds, holdings);
}
}
}
}
private void LiquidateNotSelected()
{
// liquidate securities not in the list
IEnumerable<Symbol> toLiquidate = this.Portfolio.Securities.Keys.Except(this.selectedByFundamentals);
foreach (Symbol security in toLiquidate)
{
// do not trade benchmark
if (!security.Equals(this.benchmark))
{
this.Log("Liquidate:" + security.Value);
Liquidate(security);
}
}
}
}
}