| Overall Statistics |
|
Total Trades 1058 Average Win 1.16% Average Loss -0.97% Compounding Annual Return 18.516% Drawdown 32.600% Expectancy 0.424 Net Profit 761.503% Sharpe Ratio 0.69 Loss Rate 35% Win Rate 65% Profit-Loss Ratio 1.19 Alpha 0.142 Beta 0.547 Annual Standard Deviation 0.27 Annual Variance 0.073 Information Ratio 0.397 Tracking Error 0.264 Treynor Ratio 0.34 Total Fees $1092.61 |
namespace QuantConnect.Algorithm.CSharp
{
public class FundamentalFactor : QCAlgorithm
{
#region Private Fields
private const int _timeToWaitAfterMarketOpens = 98;
private readonly int _formationDays = 200;
private readonly decimal _fullHoldings = 0.8m;
private readonly bool _lowMomentum = false;
private readonly int _numberOfStocks = 10;
private readonly int _numOfScreener = 100;
private readonly int _periods = 1;
private readonly int _spyHistoryToConsider = 125;
private readonly decimal _startingCash = 10000m;
private readonly int _waitForAfternoon = 300;
private readonly string bondString = "TLT";
private readonly string spyTicker = "SPY";
private bool firstMonthTradeFlg = true;
private bool panicFlg = false;
private bool rebalanceFlg = false;
private decimal spyRatio = 0;
private QuantConnect.Symbol spySymbol, tltSymbol;
private IEnumerable<Symbol> symbols;
private bool tradeFlag = true;
#endregion Private Fields
#region Public Methods
/// <summary>
/// Coarse the selection function.
/// </summary>
/// <param name="coarse">The coarse.</param>
/// <returns></returns>
public IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
{
if (!rebalanceFlg && !firstMonthTradeFlg)
{
return symbols;
}
var stocks = (from c in coarse
where c.Price > 10 &&
c.HasFundamentalData
orderby c.DollarVolume descending
select c.Symbol).Take(_numOfScreener * 2);
return stocks;
}
/// <summary>
/// Fines the selection function.
/// </summary>
/// <param name="fine">The fine.</param>
/// <returns></returns>
public IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine)
{
if (!rebalanceFlg && !firstMonthTradeFlg)
{
return symbols;
}
try
{
var s = from f in fine
where f.SecurityReference.SecurityType == "ST00000001"
&& f.SecurityReference.IsPrimaryShare
&& f.ValuationRatios.EVToEBITDA > 0
&& f.EarningReports.BasicAverageShares.ThreeMonths > 0
select f;
var s1 = s.Where(f =>
{
var averageShares = f.EarningReports.BasicAverageShares.ThreeMonths;
var history = History(f.Symbol, _periods, Resolution.Daily);
var close = history.FirstOrDefault()?.Close;
return close == null ? false :
averageShares * close > 2 * 1000000000;
}).Select(a => a.Symbol).Take(_numOfScreener).ToList();
var tmpSymbol = AddEquity(spyTicker, Resolution.Daily).Symbol;
s1.Add(tmpSymbol);
tmpSymbol = AddEquity(bondString, Resolution.Daily).Symbol;
s1.Add(tmpSymbol);
rebalanceFlg = false;
firstMonthTradeFlg = false;
tradeFlag = true;
symbols = s1;
return s1;
}
catch (Exception ex)
{
Log($"Exception occurred on {GetReportingDate()}; exception details:");
Log(ex.Message);
symbols = null;
return null;
}
}
/// <summary>
/// Initialize the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
/// </summary>
/// <seealso cref="M:QuantConnect.Algorithm.QCAlgorithm.SetStartDate(System.DateTime)" />
/// <seealso cref="M:QuantConnect.Algorithm.QCAlgorithm.SetEndDate(System.DateTime)" />
/// <seealso cref="M:QuantConnect.Algorithm.QCAlgorithm.SetCash(System.Decimal)" />
public override void Initialize()
{
SetStartDate(2007, 2, 2);
SetEndDate(DateTime.Now);
SetCash(_startingCash);
//UniverseSettings.Leverage = 1.5m;
UniverseSettings.Resolution = Resolution.Daily;
AddUniverse(CoarseSelectionFunction, FineSelectionFunction);
spySymbol = AddEquity(spyTicker, Resolution.Daily).Symbol;
tltSymbol = AddEquity(bondString, Resolution.Daily).Symbol;
Schedule.On(DateRules.MonthStart(), TimeRules.AfterMarketOpen(spySymbol, _timeToWaitAfterMarketOpens), WorkOnPortfolio);
Schedule.On(DateRules.MonthEnd(), TimeRules.AfterMarketOpen(spySymbol, _waitForAfternoon), GetReadyForMonthStart);
}
/// <summary>
/// Gets the ready for month start.
/// </summary>
private void GetReadyForMonthStart()
{
rebalanceFlg = true;
}
/// <summary>
/// Event - v3.0 DATA EVENT HANDLER: (Pattern) Basic template for user to override for receiving all subscription data in a single event
/// </summary>
/// <param name="slice">The current slice of data keyed by symbol string</param>
/// <code>
/// TradeBars bars = slice.Bars;
/// Ticks ticks = slice.Ticks;
/// TradeBar spy = slice["SPY"];
/// List{Tick} aaplTicks = slice["AAPL"]
/// Quandl oil = slice["OIL"]
/// dynamic anySymbol = slice[symbol];
/// DataDictionary{Quandl} allQuandlData = slice.Get{Quand}
/// Quandl oil = slice.Get{Quandl}("OIL")
/// </code>
public override void OnData(Slice slice)
{
if (spyRatio == 0)
{
if (Portfolio[spySymbol].Price != 0)
{
spyRatio = _startingCash / Portfolio[spySymbol].Price;
}
}
Plot("Performance", "SPY", Portfolio[spySymbol].Price * spyRatio);
Plot("Performance", "Portfolio", Portfolio.TotalPortfolioValue);
}
/// <summary>
/// Works the on portfolio.
/// </summary>
public void WorkOnPortfolio()
{
//if (!tradeFlag || !firstMonthTradeFlg)
var datePrintStr = GetReportingDate();
if (symbols == null || !symbols.Any())
{
Log($"As of {datePrintStr} Symbols not yet ready!");
}
TestIfMarketIsGoingDown();
if (panicFlg || !tradeFlag)
{
Log($"Got signal not to rebalance: Panic:{panicFlg}; Trade flag:{tradeFlag}.");
return;
}
Log($"Rebalance started at {Time}");
var investmentLst = GetSecuritiesToInvest(symbols);
var weight = _fullHoldings / investmentLst.Count();
if (Portfolio.ContainsKey(tltSymbol))
{
var tltQty = Portfolio[tltSymbol].Quantity;
if (tltQty != 0)
{
Liquidate(tltSymbol, " Coming out of TLT");
}
}
foreach (var symbol in symbols)
{
var checkIfCurrentDarling = investmentLst.Where(x => x.Key == symbol).FirstOrDefault().Key;
if (checkIfCurrentDarling == null)
{
SetHoldings(symbol, 0);
Liquidate(symbol, $" Coming out of {symbol.Value}");
}
else
{
AddEquity(symbol.Value, Resolution.Daily);
SetHoldings(symbol, weight);
}
}
}
#endregion Public Methods
#region Private Methods
/// <summary>
/// Calculates the return.
/// </summary>
/// <param name="symbols">The symbols.</param>
/// <returns></returns>
private IEnumerable<KeyValuePair<Symbol, decimal>> GetSecuritiesToInvest(IEnumerable<Symbol> symbols)
{
//var priceHisotry = new Dictionary<Symbol, List<decimal>>();
var lastPriceRatio = new Dictionary<Symbol, decimal>();
var notInterestedSymbols = new List<Symbol>
{
spySymbol,
tltSymbol
};
symbols = symbols.Except(notInterestedSymbols);
foreach (var symbol in symbols.Where(x => x.SecurityType == SecurityType.Equity))
{
var historicalValues = History(symbol, _formationDays, Resolution.Daily)
.Select(x => x.Close).ToList();
//get current price.
var currentValue = History(symbol, _periods, Resolution.Minute).FirstOrDefault()?.Close;
if (currentValue != null)
{
historicalValues.Add((decimal)currentValue);
}
if (historicalValues.Count > _formationDays / 2)
{
lastPriceRatio.Add(symbol, ((historicalValues[historicalValues.Count - 1] - historicalValues.First()) / historicalValues.First()));
}
}
IEnumerable<KeyValuePair<Symbol, decimal>> returnValue
= from a in lastPriceRatio
orderby a.Value
select a;
returnValue = _lowMomentum ?
(from a in lastPriceRatio
orderby a.Value
select a).OrderBy(x => x.Value).Take(_numberOfStocks)
: (from a in lastPriceRatio
orderby a.Value
select a).OrderByDescending(x => x.Value).Take(_numberOfStocks);
return returnValue;
}
private string GetReportingDate()
{
return Time.ToString("MM/dd/yyyy", CultureInfo.InvariantCulture);
}
/// <summary>
/// Tests if market is going down.
/// </summary>
private void TestIfMarketIsGoingDown()
{
var spySMA = History(spySymbol, _spyHistoryToConsider, Resolution.Daily)
.Select(x => x.Close)
.Average();
if (Portfolio[spySymbol].Price > spySMA)
{
if (panicFlg)
{
Log($"{GetReportingDate()}: Panic flag off. SPY Price {Portfolio[spySymbol].Price}; its SMA {spySMA}");
}
panicFlg = false;
return;
}
if (!panicFlg)
{
Log($"{GetReportingDate()}: Panic flag on. SPY Price {Portfolio[spySymbol].Price}; its SMA {spySMA}");
}
panicFlg = true;
if (tltSymbol == null)
{
tltSymbol = AddEquity(bondString, Resolution.Daily).Symbol;
}
foreach (var security in Portfolio.Keys)
{
if (Portfolio[security].Symbol != tltSymbol && Portfolio[security].Invested)
{
Liquidate(security, "Selling as S&P 500 is too low ");
}
}
SetHoldings(tltSymbol, _fullHoldings);
}
#endregion Private Methods
}
}