Add ORB confluence factors (NR4/NR7, gap alignment, breakout volume, prior close) + session file logger
Some checks failed
Build and Test / build (push) Has been cancelled

This commit is contained in:
2026-03-19 12:16:39 -04:00
parent ee4da1b607
commit 498f298975
11 changed files with 1569 additions and 76 deletions

View File

@@ -0,0 +1,299 @@
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NT8.Core.Common.Models;
using NT8.Core.Intelligence;
using NT8.Core.Logging;
namespace NT8.Core.Tests.Intelligence
{
[TestClass]
public class OrbConfluenceFactorTests
{
[TestMethod]
public void NarrowRange_NR7_ScoresOne()
{
var calc = new NarrowRangeFactorCalculator(new BasicLogger("test"));
var intent = CreateIntent();
intent.Metadata["daily_bars"] = CreateDailyContext(new double[] { 10, 10, 10, 10, 10, 10, 5 });
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.AreEqual(1.0, result.Score, 0.000001);
}
[TestMethod]
public void NarrowRange_NR4_Scores075()
{
var calc = new NarrowRangeFactorCalculator(new BasicLogger("test"));
var intent = CreateIntent();
intent.Metadata["daily_bars"] = CreateDailyContext(new double[] { 5, 5, 5, 10, 9, 8, 7 });
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.AreEqual(0.75, result.Score, 0.000001);
}
[TestMethod]
public void NarrowRange_WideRange_ScoresLow()
{
var calc = new NarrowRangeFactorCalculator(new BasicLogger("test"));
var intent = CreateIntent();
intent.Metadata["daily_bars"] = CreateDailyContext(new double[] { 5, 5, 5, 5, 5, 5, 12 });
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.IsTrue(result.Score <= 0.3);
}
[TestMethod]
public void NarrowRange_MissingContext_DefaultsTo03()
{
var calc = new NarrowRangeFactorCalculator(new BasicLogger("test"));
var intent = CreateIntent();
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.AreEqual(0.3, result.Score, 0.000001);
}
[TestMethod]
public void NarrowRange_InsufficientBars_DefaultsTo03()
{
var calc = new NarrowRangeFactorCalculator(new BasicLogger("test"));
var intent = CreateIntent();
intent.Metadata["daily_bars"] = CreateDailyContext(new double[] { 8, 7, 6, 5 });
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.AreEqual(0.3, result.Score, 0.000001);
}
[TestMethod]
public void OrbRangeVsAtr_SmallRange_ScoresOne()
{
var calc = new OrbRangeVsAtrFactorCalculator(new BasicLogger("test"));
var intent = CreateIntent();
var daily = CreateDailyContext(new double[] { 10, 10, 10, 10, 10, 10, 10 });
intent.Metadata["daily_bars"] = daily;
intent.Metadata["orb_range_ticks"] = 8.0;
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.AreEqual(1.0, result.Score, 0.000001);
}
[TestMethod]
public void OrbRangeVsAtr_LargeRange_ScoresVeryLow()
{
var calc = new OrbRangeVsAtrFactorCalculator(new BasicLogger("test"));
var intent = CreateIntent();
var daily = CreateDailyContext(new double[] { 10, 10, 10, 10, 10, 10, 10 });
intent.Metadata["daily_bars"] = daily;
intent.Metadata["orb_range_ticks"] = 40.0;
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.IsTrue(result.Score <= 0.15);
}
[TestMethod]
public void OrbRangeVsAtr_MissingContext_DefaultsTo05()
{
var calc = new OrbRangeVsAtrFactorCalculator(new BasicLogger("test"));
var intent = CreateIntent();
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.AreEqual(0.5, result.Score, 0.000001);
}
[TestMethod]
public void GapDirection_LargeAlignedGap_ScoresOne()
{
var calc = new GapDirectionAlignmentCalculator(new BasicLogger("test"));
var intent = CreateIntent();
var daily = CreateDailyContext(new double[] { 8, 8, 8, 8, 8, 8, 8 });
daily.Closes[daily.Count - 2] = 100.0;
daily.TodayOpen = 106.0;
daily.TradeDirection = 1;
intent.Metadata["daily_bars"] = daily;
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.AreEqual(1.0, result.Score, 0.000001);
}
[TestMethod]
public void GapDirection_LargeOpposingGap_ScoresVeryLow()
{
var calc = new GapDirectionAlignmentCalculator(new BasicLogger("test"));
var intent = CreateIntent();
var daily = CreateDailyContext(new double[] { 8, 8, 8, 8, 8, 8, 8 });
daily.Closes[daily.Count - 2] = 100.0;
daily.TodayOpen = 106.0;
daily.TradeDirection = -1;
intent.Metadata["daily_bars"] = daily;
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.IsTrue(result.Score <= 0.15);
}
[TestMethod]
public void GapDirection_FlatOpen_ScoresNeutral()
{
var calc = new GapDirectionAlignmentCalculator(new BasicLogger("test"));
var intent = CreateIntent();
var daily = CreateDailyContext(new double[] { 8, 8, 8, 8, 8, 8, 8 });
daily.Closes[daily.Count - 2] = 100.0;
daily.TodayOpen = 100.1;
daily.TradeDirection = 1;
intent.Metadata["daily_bars"] = daily;
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.AreEqual(0.55, result.Score, 0.000001);
}
[TestMethod]
public void BreakoutVolume_ThreeX_ScoresOne()
{
var calc = new BreakoutVolumeStrengthCalculator(new BasicLogger("test"));
var intent = CreateIntent();
var daily = CreateDailyContext(new double[] { 8, 8, 8, 8, 8, 8, 8 });
daily.BreakoutBarVolume = 3000.0;
daily.AvgIntradayBarVolume = 1000.0;
intent.Metadata["daily_bars"] = daily;
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.AreEqual(1.0, result.Score, 0.000001);
}
[TestMethod]
public void BreakoutVolume_BelowAverage_ScoresLow()
{
var calc = new BreakoutVolumeStrengthCalculator(new BasicLogger("test"));
var intent = CreateIntent();
var daily = CreateDailyContext(new double[] { 8, 8, 8, 8, 8, 8, 8 });
daily.BreakoutBarVolume = 800.0;
daily.AvgIntradayBarVolume = 1200.0;
intent.Metadata["daily_bars"] = daily;
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.IsTrue(result.Score <= 0.25);
}
[TestMethod]
public void PriorCloseStrength_LongTopQuartile_ScoresOne()
{
var calc = new PriorDayCloseStrengthCalculator(new BasicLogger("test"));
var intent = CreateIntent();
var daily = CreateDailyContext(new double[] { 8, 8, 8, 8, 8, 8, 8 });
int prev = daily.Count - 2;
daily.Lows[prev] = 100.0;
daily.Highs[prev] = 120.0;
daily.Closes[prev] = 118.0;
daily.TradeDirection = 1;
intent.Metadata["daily_bars"] = daily;
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.AreEqual(1.0, result.Score, 0.000001);
}
[TestMethod]
public void PriorCloseStrength_LongBottomQuartile_ScoresLow()
{
var calc = new PriorDayCloseStrengthCalculator(new BasicLogger("test"));
var intent = CreateIntent();
var daily = CreateDailyContext(new double[] { 8, 8, 8, 8, 8, 8, 8 });
int prev = daily.Count - 2;
daily.Lows[prev] = 100.0;
daily.Highs[prev] = 120.0;
daily.Closes[prev] = 101.0;
daily.TradeDirection = 1;
intent.Metadata["daily_bars"] = daily;
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.IsTrue(result.Score <= 0.20);
}
[TestMethod]
public void PriorCloseStrength_ShortBottomQuartile_ScoresOne()
{
var calc = new PriorDayCloseStrengthCalculator(new BasicLogger("test"));
var intent = CreateIntent();
var daily = CreateDailyContext(new double[] { 8, 8, 8, 8, 8, 8, 8 });
int prev = daily.Count - 2;
daily.Lows[prev] = 100.0;
daily.Highs[prev] = 120.0;
daily.Closes[prev] = 101.0;
daily.TradeDirection = -1;
intent.Metadata["daily_bars"] = daily;
var result = calc.Calculate(intent, CreateContext(), CreateBar());
Assert.AreEqual(1.0, result.Score, 0.000001);
}
private static StrategyIntent CreateIntent()
{
return new StrategyIntent(
"ES",
OrderSide.Buy,
OrderType.Market,
null,
8,
16,
0.8,
"test",
new Dictionary<string, object>());
}
private static StrategyContext CreateContext()
{
return new StrategyContext(
"ES",
DateTime.UtcNow,
new Position("ES", 0, 0, 0, 0, DateTime.UtcNow),
new AccountInfo(100000, 100000, 0, 0, DateTime.UtcNow),
new MarketSession(DateTime.Today.AddHours(9.5), DateTime.Today.AddHours(16), true, "RTH"),
new Dictionary<string, object>());
}
private static BarData CreateBar()
{
return new BarData("ES", DateTime.UtcNow, 5000, 5005, 4998, 5002, 1000, TimeSpan.FromMinutes(5));
}
private static DailyBarContext CreateDailyContext(double[] ranges)
{
DailyBarContext context = new DailyBarContext();
context.Count = ranges.Length;
context.Highs = new double[ranges.Length];
context.Lows = new double[ranges.Length];
context.Closes = new double[ranges.Length];
context.Opens = new double[ranges.Length];
context.Volumes = new long[ranges.Length];
for (int i = 0; i < ranges.Length; i++)
{
context.Lows[i] = 100.0;
context.Highs[i] = 100.0 + ranges[i];
context.Opens[i] = 100.0 + (ranges[i] * 0.25);
context.Closes[i] = 100.0 + (ranges[i] * 0.75);
context.Volumes[i] = 100000;
}
context.TodayOpen = context.Closes[Math.Max(0, context.Count - 2)] + 1.0;
context.BreakoutBarVolume = 1000.0;
context.AvgIntradayBarVolume = 1000.0;
context.TradeDirection = 1;
return context;
}
}
}

