Initial commit: Add project files
ci / backend (push) Failing after 12s
ci / frontend (push) Failing after 19s
ci / static (push) Failing after 45s

This commit is contained in:
2026-08-02 05:15:36 +09:00
commit dcd1322d41
636 changed files with 122352 additions and 0 deletions
@@ -0,0 +1,29 @@
using KArtSell.BuildingBlocks.DataIntegrity;
namespace KArtSell.SignalEngine.UnitTests.BuildingBlocks;
public sealed class PitRecordMetadataTests
{
[Fact]
public void Quarantined_record_is_never_usable()
{
var published = new DateTimeOffset(2026, 1, 2, 0, 0, 0, TimeSpan.Zero);
var record = new PitRecordMetadata(
"source-1", published, published, published.AddMinutes(1), published, null,
0, "hash", "dataset", DataQualityStatus.Quarantined);
Assert.False(record.IsUsableAt(published.AddDays(1)));
}
[Fact]
public void Pass_record_is_usable_only_after_publication()
{
var published = new DateTimeOffset(2026, 1, 2, 0, 0, 0, TimeSpan.Zero);
var record = new PitRecordMetadata(
"source-1", published, published, published.AddMinutes(1), published, null,
0, "hash", "dataset", DataQualityStatus.Pass);
Assert.False(record.IsUsableAt(published.AddTicks(-1)));
Assert.True(record.IsUsableAt(published));
}
}
@@ -0,0 +1,13 @@
using KArtSell.BuildingBlocks.Versioning;
namespace KArtSell.SignalEngine.UnitTests.BuildingBlocks;
public sealed class VersionSetTests
{
[Fact]
public void Blank_version_component_is_rejected()
{
var value = new VersionSet("dataset", "data", "model", "config", "", "contract");
Assert.Throws<ArgumentException>(value.EnsureValid);
}
}
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../src/KArtSell.Modules.SignalEngine/KArtSell.Modules.SignalEngine.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" PrivateAssets="all" />
</ItemGroup>
</Project>
@@ -0,0 +1,64 @@
using KArtSell.Modules.SignalEngine.Domain;
namespace KArtSell.SignalEngine.UnitTests;
public sealed class ReentryStateMachineTests
{
[Fact]
public void Reentry_requires_wait_spacing_trend_breakout_and_asset_confirmation()
{
var state = ReentryStateMachine.Evaluate(
ReentryState.Watching,
new ReentryInput(10, 10, true, true, true, false, false));
Assert.Equal(ReentryState.Ready, state);
}
[Fact]
public void Missing_asset_confirmation_keeps_watch_open()
{
var state = ReentryStateMachine.Evaluate(
ReentryState.Watching,
new ReentryInput(10, 10, true, true, false, false, false));
Assert.Equal(ReentryState.Watching, state);
}
[Fact]
public void Executed_stage_moves_to_reentered_then_watching_when_stages_remain()
{
var reentered = ReentryStateMachine.Evaluate(
ReentryState.Ready,
new ReentryInput(10, 10, true, true, true, false, false,
StageExecuted: true, HasRemainingStages: true));
var next = ReentryStateMachine.Evaluate(
reentered,
new ReentryInput(10, 0, true, true, true, false, false,
HasRemainingStages: true));
Assert.Equal(ReentryState.Reentered, reentered);
Assert.Equal(ReentryState.Watching, next);
}
[Fact]
public void Last_executed_stage_moves_to_open()
{
var next = ReentryStateMachine.Evaluate(
ReentryState.Reentered,
new ReentryInput(20, 10, true, true, true, false, false,
HasRemainingStages: false));
Assert.Equal(ReentryState.Open, next);
}
[Fact]
public void Hard_impairment_closes_watch()
{
var state = ReentryStateMachine.Evaluate(
ReentryState.Watching,
new ReentryInput(100, 100, true, true, true, true, false));
Assert.Equal(ReentryState.Closed, state);
}
}
@@ -0,0 +1,10 @@
using KArtSell.Modules.SignalEngine.Domain;
namespace KArtSell.SignalEngine.UnitTests;
public sealed class SellDecisionEvidenceGuardTests
{
[Fact] public void Rejects_lookahead_and_unit_confusion() {
var now=DateTimeOffset.UtcNow;
Assert.Throws<InvalidOperationException>(()=>SellDecisionEvidenceGuard.EnsureValid(new("e","d","m","c","sha",now,now.AddMinutes(1),.2m,.3m,.1m,.2m)));
Assert.Throws<ArgumentOutOfRangeException>(()=>SellDecisionEvidenceGuard.EnsureValid(new("e","d","m","c","sha",now,now,20m,.3m,.1m,.2m)));
}
}
@@ -0,0 +1,167 @@
using KArtSell.Modules.SignalEngine.Domain;
using KArtSell.Modules.SignalEngine.Domain.Policies;
namespace KArtSell.SignalEngine.UnitTests;
public sealed class SellPolicyChainTests
{
private static SellPolicyChain CreateSut() => new(new ISellPolicy[]
{
new OpportunityCostPolicy(),
new ConcentrationLiquidityPolicy(),
new TwoCloseFloorBreachPolicy(),
new GapFloorBreachPolicy(),
new PortfolioSurvivalPolicy(),
new HardImpairmentPolicy()
});
[Fact]
public void Hard_impairment_cannot_be_overridden_by_lower_priority_policy()
{
var input = BaseInput() with
{
HardImpairmentApproved = true,
CapitalFloorBreached = true,
GapBelowFloorAtr = 2.0m,
ConsecutiveCloseBreaches = 3,
OpportunityEdgeLowerBound = 0.10m,
OpportunitySellRatioOfLot = 0.25m
};
var result = CreateSut().Evaluate(input);
Assert.Equal("ALG-SELL-001", result.PolicyId);
Assert.Equal(SellAction.FullSell, result.Action);
Assert.Equal(1m, result.SellRatioOfLot);
Assert.False(result.ReentryEligible);
Assert.Single(result.PolicyTrace);
Assert.Equal(PolicyDisposition.Applied, result.PolicyTrace[0].Disposition);
}
[Fact]
public void Portfolio_survival_outranks_profit_floor_and_may_cross_strategic_core()
{
var input = BaseInput() with
{
CurrentSecurityPortfolioWeight = 0.40m,
CurrentLotPortfolioWeight = 0.20m,
StrategicCoreFloorWeight = 0.30m,
CapitalFloorBreached = true,
SurvivalSellRatioOfLot = 0.75m,
GapBelowFloorAtr = 2.0m
};
var result = CreateSut().Evaluate(input);
Assert.Equal("ALG-SELL-PORT-001", result.PolicyId);
Assert.Equal(0.75m, result.SellRatioOfLot);
Assert.Equal(0.25m, result.TargetSecurityPortfolioWeightAfter);
}
[Fact]
public void Lot_relative_ratio_uses_lot_weight_not_whole_security_weight()
{
var input = BaseInput() with
{
CurrentSecurityPortfolioWeight = 0.60m,
CurrentLotPortfolioWeight = 0.20m,
StrategicCoreFloorWeight = 0.50m,
GapBelowFloorAtr = 2.0m
};
var result = CreateSut().Evaluate(input);
Assert.Equal("ALG-SELL-002", result.PolicyId);
Assert.Equal(0.40m, result.SellRatioOfLot);
Assert.Equal(0.52m, result.TargetSecurityPortfolioWeightAfter);
Assert.False(result.PolicyTrace.Single(x => x.PolicyId == "ALG-SELL-002").StrategicCoreClampApplied);
}
[Fact]
public void Strategic_core_clamps_lot_ratio_when_only_part_of_lot_is_sellable()
{
var input = BaseInput() with
{
CurrentSecurityPortfolioWeight = 0.60m,
CurrentLotPortfolioWeight = 0.40m,
StrategicCoreFloorWeight = 0.50m,
GapBelowFloorAtr = 2.0m
};
var result = CreateSut().Evaluate(input);
Assert.Equal(0.25m, result.SellRatioOfLot);
Assert.Equal(0.50m, result.TargetSecurityPortfolioWeightAfter);
Assert.True(result.PolicyTrace.Single(x => x.PolicyId == "ALG-SELL-002").StrategicCoreClampApplied);
}
[Fact]
public void Opportunity_sell_requires_positive_lower_confidence_edge()
{
var blocked = CreateSut().Evaluate(BaseInput() with
{
OpportunityEdgeLowerBound = 0m,
OpportunitySellRatioOfLot = 0.20m
});
var allowed = CreateSut().Evaluate(BaseInput() with
{
OpportunityEdgeLowerBound = 0.01m,
OpportunitySellRatioOfLot = 0.20m
});
Assert.Equal(SellAction.Hold, blocked.Action);
Assert.Equal("ALG-SELL-005", allowed.PolicyId);
Assert.Equal(0.20m, allowed.SellRatioOfLot);
}
[Fact]
public void Positive_opportunity_edge_with_zero_requested_ratio_cannot_create_a_sell()
{
var result = CreateSut().Evaluate(BaseInput() with
{
OpportunityEdgeLowerBound = 0.02m,
OpportunitySellRatioOfLot = 0m
});
Assert.Equal(SellAction.Hold, result.Action);
var trace = Assert.Single(result.PolicyTrace, x => x.PolicyId == SellPolicyContract.OpportunityCostPolicyId);
Assert.Equal(PolicyDisposition.Blocked, trace.Disposition);
Assert.Equal("OPPORTUNITY_RATIO_NOT_POSITIVE", trace.ReasonCode);
}
[Fact]
public void Future_published_evidence_is_rejected()
{
var input = BaseInput() with
{
PublishedAtCutoff = DateTimeOffset.Parse("2026-08-01T08:00:00Z"),
AsOf = DateTimeOffset.Parse("2026-08-01T07:00:00Z")
};
Assert.Throws<InvalidOperationException>(() => CreateSut().Evaluate(input));
}
private static SellDecisionInput BaseInput() => new(
Guid.Parse("00000000-0000-0000-0000-000000000001"),
Guid.Parse("00000000-0000-0000-0000-000000000002"),
"evidence-1",
"dataset-1",
"model-1",
"config-1",
"code-sha-1",
DateTimeOffset.Parse("2026-08-01T07:00:00Z"),
DateTimeOffset.Parse("2026-08-01T06:00:00Z"),
0.60m,
0.20m,
0.30m,
false,
false,
0m,
0m,
0,
true,
0m,
0m,
0m);
}
@@ -0,0 +1,27 @@
using KArtSell.Modules.SignalEngine.Domain;
namespace KArtSell.SignalEngine.UnitTests;
public sealed class SellPolicyContractTests
{
[Fact]
public void Policy_ids_and_priorities_are_unique_and_strictly_ordered()
{
var definitions = SellPolicyContract.Definitions;
Assert.Equal(definitions.Count, definitions.Select(x => x.PolicyId).Distinct().Count());
Assert.Equal(definitions.Count, definitions.Select(x => x.Priority).Distinct().Count());
Assert.True(definitions.Zip(definitions.Skip(1), (left, right) => left.Priority > right.Priority).All(x => x));
}
[Fact]
public void Approved_ratios_and_thresholds_remain_within_documented_ranges()
{
Assert.InRange(SellPolicyContract.HardImpairmentSellRatioOfLot, 0.80m, 1.00m);
Assert.InRange(SellPolicyContract.GapFloorSellRatioOfLot, 0.30m, 0.50m);
Assert.InRange(SellPolicyContract.TwoCloseSellRatioOfLot, 0.15m, 0.25m);
Assert.InRange(SellPolicyContract.OpportunityMinimumSellRatioOfLot, 0.10m, 0.25m);
Assert.InRange(SellPolicyContract.OpportunityMaximumSellRatioOfLot, 0.10m, 0.25m);
Assert.True(SellPolicyContract.OpportunityMinimumSellRatioOfLot <= SellPolicyContract.OpportunityMaximumSellRatioOfLot);
}
}