Overall Statistics |
Total Trades 478 Average Win 2.02% Average Loss -1.65% Compounding Annual Return -0.383% Drawdown 66.600% Expectancy 0.136 Net Profit -2.620% Sharpe Ratio 0.22 Loss Rate 49% Win Rate 51% Profit-Loss Ratio 1.23 Alpha 0.074 Beta 0.457 Annual Standard Deviation 0.514 Annual Variance 0.265 Information Ratio 0.054 Tracking Error 0.516 Treynor Ratio 0.247 Total Fees $0.00 |
namespace QuantConnect { public enum EntryMarketDirection { GoShort = -1, DoNothing = 0, GoLong = 1 } }
namespace QuantConnect { /// <summary> /// Every month, the investor considers whether the excess return of each asset over the past 12 /// months is positive or negative and goes long on the contract if it is positive and short if /// negative. The position size is set to be inversely proportional to the instrument’s volatility. /// Source: http://quantpedia.com/Screener/Details/118 /// </summary> /// <seealso cref="QuantConnect.Algorithm.QCAlgorithm" /> public class TimeSeriesMomentumEffect : QCAlgorithm { #region Algorithm Parameters private const decimal maxExposure = 0.5m; private readonly string forexMarket = "oanda"; private readonly string cfdMarket = "oanda"; private const int cfdLeverage = 20; private const int forexLeverage = 20; private const int volatilityWindow = 30; #endregion #region Investment Universe /* The investment universe consists of: - 22 commodity futures - 12 cross-currency pairs (with 9 underlying currencies) - 9 developed equity indexes - 4 developed government bond futures. */ // The 10 year maturity Treasury Yield Curve is used as a proxy of the risk free rate. private const string riskFreeReturnQuandlCode = "USTREASURY/YIELD"; private readonly string[] comoditiesFuturesTickers = { Futures.Metals.Gold, Futures.Metals.Platinum, Futures.Metals.Silver, Futures.Metals.Palladium, Futures.Energies.CrudeOilWTI, Futures.Energies.HeatingOil, Futures.Energies.Gasoline, Futures.Energies.NaturalGas, Futures.Grains.Corn, Futures.Grains.Soybeans, Futures.Grains.SoybeanMeal, Futures.Grains.SoybeanOil, Futures.Grains.Wheat, Futures.Grains.Oats, Futures.Meats.LeanHogs, Futures.Meats.LiveCattle, Futures.Meats.FeederCattle, Futures.Softs.OrangeJuice, Futures.Softs.Cocoa, Futures.Softs.Coffee, Futures.Softs.Cotton2, Futures.Softs.Sugar11 }; private readonly string[] forexTickers = { "EURUSD", "USDJPY", "USDCHF", "GBPUSD", "USDCAD", "AUDUSD", "USDCNH", "NZDUSD", "EURJPY", "EURCHF", "EURGBP", "GBPJPY" }; private readonly string[] cfdTickers = { "AU200AUD", // Australia 200 - Australian Dollar "DE30EUR", // Germany 30 - Euro Dollar "EU50EUR", // Europe 50 - Euro Dollar "CH20CHF", // Swiss 20 - Swiss Frank "FR40EUR", // France 40 - Euro Dollar "HK33HKD", // Hong Kong 33 - Hk Dollar "JP225USD", // Japan 225 - Us Dollar "UK100GBP", // Uk 100 - English Pound "SPX500USD" // S&p 500 - Us Dollar }; private readonly string[] bondsFuturesTickers = { "SCF/CME_TU1_ON", // CBOT 2-year US Treasury Note Futures "SCF/CME_FV1_ON", // CBOT 5-year US Treasury Note Futures "SCF/CME_TY1_ON", // CBOT 10-year US Treasury Note Futures "SCF/CME_US2_ON", // CBOT 30-year US Treasury Bond Futures "SCF/EUREX_FGBL2_EN", // EUREX Euro-Bund Futures (German debt security) "SCF/EUREX_FBTP2_EN", // EUREX Euro-BTP Futures (Italian debt security) "SCF/EUREX_FOAT2_EN" // EUREX Euro-OAT Futures (French debt security) }; #endregion #region Fields private readonly List<Symbol> symbols = new List<Symbol>(); private readonly Dictionary<Symbol, decimal> excessReturns = new Dictionary<Symbol, decimal>(); private readonly Dictionary<SecurityType, decimal> portfolioShareToAssetType = new Dictionary<SecurityType, decimal>(); private bool liquidateAllPositions; private bool monthlyRebalance; private decimal riskFreeRetun; #endregion #region QCAlgorithm Methods public override void Initialize() { // Set the basic algorithm parameters. SetStartDate(2010, 06, 01); SetEndDate(2017, 03, 30); SetCash(100000); var brokerage = forexMarket == "fxcm" ? BrokerageName.FxcmBrokerage : BrokerageName.OandaBrokerage; SetBrokerageModel(brokerage); AddData<QuandlUSTeasuryYield>(riskFreeReturnQuandlCode, Resolution.Daily); foreach (var ticker in forexTickers) { var security = AddForex(ticker, Resolution.Daily, forexMarket, leverage: forexLeverage); symbols.Add(security.Symbol); } foreach (var ticker in cfdTickers) { var security = AddCfd(ticker, Resolution.Daily, cfdMarket, leverage: cfdLeverage); symbols.Add(security.Symbol); } foreach (var symbol in symbols) { // Given the big diversity in currencies and prices,, variance can't be used as volatility proxy because the values will not be comparable. // That's why I use https://en.wikipedia.org/wiki/Coefficient_of_variation Securities[symbol].VolatilityModel = new IndicatorVolatilityModel<IndicatorDataPoint>( STD(symbol, volatilityWindow, Resolution.Daily) .Over(SMA(symbol, volatilityWindow, Resolution.Daily))); if (!portfolioShareToAssetType.ContainsKey(symbol.SecurityType)) portfolioShareToAssetType[symbol.SecurityType] = 1; } /* foreach (var ticker in comoditiesFuturesTickers) symbols.Add(AddFuture(ticker, Resolution.Daily).Symbol); */ var assets = portfolioShareToAssetType.Keys.ToArray(); foreach (var assetType in assets) { portfolioShareToAssetType[assetType] = 1m / assets.Length; } Schedule.On(DateRules.MonthStart(), TimeRules.At(0, 0), () => { UpdateAssetsReturns(); liquidateAllPositions = true; }); SetWarmUp(TimeSpan.FromDays(volatilityWindow)); } public override void OnData(Slice slice) { if (IsWarmingUp) return; if (liquidateAllPositions) { // Liquidate all existing holdings. Liquidate(); liquidateAllPositions = false; monthlyRebalance = true; } if (monthlyRebalance && !Portfolio.Invested) { var newOrders = EstimateNewOrders(); foreach (var order in newOrders) { var unitValue = new MarketOrder(order.Symbol, 1, Time).GetValue(Securities[order.Symbol]); if (unitValue == 0) continue; var orderValue = Portfolio.TotalPortfolioValue * order.TargetHolding; var quantity = (int) (orderValue / unitValue); if (quantity != 0) { MarketOrder(order.Symbol, quantity); } //SetHoldings(order.Symbol, order.TargetHolding); } monthlyRebalance = false; } } public void OnData(Quandl data) { riskFreeRetun = data.Price; } #endregion #region Auxiliary Methods private List<SecuritiesOrders> EstimateNewOrders() { var orders = new List<SecuritiesOrders>(); var volatilitySumByAsset = from s in symbols group s by s.SecurityType into grouped select new { AssetType = grouped.Key, VolatilitySum = grouped.Sum(s => Securities[s].VolatilityModel.Volatility) }; foreach (var symbol in symbols) { var volatility = Securities[symbol].VolatilityModel.Volatility; var weightedVolatility = volatility / volatilitySumByAsset .First(v => v.AssetType == symbol.SecurityType) .VolatilitySum; var leverage = Securities[symbol].Leverage; var portfolioShareAsAsset = portfolioShareToAssetType[symbol.SecurityType]; var orderDirection = Math.Sign(excessReturns[symbol]); var targetHoldings = maxExposure * portfolioShareAsAsset * orderDirection * weightedVolatility * leverage; orders.Add(new SecuritiesOrders { Symbol = symbol, Direction = (EntryMarketDirection) orderDirection, TargetHolding = targetHoldings }); } return orders; } private void UpdateAssetsReturns() { var dateRequest = new DateTime(Time.Year - 1, Time.Month, Time.Day); // I ask for some days before just in case the selected day hasn't historical prices record. var history = History(symbols, dateRequest.AddDays(-5), dateRequest.AddDays(1), Resolution.Daily); foreach (var symbol in symbols) try { var slice = history.Last(s => s.ContainsKey(symbol)); excessReturns[symbol] = (Securities[symbol].Price / slice[symbol].Price - 1m) * 100m - riskFreeRetun; } catch (Exception e) { Console.WriteLine(symbol + " hasn't data to estimate excess returns."); excessReturns[symbol] = 0m; } } #endregion } public struct SecuritiesOrders { public Symbol Symbol; public EntryMarketDirection Direction; public decimal TargetHolding; } public class QuandlBondFutures : Quandl { public QuandlBondFutures() : base("Settle") { } } public class QuandlUSTeasuryYield : Quandl { public QuandlUSTeasuryYield() : base("10 YR") { } } }