| Overall Statistics |
|
Total Trades 129 Average Win 2.80% Average Loss -2.36% Compounding Annual Return 200.845% Drawdown 22.100% Expectancy 0.266 Net Profit 44.052% Sharpe Ratio 2.198 Loss Rate 42% Win Rate 58% Profit-Loss Ratio 1.19 Alpha 0.832 Beta -0.109 Annual Standard Deviation 0.378 Annual Variance 0.143 Information Ratio 2.065 Tracking Error 0.395 Treynor Ratio -7.592 Total Fees $377.15 |
namespace QuantConnect
{
/// <summary>
/// Basic template algorithm simply initializes the date range and cash
/// </summary>
public class ForexRiskManagerExample : QCAlgorithm
{
List<Symbol> forexPairs = new List<Symbol>();
Dictionary<Symbol, CrossingMovingAverages> MovingAverageCross = new Dictionary<QuantConnect.Symbol, CrossingMovingAverages>();
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.8m;
// How much of the total strategy equity can be at risk in a single trade.
private const decimal _maxExposurePerTrade = 0.25m;
// The max strategy equity proportion to put at risk in a single operation.
private const decimal _riskPerTrade = 0.03m;
// Smallest lot
private LotSize _lotSize = LotSize.Mini;
FxRiskManagment RiskManager;
public override void Initialize()
{
SetStartDate(2015, 01, 01); //Set Start Date
SetEndDate(2015, 04, 30); //Set End Date
SetCash(10000); //Set Strategy Cash
forexPairs.Add(AddForex("EURUSD", Resolution.Minute, market: "oanda", leverage: 50).Symbol);
forexPairs.Add(AddForex("USDJPY", Resolution.Minute, market: "oanda", leverage: 50).Symbol);
RiskManager = new FxRiskManagment(Portfolio, _riskPerTrade, _maxExposurePerTrade, _maxExposure, _lotSize);
foreach (var pair in forexPairs)
{
Securities[pair].VolatilityModel = new ThreeSigmaVolatilityModel(STD(pair, 12 * 60, Resolution.Minute));
var fast_moving_average = new ExponentialMovingAverage(1440).Of(Identity(pair));
var slow_moving_average = new LinearWeightedMovingAverage(1440).Of(Identity(pair));
MovingAverageCross[pair] = new CrossingMovingAverages(fast_moving_average, slow_moving_average);
}
SetWarmUp(TimeSpan.FromMinutes(1440));
SetBrokerageModel(Brokerages.BrokerageName.OandaBrokerage);
}
/// <summary>
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
/// </summary>
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
public override void OnData(Slice data)
{
if (IsWarmingUp) return;
foreach (var pair in forexPairs)
{
if (!data.ContainsKey(pair) || !MovingAverageCross[pair].IsReady) continue;
var signal = MovingAverageCross[pair].Signal;
if ( signal == CrossingMovingAveragesSignals.FastCrossSlowFromAbove
|| signal == CrossingMovingAveragesSignals.FastCrossSlowFromBelow)
{
if ((Portfolio[pair].IsLong && signal == CrossingMovingAveragesSignals.FastCrossSlowFromAbove)
|| (Portfolio[pair].IsShort && signal == CrossingMovingAveragesSignals.FastCrossSlowFromBelow))
{
Liquidate(pair);
}
else if (!Portfolio[pair].Invested)
{
var actualAction = (signal == CrossingMovingAveragesSignals.FastCrossSlowFromBelow) ? AgentAction.GoLong : AgentAction.GoShort;
var entryValues = RiskManager.CalculateEntryOrders(pair, actualAction);
if (entryValues.Item1 == 0) continue;
var ticket = MarketOrder(pair, entryValues.Item1);
StopMarketOrder(pair, -entryValues.Item1, entryValues.Item2);
}
}
}
RiskManager.UpdateTrailingStopOrders();
}
}
}namespace QuantConnect
{
public enum CrossingMovingAveragesSignals
{
Bullish = 1,
FastCrossSlowFromAbove = -2,
Bearish = -1,
FastCrossSlowFromBelow = 2,
}
public class CrossingMovingAverages
{
CompositeIndicator<IndicatorDataPoint> _moving_average_difference;
public CrossingMovingAveragesSignals Signal { get; private set; }
private bool _isReady;
int _lastSignal;
public bool IsReady
{
get { return _isReady; }
}
public CrossingMovingAverages(IndicatorBase<IndicatorDataPoint> fast_moving_average, IndicatorBase<IndicatorDataPoint> slow_moving_average)
{
_moving_average_difference = fast_moving_average.Minus(slow_moving_average);
_moving_average_difference.Updated += ma_Updated;
}
private void ma_Updated(object sender, IndicatorDataPoint updated)
{
if (!_isReady)
{
_isReady = _moving_average_difference.Right.IsReady;
return;
}
var actualSignal = Math.Sign(_moving_average_difference);
if (actualSignal == _lastSignal || _lastSignal == 0)
{
Signal = (CrossingMovingAveragesSignals)actualSignal;
}
else if (_lastSignal == -1 && actualSignal == 1)
{
Signal = CrossingMovingAveragesSignals.FastCrossSlowFromBelow;
}
else if (_lastSignal == 1 && actualSignal == -1)
{
Signal = CrossingMovingAveragesSignals.FastCrossSlowFromAbove;
}
_lastSignal = actualSignal;
}
}
}namespace QuantConnect
{
public enum LotSize
{
Standard = 100000,
Mini = 10000,
Micro = 1000,
Nano = 100,
}
public enum AgentAction
{
GoShort = -1,
DoNothing = 0,
GoLong = 1
}
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 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;
}
/// <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 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.
var 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;
var stopLossPrice = closePrice + (action == AgentAction.GoLong ? -volatility : volatility);
return Tuple.Create(quantity, stopLossPrice);
}
/// <summary>
/// Updates the stop-loss price of all open StopMarketOrders.
/// </summary>
public void UpdateTrailingStopOrders()
{
// Get all the spot-loss orders.
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 = newStopLossPrice });
}
}
}
}
}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>();
}
}
}