This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using QuantConnect.Data; | |
using QuantConnect.Data.Market; | |
using QuantConnect.Indicators; | |
using QuantConnect.Orders; | |
using QuantConnect.Orders.Fees; | |
using QuantConnect.Orders.Fills; | |
using QuantConnect.Securities; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
namespace QuantConnect.Algorithm.CSharp | |
{ | |
public class RiskManagerTestingAlgorithm : QCAlgorithm | |
{ | |
private decimal _maxExposure = 0.8m; | |
private decimal _maxExposurePerTrade = 0.3m; | |
private decimal _riskPerTrade = 0.02m; | |
bool _runMarginTest = true; | |
private List<Identity> priceIndicators; | |
private FxRiskManagment RiskManager; | |
public override void Initialize() | |
{ | |
SetStartDate(2008, 11, 17); //Set Start Date | |
SetEndDate(2008, 11, 28); //Set End Date | |
SetCash(10000); //Set Strategy Cash | |
AddForex("USDJPY", Resolution.Daily, market: "oanda", leverage: 10); | |
AddForex("EURUSD", Resolution.Daily, market: "oanda", leverage: 10); | |
var gbpResolution = LiveMode ? Resolution.Minute : Resolution.Daily; | |
AddForex("GBPUSD", gbpResolution, market: "oanda", leverage: 20); | |
priceIndicators = new List<Identity>(); | |
foreach (var pair in Securities.Keys) | |
{ | |
Securities[pair].FeeModel = new ConstantFeeModel(0m); | |
Securities[pair].FillModel = new ImmediateFillModel(); | |
var vol = 0.01m * (pair.Value == "USDJPY" ? 100 : 1); | |
Securities[pair].VolatilityModel = new ConstantVolatilityModel(vol); | |
priceIndicators.Add(Identity(pair)); | |
} | |
RiskManager = new FxRiskManagment(Portfolio, _riskPerTrade, _maxExposurePerTrade, _maxExposure); | |
} | |
public override void OnData(Slice slice) | |
{ | |
Tuple<int, decimal> entryOrders; | |
decimal stopLossPrice; | |
if (!LiveMode) | |
{ | |
#region Test: if the used margin is bigger than the max exposure, then the entry order quantity must be zero. | |
// Buy EURUSD beyond the max exposure. | |
if (slice.Time.Day == 17) | |
{ | |
MarketOrder("EURUSD", 65000); | |
} | |
if (slice.Time.Day == 18) | |
{ | |
// Test 1: if the used margin is bigger than the max exposure, then the entry order quantity must be zero. | |
entryOrders = RiskManager.CalculateEntryOrders("EURUSD", AgentAction.GoLong); | |
if (entryOrders.Item1 != 0) | |
{ | |
throw new Exception("The RiskManager allows operations beyond the max exposure."); | |
} | |
// Clean Stuff and set up the next test. | |
Liquidate("EURUSD"); | |
} | |
#endregion Test: if the used margin is bigger than the max exposure, then the entry order quantity must be zero. | |
#region Tests: Happy path. | |
if (slice.Time.Day == 19) | |
{ | |
Portfolio.SetCash(10000m); | |
// Test 2: happy path with a long entry with USD as base currency. | |
entryOrders = RiskManager.CalculateEntryOrders("EURUSD", AgentAction.GoLong); | |
stopLossPrice = slice["EURUSD"].Close - Securities["EURUSD"].VolatilityModel.Volatility; | |
if (entryOrders.Item1 != 20000 || entryOrders.Item2 != stopLossPrice) | |
{ | |
throw new Exception("Quantity or StopLoss price estimated incorrectly when USD is the base currency."); | |
} | |
// Test 3: estimate a short entry orders with JPY as base currency. | |
entryOrders = RiskManager.CalculateEntryOrders("USDJPY", AgentAction.GoShort); | |
stopLossPrice = slice["USDJPY"].Close + Securities["USDJPY"].VolatilityModel.Volatility; | |
if (entryOrders.Item1 != -19000 || entryOrders.Item2 != stopLossPrice) | |
{ | |
throw new Exception("Quantity or StopLoss price estimated incorrectly when USD is not the base currency."); | |
} | |
// Send the order and set the thing up for the next tests. | |
MarketOrder("USDJPY", entryOrders.Item1); | |
// Stop price high to avoid execution. | |
StopMarketOrder("USDJPY", -entryOrders.Item1, 98); | |
} | |
if (slice.Time.Day == 20) | |
{ | |
// Test 4: estimate a new long entry order with USD as base currency. | |
entryOrders = RiskManager.CalculateEntryOrders("EURUSD", AgentAction.GoLong); | |
stopLossPrice = slice["EURUSD"].Close - Securities["EURUSD"].VolatilityModel.Volatility; | |
if (entryOrders.Item1 != 21000 || entryOrders.Item2 != stopLossPrice) | |
{ | |
throw new Exception("Quantity or StopLoss price estimated incorrectly when USD is the base currency."); | |
} | |
MarketOrder("EURUSD", entryOrders.Item1); | |
StopMarketOrder("EURUSD", -entryOrders.Item1, entryOrders.Item2); | |
} | |
#endregion Tests: Happy path. | |
#region Test: Update trailing orders. | |
if (slice.Time.Day == 21) | |
{ | |
// Test 5: update trailing orders | |
RiskManager.UpdateTrailingStopOrders(); | |
var tickets = Transactions.GetOrderTickets(o => o.OrderType == OrderType.StopMarket && o.Status == OrderStatus.Submitted); | |
foreach (var ticket in tickets) | |
{ | |
var actualStopLossPrice = ticket.UpdateRequests.First().StopPrice; | |
var expectedStopLossPrice = Securities[ticket.Symbol].Price + | |
(Securities[ticket.Symbol].VolatilityModel.Volatility * (ticket.Quantity < 0 ? -1 : 1)); | |
if (actualStopLossPrice != expectedStopLossPrice) throw new Exception("Trailing stop loss fail."); | |
} | |
// Clean all stuff for the next test. | |
Liquidate("EURUSD"); | |
Liquidate("USDJPY"); | |
} | |
#endregion Test: Update trailing orders. | |
} | |
#region Test: Margin use and Leverage. | |
var testDay = 23; | |
if (LiveMode) testDay = DateTime.Today.Day; | |
if (slice.Time.Day == testDay && _runMarginTest) | |
{ | |
// Get the actual portfolio value | |
var marginPreOrder = Portfolio.MarginRemaining; | |
entryOrders = RiskManager.CalculateEntryOrders("GBPUSD", AgentAction.GoLong); | |
var orderQuantity = entryOrders.Item1; | |
var pairLeverage = Securities["GBPUSD"].Leverage; | |
var pairRate = Securities["GBPUSD"].Price; | |
var expectedMarginPostOrder = marginPreOrder - (orderQuantity * pairRate) / pairLeverage; | |
var orderTicket = MarketOrder("GBPUSD", entryOrders.Item1); | |
if (LiveMode) | |
{ | |
do | |
{ } while (orderTicket.Status == OrderStatus.Filled); | |
} | |
// The difference can be one because of the default fees. | |
var actualdMarginPostOrder = Portfolio.MarginRemaining; | |
var difference = Math.Abs((double)(actualdMarginPostOrder - expectedMarginPostOrder)); | |
var differenceTolerance = 5; | |
if (difference > differenceTolerance) | |
{ | |
Liquidate("GBPUSD"); | |
var errorMsg = string.Format(@"Expected margin post order fill: {0}\nActual margin: {1}.\nDifference: {2}", | |
expectedMarginPostOrder, actualdMarginPostOrder, difference); | |
throw new Exception(errorMsg); | |
} | |
_runMarginTest = false; | |
Liquidate("GBPUSD"); | |
} | |
#endregion Test: Margin use and Leverage. | |
} | |
internal class ConstantVolatilityModel : IVolatilityModel | |
{ | |
private decimal volatility; | |
public ConstantVolatilityModel(decimal v) | |
{ | |
this.volatility = v; | |
} | |
public decimal Volatility | |
{ | |
get | |
{ | |
return volatility; | |
} | |
} | |
public IEnumerable<HistoryRequest> GetHistoryRequirements(Security security, DateTime utcTime) | |
{ | |
return Enumerable.Empty<HistoryRequest>(); | |
} | |
public void Update(Security security, BaseData data) | |
{ } | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment