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 } }