View File

@@ -15,11 +15,20 @@ namespace NT8.Integration.Tests
[TestClass]
public class NT8OrderAdapterIntegrationTests
{
private class FakeBridge : INT8ExecutionBridge
{
public void EnterLongManaged(int q, string n, int s, int t, double ts) { }
public void EnterShortManaged(int q, string n, int s, int t, double ts) { }
public void ExitLongManaged(string n) { }
public void ExitShortManaged(string n) { }
public void FlattenAll() { }
}
[TestMethod]
public void Initialize_NullRiskManager_ThrowsArgumentNullException()
{
// Arrange
var adapter = new NT8OrderAdapter();
var adapter = new NT8OrderAdapter(new FakeBridge());
var sizer = new TestPositionSizer(1);
// Act / Assert
@@ -31,7 +40,7 @@ namespace NT8.Integration.Tests
public void Initialize_NullPositionSizer_ThrowsArgumentNullException()
{
// Arrange
var adapter = new NT8OrderAdapter();
var adapter = new NT8OrderAdapter(new FakeBridge());
var risk = new TestRiskManager(true);
// Act / Assert
@@ -43,7 +52,7 @@ namespace NT8.Integration.Tests
public void ExecuteIntent_NotInitialized_ThrowsInvalidOperationException()
{
// Arrange
var adapter = new NT8OrderAdapter();
var adapter = new NT8OrderAdapter(new FakeBridge());
// Act / Assert
Assert.ThrowsException<InvalidOperationException>(
@@ -54,7 +63,7 @@ namespace NT8.Integration.Tests
public void ExecuteIntent_RiskRejected_DoesNotRecordExecution()
{
// Arrange
var adapter = new NT8OrderAdapter();
var adapter = new NT8OrderAdapter(new FakeBridge());
var risk = new TestRiskManager(false);
var sizer = new TestPositionSizer(3);
adapter.Initialize(risk, sizer);
@@ -71,7 +80,7 @@ namespace NT8.Integration.Tests
public void ExecuteIntent_AllowedAndSized_RecordsExecution()
{
// Arrange
var adapter = new NT8OrderAdapter();
var adapter = new NT8OrderAdapter(new FakeBridge());
var risk = new TestRiskManager(true);
var sizer = new TestPositionSizer(4);
adapter.Initialize(risk, sizer);
@@ -94,7 +103,7 @@ namespace NT8.Integration.Tests
public void GetExecutionHistory_ReturnsCopy_NotMutableInternalReference()
{
// Arrange
var adapter = new NT8OrderAdapter();
var adapter = new NT8OrderAdapter(new FakeBridge());
var risk = new TestRiskManager(true);
var sizer = new TestPositionSizer(2);
adapter.Initialize(risk, sizer);
@@ -113,7 +122,7 @@ namespace NT8.Integration.Tests
public void OnOrderUpdate_EmptyOrderId_ThrowsArgumentException()
{
// Arrange
var adapter = new NT8OrderAdapter();
var adapter = new NT8OrderAdapter(new FakeBridge());
// Act / Assert
Assert.ThrowsException<ArgumentException>(
@@ -124,7 +133,7 @@ namespace NT8.Integration.Tests
public void OnExecutionUpdate_EmptyExecutionId_ThrowsArgumentException()
{
// Arrange
var adapter = new NT8OrderAdapter();
var adapter = new NT8OrderAdapter(new FakeBridge());
// Act / Assert
Assert.ThrowsException<ArgumentException>(
@@ -135,7 +144,7 @@ namespace NT8.Integration.Tests
public void OnExecutionUpdate_EmptyOrderId_ThrowsArgumentException()
{
// Arrange
var adapter = new NT8OrderAdapter();
var adapter = new NT8OrderAdapter(new FakeBridge());
// Act / Assert
Assert.ThrowsException<ArgumentException>(