Files
nt8-sdk/tests/NT8.Core.Tests/Risk/BasicRiskManagerTests.cs
Billy Valentine 92f3732b3d
Some checks failed
Build and Test / build (push) Has been cancelled
Phase 0 completion: NT8 SDK core framework with risk management and position sizing
2025-09-09 17:06:37 -04:00

112 lines
4.4 KiB
C#

using NT8.Core.Risk;
using NT8.Core.Common.Models;
using NT8.Core.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace NT8.Core.Tests.Risk
{
[TestClass]
public class BasicRiskManagerTests
{
private ILogger _logger;
private BasicRiskManager _riskManager;
[TestInitialize]
public void TestInitialize()
{
_logger = new BasicLogger("BasicRiskManagerTests");
_riskManager = new BasicRiskManager(_logger);
}
[TestMethod]
public void ValidateOrder_WithinLimits_ShouldAllow()
{
// Arrange
var intent = TestDataBuilder.CreateValidIntent(stopTicks: 8);
var context = TestDataBuilder.CreateTestContext();
var config = TestDataBuilder.CreateTestRiskConfig();
// Act
var result = _riskManager.ValidateOrder(intent, context, config);
// Assert
Assert.IsTrue(result.Allow);
Assert.IsNull(result.RejectReason);
Assert.AreEqual(RiskLevel.Low, result.RiskLevel);
Assert.IsTrue(result.RiskMetrics.ContainsKey("trade_risk"));
Assert.IsTrue(result.RiskMetrics.ContainsKey("daily_pnl"));
}
[TestMethod]
public void ValidateOrder_ExceedsDailyLimit_ShouldReject()
{
// Arrange
var intent = TestDataBuilder.CreateValidIntent();
var context = TestDataBuilder.CreateTestContext();
var config = new RiskConfig(
dailyLossLimit: 1000,
maxTradeRisk: 500,
maxOpenPositions: 5,
emergencyFlattenEnabled: true
);
// Simulate daily loss exceeding limit
_riskManager.OnPnLUpdate(0, -1001);
// Act
var result = _riskManager.ValidateOrder(intent, context, config);
// Assert
Assert.IsFalse(result.Allow);
// Accept either "Trading halted" or "Daily loss limit" as valid rejection reasons
Assert.IsTrue(result.RejectReason.Contains("Trading halted") || result.RejectReason.Contains("Daily loss limit breached"),
"Expected reject reason to contain either 'Trading halted' or 'Daily loss limit breached', but got: " + result.RejectReason);
Assert.AreEqual(RiskLevel.Critical, result.RiskLevel);
Assert.AreEqual(-1001.0, result.RiskMetrics["daily_pnl"]);
}
[TestMethod]
public void ValidateOrder_ExceedsTradeRisk_ShouldReject()
{
// Arrange
var intent = TestDataBuilder.CreateValidIntent(stopTicks: 100); // High risk trade
var context = TestDataBuilder.CreateTestContext();
var config = new RiskConfig(
dailyLossLimit: 10000,
maxTradeRisk: 500, // Lower than calculated trade risk
maxOpenPositions: 5,
emergencyFlattenEnabled: true
);
// Act
var result = _riskManager.ValidateOrder(intent, context, config);
// Assert
Assert.IsFalse(result.Allow);
// Accept either "Trading halted" or "Trade risk too high" as valid rejection reasons
Assert.IsTrue(result.RejectReason.Contains("Trading halted") || result.RejectReason.Contains("Trade risk too high"),
"Expected reject reason to contain either 'Trading halted' or 'Trade risk too high', but got: " + result.RejectReason);
Assert.AreEqual(RiskLevel.High, result.RiskLevel);
// Verify risk calculation
var expectedRisk = 100 * 12.50; // 100 ticks * ES tick value
Assert.AreEqual(expectedRisk, result.RiskMetrics["trade_risk"]);
}
[TestMethod]
public void ValidateOrder_WithNullParameters_ShouldThrow()
{
// Arrange
var intent = TestDataBuilder.CreateValidIntent();
var context = TestDataBuilder.CreateTestContext();
var config = TestDataBuilder.CreateTestRiskConfig();
// Act & Assert
Assert.ThrowsException<ArgumentNullException>(() => _riskManager.ValidateOrder(null, context, config));
Assert.ThrowsException<ArgumentNullException>(() => _riskManager.ValidateOrder(intent, null, config));
Assert.ThrowsException<ArgumentNullException>(() => _riskManager.ValidateOrder(intent, context, null));
}
}
}