RF-Slice 1: ResolutionFarming-Modul-Skelett + geldkritische pure Logik + Tests
Neues Strategiemodul PolyTrader.Modules.ResolutionFarming (IPolyTraderModule, Name=ResolutionFarming, DbPrefix=rf_), in Solution/App/Tests + beide Modul-Listen in Program.cs eingebunden. Skelett laedt (RegisterServices/UI noch no-op). Geldkritische Entscheidungslogik pur und vollstaendig unit-getestet: - FarmingRiskEngine: Netto-Edge nach Fees (NetEdge/NetEdgePct/HasEdge), Positionsgroesse unter Markt-/Cluster-/Gesamt-Exposure-Limits (AllowedPositionUsd), Kill-Switch, Tages-Drossel. - FarmingScanner: Preisband, Kategorie-Whitelist, Blacklist, Cluster-Key (korrelierte Favoriten teilen einen Cluster), Kandidaten-Score. - FarmingFillModel: Shares fuer Budget (2-Dezimal-Floor), Resolve-PnL (Auszahlung - Kosten - Entry-Fee, Maker/Taker), Einstiegskosten inkl. Fee. Nutzt Core.FeeModel. - RfSettings (rf_settings) mit konservativen Defaults + PropertyGrid-Attributen. 39 neue Tests. Build 0 Fehler, 283 Tests gruen, --smoke-ui ok. Persistenz (DbContext/Migration/Repos), Scanner-/Monitor-Jobs, Execution und UI folgen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2f5270a2f3
commit
c9eb11afe4
@@ -0,0 +1,60 @@
|
||||
using PolyTrader.Modules.ResolutionFarming.Logic;
|
||||
using Xunit;
|
||||
using static PolyTrader.Modules.ResolutionFarming.Logic.FarmingScanner;
|
||||
|
||||
namespace PolyTrader.Tests
|
||||
{
|
||||
/// <summary>Sicherheitsnetz für die Scanner-Filter (Preisband, Kategorie, Blacklist, Cluster-Key, Score).</summary>
|
||||
public class FarmingScannerTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(0.90, 0.90, 0.98, true)] // untere Grenze inkl.
|
||||
[InlineData(0.98, 0.90, 0.98, true)] // obere Grenze inkl.
|
||||
[InlineData(0.89, 0.90, 0.98, false)]
|
||||
[InlineData(0.99, 0.90, 0.98, false)]
|
||||
public void InPriceBand_is_inclusive(double ask, double min, double max, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, InPriceBand((decimal)ask, (decimal)min, (decimal)max));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Sports", "Sports,Politics", true)]
|
||||
[InlineData("sports", "Sports,Politics", true)] // case-insensitiv
|
||||
[InlineData("Crypto", "Sports,Politics", false)]
|
||||
[InlineData("Sports", "", true)] // leere Whitelist = alles
|
||||
[InlineData("", "Sports,Politics", false)] // leere Kategorie
|
||||
public void CategoryAllowed_matches_whitelist(string category, string whitelist, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, CategoryAllowed(category, whitelist));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("us-election-dispute", null, "dispute,uma", true)]
|
||||
[InlineData("nfl-week1", "sports,nfl", "uma", false)]
|
||||
[InlineData("anything", null, "", false)] // leere Blacklist
|
||||
public void IsBlacklisted_matches_slug_or_tags(string slug, string? tags, string blacklist, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, IsBlacklisted(slug, tags, blacklist));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClusterKey_prefers_event_slug_then_market_slug()
|
||||
{
|
||||
Assert.Equal("epl-2026-03-14", ClusterKey("EPL-2026-03-14", "arsenal-win"));
|
||||
Assert.Equal("arsenal-win", ClusterKey(null, "Arsenal-Win"));
|
||||
Assert.Equal("arsenal-win", ClusterKey(" ", "arsenal-win"));
|
||||
Assert.Equal("", ClusterKey(null, null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Score_increases_with_edge_and_earlier_resolution()
|
||||
{
|
||||
// mehr Edge -> höherer Score
|
||||
Assert.True(Score(5m, 24) > Score(2m, 24));
|
||||
// frühere Auflösung -> höherer Score (Bindungsbonus)
|
||||
Assert.True(Score(3m, 1) > Score(3m, 40));
|
||||
// negativer Edge zählt als 0, aber Zeitbonus bleibt >= 0
|
||||
Assert.True(Score(-5m, 10) >= 0m);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user