Overall Statistics |
Total Trades 268 Average Win 4.74% Average Loss -1.53% Compounding Annual Return 61.743% Drawdown 22.000% Expectancy 0.323 Net Profit 82.093% Sharpe Ratio 1.355 Loss Rate 68% Win Rate 32% Profit-Loss Ratio 3.09 Alpha 0.367 Beta 0.012 Annual Standard Deviation 0.272 Annual Variance 0.074 Information Ratio 0.981 Tracking Error 0.289 Treynor Ratio 30.703 Total Fees $3468.73 |
using NodaTime; namespace QuantConnect { /// <summary> /// Daily Fx demonstration to call on and use the FXCM Calendar API /// </summary> public class FXCM_MacroEconomicSentiment : QCAlgorithm { string[] _tickers = { "EURUSD", "USDJPY", "USDCHF", "GBPUSD", "USDCAD", "AUDUSD"}; List<Symbol> _symbols = new List<Symbol>(); FxRiskManagment RiskManager; private const decimal _leverage = 50m; // How much of the total strategy equity can be at risk as maximum in all trades. private const decimal _maxExposure = 0.7m; // How much of the total strategy equity can be at risk in a single trade. private const decimal _maxExposurePerTrade = 0.1m; // The max strategy equity proportion to put at risk in a single operation. private const decimal _riskPerTrade = 0.02m; // Smallest lot private LotSize _lotSize = LotSize.Mini; /// <summary> /// Add the Daily FX type to our algorithm and use its events. /// </summary> public override void Initialize() { SetStartDate(2016, 1, 1); //Set Start Date SetEndDate(2017, 3, 30); //Set End Date SetCash(100000); //Set Strategy Cash AddData<DailyFx>("DFX", Resolution.Minute, DateTimeZone.Utc); foreach (var ticker in _tickers) { _symbols.Add(AddForex(ticker, market: Market.Oanda, leverage: _leverage).Symbol); var symbol = _symbols.Last(); Securities[symbol].VolatilityModel = new ThreeSigmaVolatilityModel(STD(symbol, 24, Resolution.Hour)); } SetBrokerageModel(BrokerageName.OandaBrokerage); RiskManager = new FxRiskManagment(Portfolio, _riskPerTrade, _maxExposurePerTrade, _maxExposure, _lotSize); } public override void OnData(Slice slice) { // RiskManager.UpdateTrailingStopOrders(); } /// <summary> /// Trigger an event on a complete calendar event which has an actual value. /// </summary> public void OnData(DailyFx calendar) { // We only want to trade the news of all others currencies against USD. if (calendar.Currency.ToUpper() == "USD") return; // The algorithm only uses meaningful and important news/announcements. if (calendar.Importance == FxDailyImportance.High && calendar.Meaning != FxDailyMeaning.None) { foreach (var symbol in _symbols) { var fxPair = (Forex)Securities[symbol]; // Check if the new/announcement affects the Base or the Quote of the actual pair. var isBaseCurrency = fxPair.BaseCurrencySymbol == calendar.Currency.ToUpper(); var isQuoteCurrency = fxPair.QuoteCurrency.Symbol == calendar.Currency.ToUpper(); var quantity = 0; var stopLossPrice = 0m; if (calendar.Meaning == FxDailyMeaning.Better && isBaseCurrency || calendar.Meaning == FxDailyMeaning.Worse && isQuoteCurrency) { var entryValues = RiskManager.CalculateEntryOrders(symbol, AgentAction.GoLong); if (entryValues.Item1 == 0) continue; quantity = entryValues.Item1; stopLossPrice = entryValues.Item2; } if (calendar.Meaning == FxDailyMeaning.Worse && isBaseCurrency || calendar.Meaning == FxDailyMeaning.Better && isQuoteCurrency) { var entryValues = RiskManager.CalculateEntryOrders(symbol, AgentAction.GoShort); if (entryValues.Item1 == 0) continue; quantity = entryValues.Item1; stopLossPrice = entryValues.Item2; } if (quantity != 0) { if (Portfolio[symbol].Invested) { // If the signal has the opposite direction of our actual position, then liquidate. if (quantity > 0 && Portfolio[symbol].IsShort || quantity < 0 && Portfolio[symbol].IsLong) { Liquidate(symbol); } } else { EntryToMarket(fxPair, quantity, stopLossPrice); } } } } } private void EntryToMarket(Forex fxPair, int quantity, decimal stopLossPrice) { var price = fxPair.Price; // Entry to the Market now! MarketOrder(fxPair.Symbol, quantity, tag: "Entry"); // The take profit/loss ratio is 3:1 var takeProfitPrice = price + (price - stopLossPrice) * 3m; LimitOrder(fxPair.Symbol, -quantity, takeProfitPrice, "Profit Target"); // Set the risk RiskManager stopLoss price. StopMarketOrder(fxPair.Symbol, -quantity, stopLossPrice, "Stop Loss"); } public override void OnOrderEvent(OrderEvent orderEvent) { var actualOrder = Transactions.GetOrderById(orderEvent.OrderId); switch (orderEvent.Status) { // If the filled order isn't Market, means that the Take Profit // of the Stop Loos were hit. I use liquidate because it also // cancell all submitted but no filled orders. case OrderStatus.Filled: //Log("\t => " + orderEvent.ToString()); if (actualOrder.Type != OrderType.Market) { Liquidate(orderEvent.Symbol); } break; // To make sure the submitted orders are cancelled. case OrderStatus.Canceled: //Log("\t => " + orderEvent.ToString() + "\n"); break; default: break; } } public override void OnEndOfDay() { // When the TrailingOrder are used, QC throws complains about lack of memory, // this and helps a little, but if the Algorithm is long enough, the // memory limit is hit again. GC.Collect(); } } }
namespace QuantConnect { internal class AtrVolatility : IVolatilityModel { private AverageTrueRange averageTrueRange; public AtrVolatility(AverageTrueRange averageTrueRange) { this.averageTrueRange = averageTrueRange; } public decimal Volatility { get { return averageTrueRange; } } public IEnumerable<HistoryRequest> GetHistoryRequirements(Security security, DateTime utcTime) { return Enumerable.Empty<HistoryRequest>(); } public void Update(Security security, BaseData data) { } } }
namespace QuantConnect { /// <summary> /// Provides an implementation of <see cref="IVolatilityModel"/> that computes the /// relative standard deviation as the volatility of the security /// </summary> public class ThreeSigmaVolatilityModel : IVolatilityModel { private readonly TimeSpan _periodSpan; private StandardDeviation _standardDeviation; /// <summary> /// Gets the volatility of the security as a percentage /// </summary> public decimal Volatility { get { return _standardDeviation * 2.5m; } } /// <summary> /// Initializes a new instance of the <see cref="QuantConnect.Securities.RelativeStandardDeviationVolatilityModel"/> class /// </summary> /// <param name="periodSpan">The time span representing one 'period' length</param> /// <param name="periods">The nuber of 'period' lengths to wait until updating the value</param> public ThreeSigmaVolatilityModel(StandardDeviation standardDeviation) { _standardDeviation = standardDeviation; _periodSpan = TimeSpan.FromMinutes(standardDeviation.Period); } /// <summary> /// Updates this model using the new price information in /// the specified security instance /// </summary> /// <param name="security">The security to calculate volatility for</param> /// <param name="data"></param> public void Update(Security security, BaseData data) { } public IEnumerable<HistoryRequest> GetHistoryRequirements(Security security, DateTime utcTime) { return Enumerable.Empty<HistoryRequest>(); } } }
namespace QuantConnect { public enum AgentAction { GoShort = -1, DoNothing = 0, GoLong = 1 } public enum LotSize { Standard = 100000, Mini = 10000, Micro = 1000, Nano = 100, } public class FxRiskManagment { // Maximum equity proportion to put at risk in a single operation. private decimal _riskPerTrade; // Maximum equity proportion at risk in open positions in a given time. private decimal _maxExposure; // Maximum equity proportion at risk in a single trade. private decimal _maxExposurePerTrade; private int _lotSize; private int _minQuantity; private Dictionary<Symbol, int> RoundToPip; private SecurityPortfolioManager _portfolio; /// <summary> /// Initializes a new instance of the <see cref="FxRiskManagment"/> class. /// </summary> /// <param name="portfolio">The QCAlgorithm Portfolio.</param> /// <param name="riskPerTrade">The max risk per trade.</param> /// <param name="maxExposurePerTrade">The maximum exposure per trade.</param> /// <param name="maxExposure">The maximum exposure in all trades.</param> /// <param name="lotsize">The minimum quantity to trade.</param> /// <exception cref="System.NotImplementedException">The pairs should be added to the algorithm before initialize the risk manager.</exception> public FxRiskManagment(SecurityPortfolioManager portfolio, decimal riskPerTrade, decimal maxExposurePerTrade, decimal maxExposure, LotSize lotsize = LotSize.Micro, int minQuantity = 5) { _portfolio = portfolio; if (_portfolio.Securities.Count == 0) { throw new NotImplementedException("The pairs should be added to the algorithm before initialize the risk manager."); } this._riskPerTrade = riskPerTrade; _maxExposurePerTrade = maxExposurePerTrade; this._maxExposure = maxExposure; _lotSize = (int)lotsize; _minQuantity = minQuantity; RoundToPip = new Dictionary<Symbol, int>(); foreach (var symbol in _portfolio.Securities.Keys) { RoundToPip[symbol] = -(int)Math.Log10((double)_portfolio.Securities[symbol].SymbolProperties.MinimumPriceVariation * 10); } } /// <summary> /// Calculates the entry orders and stop-loss price. /// </summary> /// <param name="pair">The Forex pair Symbol.</param> /// <param name="action">The order direction.</param> /// <returns>a Tuple with the quantity as Item1 and the stop-loss price as Item2. If quantity is zero, then means that no trade must be done.</returns> public Tuple<int, decimal> CalculateEntryOrders(Symbol pair, AgentAction action) { // If exposure is greater than the max exposure, then return zero. if (_portfolio.TotalMarginUsed > _portfolio.TotalPortfolioValue * _maxExposure) { return Tuple.Create(0, 0m); } var quantity = 0; var stopLossPrice = 0m; try { var closePrice = _portfolio.Securities[pair].Price; var leverage = _portfolio.Securities[pair].Leverage; var exchangeRate = _portfolio.Securities[pair].QuoteCurrency.ConversionRate; var volatility = _portfolio.Securities[pair].VolatilityModel.Volatility; // Estimate the maximum entry order quantity given the risk per trade. var moneyAtRisk = _portfolio.TotalPortfolioValue * _riskPerTrade; var maxQuantitybyRisk = moneyAtRisk / (volatility * exchangeRate); // Estimate the maximum entry order quantity given the exposure per trade. var maxBuySize = Math.Min(_portfolio.MarginRemaining, _portfolio.TotalPortfolioValue * _maxExposurePerTrade) * leverage; var maxQuantitybyExposure = maxBuySize / (closePrice * exchangeRate); // The final quantity is the lowest of both. quantity = (int)(Math.Round(Math.Min(maxQuantitybyRisk, maxQuantitybyExposure) / _lotSize, 0) * _lotSize); // If the final quantity is lower than the minimum quantity of the given lot size, then return zero. if (quantity < _lotSize * _minQuantity) return Tuple.Create(0, 0m); quantity = action == AgentAction.GoLong ? quantity : -quantity; stopLossPrice = closePrice + (action == AgentAction.GoLong ? -volatility : volatility); } catch (Exception ex) { Console.WriteLine(ex.ToString()); } return Tuple.Create(quantity, stopLossPrice); } /// <summary> /// Updates the stop-loss price of all open StopMarketOrders. /// </summary> public void UpdateTrailingStopOrders() { // Get all the open var openStopLossOrders = _portfolio.Transactions.GetOrderTickets(o => o.OrderType == OrderType.StopMarket && o.Status == OrderStatus.Submitted); foreach (var ticket in openStopLossOrders) { var stopLossPrice = ticket.SubmitRequest.StopPrice; var volatility = _portfolio.Securities[ticket.Symbol].VolatilityModel.Volatility; var actualPrice = _portfolio.Securities[ticket.Symbol].Price; // The StopLossOrder has the opposite direction of the original order. var originalOrderDirection = ticket.Quantity > 0 ? OrderDirection.Sell : OrderDirection.Buy; var newStopLossPrice = actualPrice + (volatility * (originalOrderDirection == OrderDirection.Buy ? -1 : 1)); if ((originalOrderDirection == OrderDirection.Buy && newStopLossPrice > stopLossPrice) || (originalOrderDirection == OrderDirection.Sell && newStopLossPrice < stopLossPrice)) { ticket.Update(new UpdateOrderFields { StopPrice = Math.Round(newStopLossPrice, RoundToPip[ticket.Symbol]) }); } } } } }