| Overall Statistics |
|
Total Trades 342 Average Win 7.56% Average Loss -7.51% Compounding Annual Return 4.981% Drawdown 80.100% Expectancy 0.074 Net Profit 42.244% Sharpe Ratio 0.3 Loss Rate 46% Win Rate 54% Profit-Loss Ratio 1.01 Alpha 0.155 Beta -0.235 Annual Standard Deviation 0.454 Annual Variance 0.206 Information Ratio 0.118 Tracking Error 0.48 Treynor Ratio -0.581 Total Fees $4515.38 |
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Brokerages;
using QuantConnect.Orders;
using QuantConnect.Parameters;
namespace QuantConnect.Algorithm.CSharp
{
/// <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 ForexVanillaMomentum : QCAlgorithm
{
#region Investment Universe
private readonly string[] forexTickers =
{
"AUDCAD", "AUDCHF", "AUDJPY", "AUDNZD", "AUDUSD", "CADCHF", "CADJPY",
"CHFJPY", "EURAUD", "EURCAD", "EURCHF", "EURGBP", "EURJPY", "EURNOK",
"EURNZD", "EURSEK", "EURTRY", "EURUSD", "GBPAUD", "GBPCAD", "GBPCHF",
"GBPJPY", "GBPNZD", "GBPUSD", "NZDCAD", "NZDCHF", "NZDJPY", "NZDUSD",
"TRYJPY", "USDCAD", "USDCHF", "USDJPY", "USDMXN", "USDNOK", "USDSEK",
"USDTRY" // "USDCNH", "ZARJPY", "USDZAR",
};
#endregion
#region Auxiliary Methods
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 records.
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;
}
catch (Exception e)
{
Console.WriteLine(symbol + " hasn't data to estimate excess returns.");
excessReturns[symbol] = 0m;
}
}
#endregion
#region Algorithm Parameters
[Parameter("broker")] private readonly string forexMarket = "fxcm";
[Parameter("max_exposure")] private readonly decimal maxExposure = 0.5m;
[Parameter("leverage")] private readonly int leverage = 10;
[Parameter("initial_cash")] private readonly int cash = 100000;
[Parameter("pairs_to_trade")] private readonly int pairsToTrade = 1;
#endregion
#region Fields
private readonly List<Symbol> symbols = new List<Symbol>();
private readonly Dictionary<Symbol, decimal> excessReturns = new Dictionary<Symbol, decimal>();
private DateTime monthFirstTradableDay;
private DateTime monthLastTradableDay;
private Symbol[] symbolsToShort;
private Symbol[] symbolsToLong;
private bool readytoTrade;
#endregion
#region QCAlgorithm Methods
public override void Initialize()
{
// Set the basic algorithm parameters.
SetStartDate(2010, 01, 01);
SetEndDate(2017, 03, 30);
SetCash(cash);
var brokerage = forexMarket == "fxcm" ? BrokerageName.FxcmBrokerage : BrokerageName.OandaBrokerage;
SetBrokerageModel(brokerage);
foreach (var ticker in forexTickers)
{
var security = AddForex(ticker, Resolution.Daily, forexMarket, leverage: leverage);
var symbol = security.Symbol;
symbols.Add(security.Symbol);
}
Schedule.On(DateRules.MonthStart(), TimeRules.At(0, 0), () =>
{
var monthLastDay = new DateTime(Time.Year, Time.Month, DateTime.DaysInMonth(Time.Year, Time.Month));
var monthTradingdays = TradingCalendar
.GetDaysByType(TradingDayType.BusinessDay, Time, monthLastDay)
.ToArray();
monthFirstTradableDay = monthTradingdays.First().Date;
monthLastTradableDay = monthTradingdays.Last().Date;
UpdateAssetsReturns();
symbolsToShort = excessReturns.OrderBy(pair => pair.Value)
.Take(pairsToTrade)
.Select(pair => pair.Key)
.ToArray();
symbolsToLong = excessReturns.OrderBy(pair => pair.Value)
.Skip(excessReturns.Count - pairsToTrade)
.Select(pair => pair.Key)
.ToArray();
readytoTrade = true;
});
Schedule.On(DateRules.EveryDay(), TimeRules.At(9, 00), () =>
{
if (!readytoTrade) return;
var symbolsToTrade = symbolsToLong.Concat(symbolsToShort);
foreach (var symbol in symbolsToTrade)
{
if (Time.Date == monthFirstTradableDay)
{
if (Portfolio[symbol].Invested)
throw new NotImplementedException("The asset wasn't liquidated previously!!");
var unitValue = new MarketOrder(symbol, 1, Time).GetValue(Securities[symbol]);
if (unitValue == 0) return;
var orderValue = maxExposure * Portfolio.TotalPortfolioValue * leverage /
(2 * pairsToTrade);
var quantity = (int) (Math.Sign(excessReturns[symbol]) * orderValue / unitValue);
if (quantity != 0)
{
MarketOrder(symbol, quantity);
}
}
}
});
Schedule.On(DateRules.EveryDay(), TimeRules.At(16, 55), () =>
{
if (Time.Date == monthLastTradableDay) Liquidate();
});
}
#endregion
}
}