Overall Statistics
Total Trades
6
Average Win
2.82%
Average Loss
-0.43%
Compounding Annual Return
26.854%
Drawdown
5.300%
Expectancy
1.542
Net Profit
1.974%
Sharpe Ratio
1.473
Probabilistic Sharpe Ratio
53.824%
Loss Rate
67%
Win Rate
33%
Profit-Loss Ratio
6.62
Alpha
0.545
Beta
-0.939
Annual Standard Deviation
0.168
Annual Variance
0.028
Information Ratio
-0.367
Tracking Error
0.186
Treynor Ratio
-0.264
Total Fees
$5.98
Estimated Strategy Capacity
$640000.00
Lowest Capacity Asset
EURUSD 8G
using System.Collections.Generic;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Securities;

namespace QuantConnect.Algorithm.Framework.Alphas
{
    /// <summary>
    /// </summary>
    public class ExampleForexAlphaModel : AlphaModel
    {
        private readonly int _fastPeriod;
        private readonly int _slowPeriod;
        private readonly Resolution _resolution;
        private readonly Dictionary<Symbol, SymbolData> _symbolData;

        /// <summary>
        /// Initializes a new instance of the <see cref="ExampleForexAlphaModel"/> class
        /// </summary>
        /// <param name="fastPeriod">The fast EMA period</param>
        /// <param name="slowPeriod">The slow EMA period</param>
        /// <param name="resolution">The resolution of data sent into the EMA indicators</param>
        public ExampleForexAlphaModel(
            int fastPeriod = 8,
            int slowPeriod = 21,
            Resolution resolution = Resolution.Hour
            )
        {
            _fastPeriod = fastPeriod;
            _slowPeriod = slowPeriod;
            _resolution = resolution;
            _symbolData = new Dictionary<Symbol, SymbolData>();
            Name = $"{nameof(ExampleForexAlphaModel)}({fastPeriod},{slowPeriod},{resolution})";
        }

        /// <summary>
        /// Determines an insight for each security based on it's current EMA signals
        /// </summary>
        /// <param name="algorithm">The algorithm instance</param>
        /// <param name="data">The new data available</param>
        /// <returns>The new insights generated</returns>
        public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
        {
            foreach (var sd in _symbolData.Values)
            {
                if (sd.Security.Price == 0)
                {
                    continue;
                }

                var direction = InsightDirection.Flat;

                if (sd.EMAFast > sd.EMASlow)
                {
                    direction = InsightDirection.Up;
                }

                // ignore signal for same direction as previous signal
                if (direction == sd.PreviousDirection)
                {
                    continue;
                }

                var insightPeriod = _resolution.ToTimeSpan().Multiply(_fastPeriod);
                var insight = Insight.Price(sd.Security.Symbol, insightPeriod, direction);
                sd.PreviousDirection = insight.Direction;
                yield return insight;
            }
        }

        /// <summary>
        /// Event fired each time the we add/remove securities from the data feed.
        /// This initializes the EMAs for each added security and cleans up the indicator for each removed security.
        /// </summary>
        /// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
        /// <param name="changes">The security additions and removals from the algorithm</param>
        public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
        {
            foreach (var added in changes.AddedSecurities)
            {
                if (_symbolData.ContainsKey(added.Symbol))
                {
                    continue;
                }
                _symbolData.Add(added.Symbol, new SymbolData(algorithm, added, _fastPeriod, _slowPeriod, _resolution));
            }

            foreach (var removed in changes.RemovedSecurities)
            {
                SymbolData data;
                if (_symbolData.TryGetValue(removed.Symbol, out data))
                {
                    // clean up our consolidator
                    algorithm.SubscriptionManager.RemoveConsolidator(data.Security.Symbol, data.Consolidator);
                    _symbolData.Remove(removed.Symbol);
                }
            }
        }

        class SymbolData
        {
            public InsightDirection? PreviousDirection { get; set; }

            public readonly Security Security;
            public readonly IDataConsolidator Consolidator;
            public readonly ExponentialMovingAverage EMAFast;
            public readonly ExponentialMovingAverage EMASlow;

