| Overall Statistics |
|
Total Trades 666 Average Win 1.67% Average Loss -1.27% Compounding Annual Return 63.965% Drawdown 19.200% Expectancy 0.258 Net Profit 168.480% Sharpe Ratio 1.64 Loss Rate 46% Win Rate 54% Profit-Loss Ratio 1.32 Alpha -0.214 Beta 38.199 Annual Standard Deviation 0.333 Annual Variance 0.111 Information Ratio 1.581 Tracking Error 0.333 Treynor Ratio 0.014 Total Fees $3.33 |
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Data.Fundamental;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Demonstration how to group stocks by Morningstar Asset Classification and then compare fundamental factors to select the best stock.
/// This was inspired by the Fundamental Factor Long Short Strategy:
/// - https://www.quantconnect.com/tutorials/strategy-library/fundamental-factor-long-short-strategy
/// </summary>
/// <meta name="tag" content="using data" />
/// <meta name="tag" content="universes" />
/// <meta name="tag" content="coarse universes" />
/// <meta name="tag" content="fine universes" />
public class FundamentalFactorLongUniverse : QCAlgorithm
{
private const int _numberOfSymbolsCoarse = 1000;
private const int _numberOfSymbolsFine = 2;
// initialize our changes to nothing
private SecurityChanges _changes = SecurityChanges.None;
private readonly Dictionary<Symbol, decimal> _dollarVolumeBySymbol = new Dictionary<Symbol, decimal>();
public override void Initialize()
{
UniverseSettings.Resolution = Resolution.Daily;
SetSecurityInitializer(s => s.SetFeeModel(new ConstantFeeModel(.005m)));
SetStartDate(2010, 01, 01);
SetEndDate(2012, 01, 01);
SetCash(50000);
// This add universe method accepts two parameters:
// - coarse selection function: accepts an IEnumerable<CoarseFundamental> and returns an IEnumerable<Symbol>
// - fine selection function: accepts an IEnumerable<FineFundamental> and returns an IEnumerable<Symbol>
AddUniverse(CoarseSelectionFunction, FineSelectionFunction);
}
// sort the data by daily dollar volume and take the top 'NumberOfSymbolsCoarse'
public IEnumerable<Symbol> CoarseSelectionFunction(IEnumerable<CoarseFundamental> coarse)
{
// Coarse Filtering:
// - the stocks must have fundamental data
// - volume must be greater than 0
// - price must be greater than 0
// - avoids Nasdaq test symbols
// - ordered by DollarVolume descending
var sortedByDollarVolume =
(from x in coarse
where x.Symbol != "ZWZZT" || // Nasdaq test symbol
x.Symbol != "ZXZZT" || // Nasdaq test symbol
x.Symbol != "ZVZZT" // Nasdaq test symbol
where x.HasFundamentalData && x.Volume > 0 && x.Price > 0
orderby x.DollarVolume descending
select x).Take(_numberOfSymbolsCoarse).ToList();
// Add coarse symbols to the dollarVolumeBySymbol dictionary for use later in the fine selctor
_dollarVolumeBySymbol.Clear();
foreach (var i in sortedByDollarVolume)
{
_dollarVolumeBySymbol[i.Symbol] = i.DollarVolume;
}
return _dollarVolumeBySymbol.Keys;
}
/// <summary>
///
/// </summary>
/// <param name="fine"></param>
/// <returns></returns>
public IEnumerable<Symbol> FineSelectionFunction(IEnumerable<FineFundamental> fine)
{
// Split stocks into groups by Asset Classification - MorningstarIndustryCode)
// - splitting the stocks up by 148 classifications alows us to compare stocks in the same industry (apples to apples)
// - only selects US stocks
// - only selects stocks from the New York Stock Exchange or Nasdaq
// - will not select stocks within 180 days of IPO date
// - only selects Grade A growth grade stocks
// - order by dollar volume from the dollarVolumeBySector dictionary from the coarse selector
Dictionary<int, IOrderedEnumerable<FineFundamental>> topFineBySector =
(from x in fine
// Group by MorningstarIndustryCode
group x by x.AssetClassification.MorningstarIndustryCode into g
let y = from item in g
where item.CompanyReference.CountryId == "USA" &&
(item.CompanyReference.PrimaryExchangeID == "NYS" || item.CompanyReference.PrimaryExchangeID == "NAS") &&
(Time - item.SecurityReference.IPODate).Days > 180 &&
item.AssetClassification.GrowthGrade == "A"
orderby _dollarVolumeBySymbol[item.Symbol] descending
select item
select new { g.Key, Value = y }
).ToDictionary(x => x.Key, x => x.Value);
// Collections to order sectors by different valuations
Dictionary<int, List<FineFundamental>> sectorByBookValuePerShare = new Dictionary<int, List<FineFundamental>>();
Dictionary<int, List<FineFundamental>> sectorByOperationMargin = new Dictionary<int, List<FineFundamental>>();
Dictionary<int, List<FineFundamental>> sectorByEvToEbit = new Dictionary<int, List<FineFundamental>>();
// Order each sector by 3 valuations
foreach (var sector in topFineBySector)
{
var sectorNumber = sector.Key;
var sectorFine = sector.Value;
// Order each sector by book value per share ratio
var sectorFilteredByBookValuePerShare = sectorFine.Select(x => x)
.OrderBy(y => y.ValuationRatios.BookValuePerShare)
.Select(f => f).ToList();
// Order each sector by OperationMargin
var sectorFilteredByOperationMargin = sectorFine.Select(x => x)
.OrderByDescending(z => z.OperationRatios.OperationMargin.OneYear)
.Select(f => f).ToList();
// Order each sector by EvToEbit ratio.
var sectorFilteredByEvToEbit = sectorFine.Select(x => x)
.OrderBy(r => r.ValuationRatios.EVtoEBIT)
.Select(f => f).ToList();
// Add ordered secotrs to the sector valuation collections
sectorByBookValuePerShare.Add(sectorNumber, sectorFilteredByBookValuePerShare);
sectorByOperationMargin.Add(sectorNumber, sectorFilteredByOperationMargin);
sectorByEvToEbit.Add(sectorNumber, sectorFilteredByEvToEbit);
}
// Put all the sectors in the collections in list's so we can use the index to calculate a score
var bookValuePershareList = sectorByBookValuePerShare.SelectMany(x => x.Value)
.OrderBy(y => y.ValuationRatios.BookValuePerShare)
.Select(x => x.Symbol).Take(25).ToList();
var OperationMarginList = sectorByOperationMargin.SelectMany(x => x.Value)
.OrderByDescending(z => z.OperationRatios.OperationMargin.OneYear)
.Select(x => x.Symbol).Take(25).ToList();
var EvToEbitList = sectorByEvToEbit.SelectMany(x => x.Value)
.OrderBy(r => r.ValuationRatios.EVtoEBIT)
.Select(x => x.Symbol).Take(25).ToList();
Dictionary<Symbol, double> symbolDic = new Dictionary<Symbol, double>();
foreach (var symbol in bookValuePershareList)
{
// Get the index of each symbol in each valuation list. We'll use this value multiplied by a multiplier to get a score.
var value = bookValuePershareList.FindIndex(a => a == symbol);
var quality = EvToEbitList.FindIndex(a => a == symbol);
var momentum = OperationMarginList.FindIndex(a => a == symbol);
// Multiply each valuation by a weight:
// - Value: Multiplied by 40% (.4)
// - Quality: Multiplied by 40% (.4)
// - Momentum: Multiplied by 20% (.2)
var finalValue = (value * .4) + (quality * .4) + (momentum * .2);
symbolDic.Add(symbol, finalValue);
}
// Return the symbol (key) ordered by score (value) ascending and take the defined number of symbols fine
return symbolDic.Select(x => x)
.OrderBy(x => x.Value)
.Select(x => x.Key).Take(_numberOfSymbolsFine);
}
//Data Event Handler: New data arrives here. "TradeBars" type is a dictionary of strings so you can access it by symbol.
public void OnData(TradeBars data)
{
// 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("Liquidated Stock: " + security.Symbol.Value);
}
}
// we want 20% allocation in each security in our universe
foreach (var security in _changes.AddedSecurities)
{
SetHoldings(security.Symbol, 0.5m);
Debug("Purchased Stock: " + security.Symbol.Value);
}
_changes = SecurityChanges.None;
}
// this event fires whenever we have changes to our universe
public override void OnSecuritiesChanged(SecurityChanges changes)
{
_changes = changes;
if (changes.AddedSecurities.Count > 0)
{
Debug("Securities added: " + string.Join(",", changes.AddedSecurities.Select(x => x.Symbol.Value)));
}
if (changes.RemovedSecurities.Count > 0)
{
Debug("Securities removed: " + string.Join(",", changes.RemovedSecurities.Select(x => x.Symbol.Value)));
}
}
}
}