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>
45 lines
1.7 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|