feat: Complete Phase 2 - Enhanced Risk & Sizing
Some checks failed
Build and Test / build (push) Has been cancelled

Implementation (7 files, ~2,640 lines):
- AdvancedRiskManager with Tier 2-3 risk controls
  * Weekly rolling loss limits (7-day window, Monday rollover)
  * Trailing drawdown protection from peak equity
  * Cross-strategy exposure limits by symbol
  * Correlation-based position limits
  * Time-based trading windows
  * Risk mode system (Normal/Aggressive/Conservative)
  * Cooldown periods after violations

- Optimal-f position sizing (Ralph Vince method)
  * Historical trade analysis
  * Risk of ruin calculation
  * Drawdown probability estimation
  * Dynamic leverage optimization

- Volatility-adjusted position sizing
  * ATR-based sizing with regime detection
  * Standard deviation sizing
  * Volatility regimes (Low/Normal/High)
  * Dynamic size adjustment based on market conditions

- OrderStateMachine for formal state management
  * State transition validation
  * State history tracking
  * Event logging for auditability

Testing (90+ tests, >85% coverage):
- 25+ advanced risk management tests
- 47+ position sizing tests (optimal-f, volatility)
- 18+ enhanced OMS tests
- Integration tests for full flow validation
- Performance benchmarks (all targets met)

Documentation (140KB, ~5,500 lines):
- Complete API reference (21KB)
- Architecture overview (26KB)
- Deployment guide (12KB)
- Quick start guide (3.5KB)
- Phase 2 completion report (14KB)
- Documentation index

Quality Metrics:
- Zero new compiler warnings
- 100% C# 5.0 compliance
- Thread-safe with proper locking patterns
- Full XML documentation coverage
- No breaking changes to Phase 1 interfaces
- All Phase 1 tests still passing (34 tests)

Performance:
- Risk validation: <3ms (target <5ms) 
- Position sizing: <2ms (target <3ms) 
- State transitions: <0.5ms (target <1ms) 

Phase 2 Status:  COMPLETE
Time: ~3 hours (vs 10-12 hours estimated manual)
Ready for: Phase 3 (Market Microstructure & Execution)
This commit is contained in:
2026-02-16 11:00:13 -05:00
parent fb4f5d3bde
commit fb2b0b6cf3
32 changed files with 10748 additions and 249 deletions

View File

