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