            public SymbolData(QCAlgorithm algorithm, Security security, int fastPeriod, int slowPeriod, Resolution resolution)
            {
                Security = security;
                Consolidator = algorithm.ResolveConsolidator(security.Symbol, resolution);
                algorithm.SubscriptionManager.AddConsolidator(security.Symbol, Consolidator);

                EMAFast = new ExponentialMovingAverage(fastPeriod);
                EMASlow = new ExponentialMovingAverage(slowPeriod);

                algorithm.RegisterIndicator(security.Symbol, EMAFast, Consolidator);
                algorithm.RegisterIndicator(security.Symbol, EMASlow, Consolidator);
            }
        }
    }
}
using System;
using System.Collections.Generic;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;

namespace QuantConnect.Algorithm.Framework.Portfolio
{
    public class ExampleForexPortfolioConstructionModel : IPortfolioConstructionModel
    {
        private readonly decimal _profitTargetPercentage;
        public readonly decimal _maxEquityPerTradePercentage;
        private readonly decimal _maxLeverage;
        private readonly Resolution _resolution;
        private readonly Dictionary<Symbol, SymbolData> _symbolData;
        private readonly int _pricePrecision = 4;

        public ExampleForexPortfolioConstructionModel(decimal profitTargetPercentage, decimal maxEquityPerTradePercentage, decimal maxLeverage, Resolution resolution = Resolution.Hour, int pricePrecision = 4)
        {
            _profitTargetPercentage = profitTargetPercentage;
            _maxEquityPerTradePercentage = maxEquityPerTradePercentage;
            _maxLeverage = maxLeverage;
            _resolution = resolution;
            _symbolData = new Dictionary<Symbol, SymbolData>();
            _pricePrecision = pricePrecision;
        }

        public IEnumerable<IPortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights)
        {
            var targets = new List<IPortfolioTarget>();

            foreach (var insight in insights)
            {
                var sd = _symbolData[insight.Symbol];

                if (sd.Window.Count < SymbolData.QuoteBarWindowLength)
                {
                    continue;
                }

                if (insight.Direction == InsightDirection.Up)
                {
                    var price = Math.Round(algorithm.Securities[sd.Symbol].Price, _pricePrecision);
                    var stopLoss = Math.Round(sd.Window[1].Low, _pricePrecision);
                    var takeProfit = Math.Round(sd.Window[1].High * (1 + _profitTargetPercentage), _pricePrecision);

                    var maxEquityPerTrade = algorithm.Portfolio.TotalPortfolioValue * _maxLeverage * _maxEquityPerTradePercentage;
                    var maxQuantityEquity = Math.Round(maxEquityPerTrade / price, 0);

                    targets.Add(new ForexPortfolioTarget(sd.Symbol, maxQuantityEquity, price, takeProfit, stopLoss));
                }
            }

            return targets;
        }

        public void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
        {
            foreach (var added in changes.AddedSecurities)
            {
                if (_symbolData.ContainsKey(added.Symbol))
                {
                    continue;
                }
                _symbolData.Add(added.Symbol, new SymbolData(algorithm, added.Symbol, _resolution));
            }

            foreach (var removed in changes.RemovedSecurities)
            {
                if (_symbolData.TryGetValue(removed.Symbol, out SymbolData data))
                {
                    algorithm.SubscriptionManager.RemoveConsolidator(data.Symbol, data.Consolidator);
                    _symbolData.Remove(removed.Symbol);
                }
            }
        }

        private class SymbolData
        {
            public const int QuoteBarWindowLength = 2;

            public Symbol Symbol { get; }
            public RollingWindow<QuoteBar> Window { get; }
            public IDataConsolidator Consolidator { get; }

            public SymbolData(QCAlgorithm algorithm, Symbol symbol, Resolution resolution)
            {
                Symbol = symbol;

                Consolidator = algorithm.ResolveConsolidator(symbol, resolution);
                algorithm.SubscriptionManager.AddConsolidator(symbol, Consolidator);

                Window = new RollingWindow<QuoteBar>(QuoteBarWindowLength);

                Consolidator.DataConsolidated += DataConsolidated;
            }

            private void DataConsolidated(object sender, IBaseData consolidated)
            {
                var bar = (QuoteBar)consolidated;
                Window.Add(bar);
            }
        }
    }
}

