Files
RichardandClaude Opus 4.8 1c3a364df2 RF-Slice 5: Demo-Execution + Resolution-Monitor (Phase RF-2)
Schliesst die Demo-Handelsschleife des ResolutionFarming:
- FarmingExecutionPlanner (pur): waehlt aus akzeptierten Kandidaten die zu oeffnenden
  Positionen + Groesse, priorisiert nach Score, unter Markt-/Cluster-/Gesamt-Limits,
  Kill-Switch und Tages-Drossel; dedupliziert Token, schreibt Exposure im Lauf fort.
- FarmingResolution (pur): baut aus Position + Ergebnis den RfClosedTrade (PnL/Fees/Redeem-Status).
- FarmingExecutionService: Demo-Einstieg (Maker-Fill 0 Fee via FarmingFillModel) -> rf_positions.
  Live-Execution bewusst geloggt/uebersprungen (Zielland). Demo-Balance NICHT mutiert
  (kein Shared-Account-Konflikt; PnL fliesst ueber rf_closed_trades).
- FarmingResolutionMonitorService: schliesst aufgeloeste Positionen, bucht GlobalPnl,
  Dual-Write ins Core-Trade-Log (Dashboard). Auflösungsstatus via IMarketResolutionSource.
- NullMarketResolutionSource als Default (nichts loest auf), bis Live-Data-API verdrahtet ist.

15 neue Tests (Planner 8, Resolution 3, Execution-Service 2, Monitor 2). Build 0 Fehler,
311 Tests gruen, --smoke-ui ok.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 11:40:30 +02:00

45 lines
1.7 KiB
C#

using System;
using PolyTrader.Modules.ResolutionFarming.Logic;
using PolyTrader.Modules.ResolutionFarming.Models;
using Xunit;
namespace PolyTrader.Tests
{
/// <summary>Sicherheitsnetz für den reinen Positions-Abschluss bei Auflösung (PnL, Fees, Redeem-Status).</summary>
public class FarmingResolutionTests
{
private static RfPosition Pos(int feeBps = 0) => new()
{
AccountId = 1, TokenId = "a", MarketQuestion = "Q", Size = 100m, EntryPrice = 0.95m,
AmountUsd = 95m, EntryFeeBps = feeBps, IsDemo = true, OpenedAt = DateTime.UtcNow.AddHours(-5)
};
[Fact]
public void Winner_maker_pays_out_one_per_share()
{
var t = FarmingResolution.BuildClosedTrade(Pos(), isWinner: true, DateTime.UtcNow);
Assert.Equal(1.0m, t.ExitPrice);
Assert.Equal(5m, t.RealizedPnl); // 100 - 95 - 0
Assert.Equal(0m, t.TotalFees);
Assert.Equal("Pending", t.RedeemStatus); // Gewinner müssen redeemt werden
}
[Fact]
public void Loser_loses_entry_cost()
{
var t = FarmingResolution.BuildClosedTrade(Pos(), isWinner: false, DateTime.UtcNow);
Assert.Equal(0.0m, t.ExitPrice);
Assert.Equal(-95m, t.RealizedPnl);
Assert.Equal("None", t.RedeemStatus);
}
[Fact]
public void Taker_entry_fee_reduces_winner_pnl()
{
var t = FarmingResolution.BuildClosedTrade(Pos(feeBps: 100), isWinner: true, DateTime.UtcNow);
Assert.Equal(4.05m, t.RealizedPnl); // 100 - 95 - 0.95
Assert.Equal(0.95m, t.TotalFees);
}
}
}