Overall Statistics
Total Trades
273
Average Win
4.59%
Average Loss
-1.65%
Compounding Annual Return
25.352%
Drawdown
25.900%
Expectancy
0.148
Net Profit
32.531%
Sharpe Ratio
0.693
Loss Rate
70%
Win Rate
30%
Profit-Loss Ratio
2.78
Alpha
0.194
Beta
0.014
Annual Standard Deviation
0.281
Annual Variance
0.079
Information Ratio
0.364
Tracking Error
0.298
Treynor Ratio
14.338
Total Fees
$0.00
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]) });
                }
            }
        }
    }
}