using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using QuantConnect.Data;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Parameters;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// </summary>
public class FundamentalFilterExample : QCAlgorithm
{
// Max number of symbols to keep from coarse universe selection
private const int MaxCoarseSymbols = 1000;
private readonly Symbol _tlt = QuantConnect.Symbol.Create("TLT", SecurityType.Equity, Market.USA);
private readonly Symbol _spy = QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA);
// initialize our security changes to nothing
SecurityChanges _changes = SecurityChanges.None;
public override void Initialize()
{
SetStartDate(2015, 01, 01); // Set Start Date
SetEndDate(2016, 01, 01); // Set End Date
SetCash(100000); // Set Strategy Cash
AddSecurity(SecurityType.Equity, _tlt, Resolution.Daily);
AddSecurity(SecurityType.Equity, _spy, Resolution.Daily);
// Schedule.On(DateRules.MonthStart(), TimeRules.At(9, 50), () => { Rebalance(); });
UniverseSettings.Resolution = Resolution.Daily;
AddUniverse(CoarseSelectionFunction, FineSelectionFunction);
}
// this event fires whenever we have changes to our universe
public override void OnSecuritiesChanged(SecurityChanges changes)
{
_changes = changes;
}
// sort the data by daily dollar volume and take the top 'NumberOfSymbolsCoarse'
public IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
{
// select only symbols with fundamental data and sort descending by daily dollar volume
var sortedByDollarVolume = coarse
.Where(x => x.HasFundamentalData && x.Price > 1.0m && x.Volume > 0)
.OrderByDescending(x => x.DollarVolume);
// take the top entries from our sorted collection
var top = sortedByDollarVolume.Take(MaxCoarseSymbols);
Debug(Time + " coarse selection count: " + top.Count());
// we need to return only the symbol objects
return top.Select(x => x.Symbol);
}
public IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine)
{
// at this point we already have:
// * top X symbols by dollar volume
// * price > $1.0
// * volume > 0
// FIXME - how do we cap 30% max allocated to a single sector without sectors??
// FIXME - Why isn't market cap (or at least shares outstanding) available?
/*
Quantopian Q500US Rules
-------------------------
Q500US(minimum_market_cap=500000000)
A default universe containing approximately 500 US equities each day.
Constituents are chosen at the start of each calendar month by selecting the
top 500 “tradeable” stocks by 200-day average dollar volume, capped at 30% of
equities allocated to any single sector
A stock is considered “tradeable” if it meets the following criteria:
The stock must be the primary share class for its company.
The company issuing the stock must have known market capitalization.
The stock must not be a depository receipt.
The stock must not be traded over the counter (OTC).
The stock must not be for a limited partnership.
The stock must have a known previous-day close price.
The stock must have had nonzero volume on the previous trading day.
*/
Debug(Time + " fine universe selection: " + fine.Count());
// Define the approximate Q500US universe
var filtered = fine.Where(x =>
{
try {
var screen = x.SecurityReference.IsPrimaryShare
// approximate market cap > 5 billion
&& x.EarningReports.BasicAverageShares.HasValues()
&& x.EarningReports.BasicAverageShares.Value * x.Price > 5e9m
// no depository receipts
&& !x.SecurityReference.IsDepositaryReceipt
// no pink sheets
&& x.CompanyReference.PrimaryExchangeID != "OTCPK" &&
x.CompanyReference.PrimaryExchangeID != "OTCBB"
// must not be an limited parntership
&& !x.CompanyReference.IsLimitedPartnership
&& !Regex.IsMatch(x.CompanyReference.StandardName, ".*L[. ]?P.?$")
// non when-issued equities
&& !x.Symbol.EndsWith(".WI");
return screen;
} catch (Exception e) {
Debug("fine filter exception: " + e);
return false;
}
})
// order decreasing (approximate) market cap
.OrderByDescending(x => x.EarningReports.BasicAccountingChange.Value * x.Price)
.Take(500);
Debug("filtered: " + filtered.Count());
return filtered.Select(x => x.Symbol);
}
public override void OnData(Slice data)
{
Debug("OnData: " + Time + ", bars: " + data.Count);
// if we have no changes, do nothing
if (_changes == SecurityChanges.None) return;
// liquidate removed securities
foreach (var security in _changes.RemovedSecurities)
{
if (security.Invested)
{
Liquidate(security.Symbol);
}
Debug("Removing security: " + security.Symbol);
}
foreach (var security in _changes.AddedSecurities)
{
Debug("Adding security: " + security.Symbol);
}
// reset
_changes = SecurityChanges.None;
}
}
}