Testnetz: Micro-Order-Minimum + SELL-Proportionalität extrahiert; SELL-Leiter (Phase 0.1) vorbereitet

Weiter im Muster extrahieren->testen (verhaltensneutral, clob.md):
- CopyTradingRisk.IsBelowPolymarketMinimum (Shares < 5.5 || USDC < 0.10) –
  BUY-Micro-Order-Filter verdrahtet + Tests.
- SellLogic.SellProportion (signalSize/(remaining+signalSize), Denominator<=0 -> 0)
  + Konstante MinSignificantSellRatio (0.30); SELL-Proportionalitätscheck der
  Engine ruft sie jetzt (Charakterisierung, sichert Phase-2-Umbau ab).
- SellLogic-Eskalationsleiter (Phase 0.1, NOCH NICHT verdrahtet): FirstLimit
  (HF-fest/prozentual), Floor (SellFloorPct), NextPrice (relative Stufe, auf Floor
  geclamped), IsAtFloor. Reine Grundlage fuer das spaetere SELL-Wiring.
- Tests: CopyTradingRisk-Minimum (5 Faelle) + SellLogicTests (Proportion +
  Leiter). Gesamt 154 gruen.

Build/Smoke gruen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-07 11:03:44 +02:00
co-authored by Claude Opus 4.8
parent 6a8ecbc80a
commit 581755029a
5 changed files with 175 additions and 3 deletions
+89
View File
@@ -0,0 +1,89 @@
using PolyTrader.Modules.CopyTrading.Logic;
using Xunit;
using static PolyTrader.Modules.CopyTrading.Logic.SellLogic;
namespace PolyTrader.Tests
{
/// <summary>
/// Sicherheitsnetz für die SELL-Logik: Proportionalität (aktuelles Verhalten, Phase-2-Umbau)
/// und die Eskalationsleiter (Phase 0.1 Preis-Stufen, Floor-Clamping).
/// </summary>
public class SellLogicTests
{
// ---------------- SellProportion ----------------
[Fact]
public void SellProportion_is_share_over_total_before_sell()
{
// Master hält noch 300, verkauft 200 -> 200/500 = 0.40
Assert.Equal(0.40m, SellProportion(masterSharesRemaining: 300m, signalSize: 200m));
}
[Fact]
public void SellProportion_small_partial_is_below_threshold()
{
// 498 Rest, 2 verkauft -> 0.004 < 0.30
var ratio = SellProportion(498m, 2m);
Assert.True(ratio < MinSignificantSellRatio);
}
[Fact]
public void SellProportion_full_exit_is_one()
{
Assert.Equal(1.0m, SellProportion(0m, 10m));
}
[Theory]
[InlineData(0, 0)] // gar nichts
[InlineData(-5, 5)] // Denominator 0
[InlineData(-10, 5)] // Denominator negativ
public void SellProportion_guards_non_positive_denominator(double remaining, double signal)
{
Assert.Equal(0m, SellProportion((decimal)remaining, (decimal)signal));
}
// ---------------- Eskalationsleiter ----------------
[Fact]
public void FirstLimit_normal_trader_applies_percentage_discount()
{
// 0.99 * (1 - 2%) = 0.9702
Assert.Equal(0.9702m, FirstLimit(0.99m, isHf: false, maxPriceDifferencePct: 2m));
}
[Fact]
public void FirstLimit_hf_trader_applies_fixed_half_cent_discount()
{
Assert.Equal(0.985m, FirstLimit(0.99m, isHf: true, maxPriceDifferencePct: 99m));
}
[Fact]
public void Floor_is_reference_minus_floor_pct()
{
// 0.99 * (1 - 15%) = 0.8415
Assert.Equal(0.8415m, Floor(0.99m, 15m));
}
[Fact]
public void NextPrice_steps_down_relative_when_above_floor()
{
// 0.90 * (1 - 3%) = 0.873, über Floor 0.80
Assert.Equal(0.873m, NextPrice(0.90m, stepPct: 3m, floor: 0.80m));
}
[Fact]
public void NextPrice_clamps_to_floor()
{
// 0.82 * 0.97 = 0.7954 -> unter Floor -> 0.80
Assert.Equal(0.80m, NextPrice(0.82m, 3m, 0.80m));
}
[Fact]
public void IsAtFloor_is_inclusive()
{
Assert.True(IsAtFloor(0.80m, 0.80m));
Assert.True(IsAtFloor(0.79m, 0.80m));
Assert.False(IsAtFloor(0.81m, 0.80m));
}
}
}