using PolyTrader.Modules.ResolutionFarming.Models;
using PolyTrader.Modules.ResolutionFarming.Persistence.Ef;
using PolyTrader.Modules.ResolutionFarming.Services;
using PolyTrader.Tests.Fakes;
using PolyTrader.Tests.TestSupport;
using PolyTraderSharp;
using PolyTraderSharp.Models;
using PolyTraderSharp.Services;
using Xunit;
namespace PolyTrader.Tests
{
///
/// Die Farming-Ausführung hält sich an Pause und Handelsmodi (#569). Bis dahin hielt
/// "Trading pausieren" nur CopyTrading an; Resolution Farming eröffnete weiter Positionen.
///
public class FarmingExecutionGateTests
{
private sealed record Rig(FarmingExecutionService Svc, TradingState State, EfRfPositionRepository Positions);
/// Ein freigeschaltetes Konto mit zwei angenommenen Kandidaten, die ohne Sperre beide eröffnet würden.
private static Rig Build(bool demoAccount = true)
{
var factory = new InMemoryContextFactory(o => new ResolutionFarmingDbContext(o));
var candRepo = new EfRfCandidateRepository(factory);
var posRepo = new EfRfPositionRepository(factory);
var closedRepo = new EfRfClosedTradeRepository(factory);
var settingsRepo = new EfRfSettingsRepository(factory);
settingsRepo.Upsert(new RfSettings
{
AccountId = 1, Enabled = true, MaxPerMarketUsd = 25m, MaxPerClusterPct = 10m,
MaxTotalExposurePct = 60m, MaxNewPositionsPerDay = 20, DailyLossKillSwitchUsd = 0m
});
candRepo.Insert(new RfCandidate { AccountId = 1, Accepted = true, TokenId = "a", ClusterKey = "c1", Ask = 0.95m, Score = 5m });
candRepo.Insert(new RfCandidate { AccountId = 1, Accepted = true, TokenId = "b", ClusterKey = "c2", Ask = 0.95m, Score = 3m });
var state = new TradingState();
state.Accounts[1] = new AccountState { AccountId = 1, Name = "Farming", IsDemo = demoAccount, TotalBalance = 1000m };
var svc = new FarmingExecutionService(state, settingsRepo, candRepo, posRepo, closedRepo, new TerminalLogger(), new FakeDecisionJournal());
return new Rig(svc, state, posRepo);
}
[Fact]
public void Aktiver_Demo_Modus_eroeffnet_Positionen()
{
var rig = Build();
rig.State.DemoTradingMode = TradingMode.Active;
Assert.Equal(2, rig.Svc.RunCycle());
Assert.Equal(2, rig.Positions.GetOpen(1).Count);
}
[Fact]
public void Globale_Pause_verhindert_Einstiege_trotz_aktivem_Modus()
{
var rig = Build();
rig.State.DemoTradingMode = TradingMode.Active;
rig.State.GlobalTradingPaused = true;
Assert.Equal(0, rig.Svc.RunCycle());
Assert.Empty(rig.Positions.GetOpen(1));
}
[Theory]
[InlineData(TradingMode.Inactive)]
[InlineData(TradingMode.SellOnly)] // Farming kauft ausschliesslich
public void Demo_Modus_ungleich_Aktiv_verhindert_Einstiege(TradingMode mode)
{
var rig = Build();
rig.State.DemoTradingMode = mode;
Assert.Equal(0, rig.Svc.RunCycle());
Assert.Empty(rig.Positions.GetOpen(1));
}
[Fact]
public void Aktiver_Live_Modus_gibt_Demo_Konten_nicht_frei()
{
var rig = Build();
rig.State.LiveTradingMode = TradingMode.Active;
rig.State.DemoTradingMode = TradingMode.Inactive;
Assert.Equal(0, rig.Svc.RunCycle());
}
[Fact]
public void Nach_Aufheben_der_Pause_geht_es_weiter()
{
var rig = Build();
rig.State.DemoTradingMode = TradingMode.Active;
rig.State.GlobalTradingPaused = true;
Assert.Equal(0, rig.Svc.RunCycle());
rig.State.GlobalTradingPaused = false;
Assert.Equal(2, rig.Svc.RunCycle());
}
[Fact]
public void Sperrgrund_nennt_die_Ursache()
{
var state = new TradingState { DemoTradingMode = TradingMode.Active, LiveTradingMode = TradingMode.SellOnly };
Assert.Null(FarmingExecutionService.EntryBlockReason(state, isDemo: true));
Assert.Contains("Nur Verkauf", FarmingExecutionService.EntryBlockReason(state, isDemo: false));
state.GlobalTradingPaused = true;
Assert.Contains("Pause", FarmingExecutionService.EntryBlockReason(state, isDemo: true));
}
}
}