namespace QuantConnect.Algorithm.Framework.Portfolio
{
    public class ForexPortfolioTarget : IPortfolioTarget
    {
        public Symbol Symbol { get; }

        public decimal Quantity { get; }

        public decimal TakeProfit { get; }
        
        public decimal StopLoss { get; }

        public decimal Price { get; }

        public ForexPortfolioTarget(Symbol symbol, decimal quantity, decimal price, decimal takeProfit, decimal stopLoss)
        {
            Symbol = symbol;
            Quantity = quantity;
            Price = price;
            TakeProfit = takeProfit;
            StopLoss = stopLoss;
        }
    }
}
using System.Collections.Generic;

namespace QuantConnect.Algorithm.Framework.Selection
{
    public class ExampleForexCurrencySelectionModel : ManualUniverseSelectionModel
    {
        public ExampleForexCurrencySelectionModel()
            : base(new List<Symbol> {
                Symbol.Create("EURUSD", SecurityType.Forex, Market.Oanda)
            })
        {
        }
    }
}
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Risk;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Brokerages;
using QuantConnect.Orders;
using QuantConnect.Parameters;

namespace QuantConnect.Algorithm
{
    public class ExampleForexAlgoFramework : QCAlgorithm
    {
        /// <summary>
        /// Defines our target profit for each trade.
        /// </summary>
        [Parameter("ProfitTargetPercentage")]
        public decimal ProfitTargetPercentage { get; set; } // 0.1m;

        /// <summary>
        /// Defines the maximum amount of leverage to apply.
        /// </summary>
        [Parameter("MaxLeverage")]
        public int MaxLeverage { get; set; } // 20;

        /// <summary>
        /// Defines the maximum amount of our portfolio's equity to use for a trade.
        /// </summary>
        [Parameter("MaxEquityPerTradePercentage")]
        public decimal MaxEquityPerTradePercentage { get; set; } // 0.25m;

        /// <summary>
        /// Defines the maximum percentage of our portfolio's value to risk in a trade.
        /// </summary>
        [Parameter("MaxRiskPerTradePercentage")]
        public decimal MaxRiskPerTradePercentage { get; set; } // 0.05m;

        private IUniverseSelectionModel _forexSelectionModel;
        private ExampleForexAlphaModel _forexAlphaModel;
        private ExampleForexBracketExecutionModel _forexExecutionModel;
        private ExampleForexPortfolioConstructionModel _forexPortfolioConstructionModel;
        private ExampleForexRiskManagementModel _forexRiskManagementModel;

        public override void Initialize()
        {
            SetCash(100000);
            SetStartDate(2019, 4, 1);
            SetEndDate(2019, 4, 30);
            SetBrokerageModel(new AlphaStreamsBrokerageModel(AccountType.Margin));

            var resolution = Resolution.Hour;
            UniverseSettings.Resolution = resolution;

            _forexSelectionModel = new ExampleForexCurrencySelectionModel();
            _forexAlphaModel = new ExampleForexAlphaModel();
            _forexExecutionModel = new ExampleForexBracketExecutionModel();
            _forexPortfolioConstructionModel = new ExampleForexPortfolioConstructionModel(ProfitTargetPercentage, MaxEquityPerTradePercentage, MaxLeverage, resolution);
            _forexRiskManagementModel = new ExampleForexRiskManagementModel(MaxRiskPerTradePercentage);

            SetUniverseSelection(_forexSelectionModel);
            SetAlpha(_forexAlphaModel);
            SetExecution(_forexExecutionModel);
            SetPortfolioConstruction(_forexPortfolioConstructionModel);
            SetRiskManagement(_forexRiskManagementModel);
        }

        public override void OnOrderEvent(OrderEvent orderEvent)
        {
            _forexExecutionModel.OnOrderEvent(orderEvent);
        }

        public override void OnEndOfAlgorithm()
        {
            Liquidate();
        }
    }
}
using QuantConnect.Algorithm.Framework.Portfolio;
using System;
using System.Linq;
using System.Collections.Generic;

namespace QuantConnect.Algorithm.Framework.Risk
{
    public class ExampleForexRiskManagementModel : RiskManagementModel
    {
        private readonly decimal _maxRiskPerTradePercentage;
        private readonly int _pricePrecision = 4;

