| 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]) });
}
}
}
}
}