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)); } } }