        public ExampleForexRiskManagementModel(decimal maxRiskPerTradePercentage, int pricePrecision = 4)
        {
            _maxRiskPerTradePercentage = maxRiskPerTradePercentage;
            _pricePrecision = pricePrecision;
        }

        public override IEnumerable<IPortfolioTarget> ManageRisk(QCAlgorithm algorithm, IPortfolioTarget[] targets)
        {
            var adjustedTargets = new List<IPortfolioTarget>();
            var forexTargets = targets.OfType<ForexPortfolioTarget>().ToList();

            foreach (var target in forexTargets)
            {
                var maxRiskPerTrade = Math.Round(algorithm.Portfolio.TotalPortfolioValue * _maxRiskPerTradePercentage, 0);
                var riskPerUnit = Math.Round(target.Price - target.StopLoss, _pricePrecision);

                var maxQuantityRisk = Math.Round(maxRiskPerTrade / riskPerUnit, 0);
                var quantity = Math.Round(Math.Min(target.Quantity, maxQuantityRisk), 0);

                adjustedTargets.Add(new ForexPortfolioTarget(target.Symbol, quantity, target.Price, target.TakeProfit, target.StopLoss));
            }

            return adjustedTargets;
        }
    }
}
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Orders;

namespace QuantConnect.Algorithm.Framework.Execution
{
    public class ExampleForexBracketExecutionModel : ExecutionModel
    {
        public const string BuyOrderTag = "ForexBuyOrder";
        public const string TakeProfitOrderTag = "ForexTakeProfit";
        public const string StopLossOrderTag = "ForexStopLoss";

        private QCAlgorithm _algorithm;

        public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
        {
            _algorithm = algorithm;

            foreach (var target in targets)
            {
                var forexTarget = (target as ForexPortfolioTarget);

                if (target == null)
                {
                    continue;
                }

                if (!algorithm.Portfolio[target.Symbol].Invested)
                {
                    var marketBuyTicket = algorithm.MarketOrder(forexTarget.Symbol, forexTarget.Quantity, false, BuyOrderTag);
                    algorithm.LimitOrder(forexTarget.Symbol, -forexTarget.Quantity, forexTarget.TakeProfit, TakeProfitOrderTag);
                    algorithm.StopMarketOrder(forexTarget.Symbol, -forexTarget.Quantity, forexTarget.StopLoss, StopLossOrderTag);

                    algorithm.Debug($"Order time: {algorithm.Time}, tag: {marketBuyTicket.Tag}, quantity: {marketBuyTicket.Quantity}, fill price: {marketBuyTicket.AverageFillPrice}, status: {marketBuyTicket.Status}");
                }
            }
        }

        public void OnOrderEvent(OrderEvent orderEvent)
        {
            var orderFromEvent = _algorithm.Transactions.GetOrderById(orderEvent.OrderId);
            var orderTicketFromEvent = _algorithm.Transactions.GetOrderTicket(orderEvent.OrderId);
            var openOrderTickets = _algorithm.Transactions.GetOrderTickets();

            // OCO
            if (orderTicketFromEvent.Status == OrderStatus.Filled)
            {
                // Cancel stop loss order
                if (orderTicketFromEvent.Tag == TakeProfitOrderTag)
                {
                    foreach (var ticket in openOrderTickets)
                    {
                        if (ticket.Tag == StopLossOrderTag)
                        {
                            _algorithm.Debug($"event time: {orderEvent.UtcTime}, event: {orderTicketFromEvent.Tag}, cancelled order: {ticket.Tag}");
                            _algorithm.Transactions.CancelOrder(ticket.OrderId);
                        }
                    }
                }
                // Cancel take profit order
                else if (orderTicketFromEvent.Tag == StopLossOrderTag)
                {
                    foreach (var ticket in openOrderTickets)
                    {
                        if (ticket.Tag == TakeProfitOrderTag)
                        {
                            _algorithm.Debug($"event time: {orderEvent.UtcTime}, event: {orderTicketFromEvent.Tag}, cancelled order: {ticket.Tag}");
                            _algorithm.Transactions.CancelOrder(ticket.OrderId);
                        }
                    }
                }
            }
        }
    }
}