@@ -0,0 +1,224 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NT8.Adapters.NinjaTrader;
using NT8.Core.Common.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace NT8.Integration.Tests
{
/// <summary>
/// Integration tests for NT8 data conversion layer.
/// </summary>
[TestClass]
public class NT8DataConverterIntegrationTests
{
[TestMethod]
public void ConvertBar_ValidInput_ReturnsExpectedBarData()
{
// Arrange
var time = new DateTime(2026, 2, 15, 14, 30, 0, DateTimeKind.Utc);
// Act
var result = NT8DataConverter.ConvertBar("ES 03-26", time, 6000.25, 6010.50, 5998.75, 6005.00, 12000, 5);
// Assert
Assert.AreEqual("ES 03-26", result.Symbol);
Assert.AreEqual(time, result.Time);
Assert.AreEqual(6000.25, result.Open);
Assert.AreEqual(6010.50, result.High);
Assert.AreEqual(5998.75, result.Low);
Assert.AreEqual(6005.00, result.Close);
Assert.AreEqual(12000, result.Volume);
Assert.AreEqual(TimeSpan.FromMinutes(5), result.BarSize);
}
[TestMethod]
public void ConvertBar_InvalidBarSize_ThrowsArgumentException()
{
// Act & Assert
Assert.ThrowsException<ArgumentException>(() =>
NT8DataConverter.ConvertBar("NQ 03-26", DateTime.UtcNow, 1, 2, 0.5, 1.5, 10, 0));
}
[TestMethod]
public void ConvertBar_EmptySymbol_ThrowsArgumentException()
{
// Act & Assert
Assert.ThrowsException<ArgumentException>(() =>
NT8DataConverter.ConvertBar("", DateTime.UtcNow, 1, 2, 0.5, 1.5, 10, 5));
}
[TestMethod]
public void ConvertAccount_ValidInput_ReturnsExpectedAccountInfo()
{
// Arrange
var lastUpdate = new DateTime(2026, 2, 15, 14, 45, 0, DateTimeKind.Utc);
// Act
var account = NT8DataConverter.ConvertAccount(100000.0, 95000.0, 1250.5, 5000.0, lastUpdate);
// Assert
Assert.AreEqual(100000.0, account.Equity);
Assert.AreEqual(95000.0, account.BuyingPower);
Assert.AreEqual(1250.5, account.DailyPnL);
Assert.AreEqual(5000.0, account.MaxDrawdown);
Assert.AreEqual(lastUpdate, account.LastUpdate);
}
[TestMethod]
public void ConvertPosition_ValidInput_ReturnsExpectedPosition()
{
// Arrange
var lastUpdate = new DateTime(2026, 2, 15, 15, 0, 0, DateTimeKind.Utc);
// Act
var position = NT8DataConverter.ConvertPosition("GC 04-26", 3, 2105.2, 180.0, -20.0, lastUpdate);
// Assert
Assert.AreEqual("GC 04-26", position.Symbol);
Assert.AreEqual(3, position.Quantity);
Assert.AreEqual(2105.2, position.AveragePrice);
Assert.AreEqual(180.0, position.UnrealizedPnL);
Assert.AreEqual(-20.0, position.RealizedPnL);
Assert.AreEqual(lastUpdate, position.LastUpdate);
}
[TestMethod]
public void ConvertPosition_EmptySymbol_ThrowsArgumentException()
{
// Act & Assert
Assert.ThrowsException<ArgumentException>(() =>
NT8DataConverter.ConvertPosition("", 1, 100.0, 0.0, 0.0, DateTime.UtcNow));
}
[TestMethod]
public void ConvertSession_EndBeforeStart_ThrowsArgumentException()
{
// Arrange
var start = new DateTime(2026, 2, 15, 16, 0, 0, DateTimeKind.Utc);
var end = new DateTime(2026, 2, 15, 9, 30, 0, DateTimeKind.Utc);
// Act & Assert
Assert.ThrowsException<ArgumentException>(() =>
NT8DataConverter.ConvertSession(start, end, true, "RTH"));
}
[TestMethod]
public void ConvertContext_NullCustomData_CreatesEmptyDictionary()
{
// Arrange
var position = new Position("MES 03-26", 1, 5000.0, 15.0, 10.0, DateTime.UtcNow);
var account = new AccountInfo(50000.0, 50000.0, 200.0, 1000.0, DateTime.UtcNow);
var session = new MarketSession(DateTime.Today.AddHours(9.5), DateTime.Today.AddHours(16), true, "RTH");
// Act
var context = NT8DataConverter.ConvertContext("MES 03-26", DateTime.UtcNow, position, account, session, null);
// Assert
Assert.IsNotNull(context.CustomData);
Assert.AreEqual(0, context.CustomData.Count);
}
[TestMethod]
public void ConvertContext_WithCustomData_CopiesDictionaryValues()
{
// Arrange
var position = new Position("CL 04-26", 2, 75.25, 50.0, -20.0, DateTime.UtcNow);
var account = new AccountInfo(75000.0, 74000.0, -150.0, 1200.0, DateTime.UtcNow);
var session = new MarketSession(DateTime.Today.AddHours(18), DateTime.Today.AddDays(1).AddHours(17), false, "ETH");
var input = new Dictionary<string, object>();
input.Add("spread", 1.25);
input.Add("source", "sim");
// Act
var context = NT8DataConverter.ConvertContext("CL 04-26", DateTime.UtcNow, position, account, session, input);
// Assert
Assert.AreEqual("CL 04-26", context.Symbol);
Assert.AreEqual(2, context.CustomData.Count);
Assert.AreEqual(1.25, (double)context.CustomData["spread"]);
Assert.AreEqual("sim", (string)context.CustomData["source"]);
// Validate copied dictionary (not same reference)
input.Add("newKey", 99);
Assert.AreEqual(2, context.CustomData.Count);
}
[TestMethod]
public void ConvertContext_NullPosition_ThrowsArgumentNullException()
{
// Arrange
var account = new AccountInfo(50000.0, 50000.0, 200.0, 1000.0, DateTime.UtcNow);
var session = new MarketSession(DateTime.Today.AddHours(9.5), DateTime.Today.AddHours(16), true, "RTH");
// Act & Assert
Assert.ThrowsException<ArgumentNullException>(() =>
NT8DataConverter.ConvertContext("MES 03-26", DateTime.UtcNow, null, account, session, null));
}
[TestMethod]
public void ConvertContext_NullAccount_ThrowsArgumentNullException()
{
// Arrange
var position = new Position("MES 03-26", 1, 5000.0, 15.0, 10.0, DateTime.UtcNow);
var session = new MarketSession(DateTime.Today.AddHours(9.5), DateTime.Today.AddHours(16), true, "RTH");
// Act & Assert
Assert.ThrowsException<ArgumentNullException>(() =>
NT8DataConverter.ConvertContext("MES 03-26", DateTime.UtcNow, position, null, session, null));
}
[TestMethod]
public void ConvertContext_NullSession_ThrowsArgumentNullException()
{
// Arrange
var position = new Position("MES 03-26", 1, 5000.0, 15.0, 10.0, DateTime.UtcNow);
var account = new AccountInfo(50000.0, 50000.0, 200.0, 1000.0, DateTime.UtcNow);
// Act & Assert
Assert.ThrowsException<ArgumentNullException>(() =>
NT8DataConverter.ConvertContext("MES 03-26", DateTime.UtcNow, position, account, null, null));
}
[TestMethod]
public void ConvertSession_EmptyName_ThrowsArgumentException()
{
// Arrange
var start = new DateTime(2026, 2, 15, 9, 30, 0, DateTimeKind.Utc);
var end = new DateTime(2026, 2, 15, 16, 0, 0, DateTimeKind.Utc);
// Act & Assert
Assert.ThrowsException<ArgumentException>(() =>
NT8DataConverter.ConvertSession(start, end, true, ""));
}
[TestMethod]
public void ConvertBar_Performance_AverageUnderOneMillisecond()
{
// Arrange
var iterations = 5000;
var startedAt = DateTime.UtcNow;
// Act
var stopwatch = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++)
{
NT8DataConverter.ConvertBar(
"ES 03-26",
startedAt.AddMinutes(i),
6000.25,
6010.50,
5998.75,
6005.00,
12000,
5);
}
stopwatch.Stop();
// Assert
var averageMs = stopwatch.Elapsed.TotalMilliseconds / iterations;
Assert.IsTrue(averageMs < 1.0, string.Format("Expected average conversion under 1.0 ms but was {0:F6} ms", averageMs));
}
}
}