using PolyTrader.Modules.CopyTrading.Logic; using Xunit; using static PolyTrader.Modules.CopyTrading.Logic.SellLogic; namespace PolyTrader.Tests { /// /// Sicherheitsnetz für die SELL-Logik: Proportionalität (aktuelles Verhalten, Phase-2-Umbau) /// und die Eskalationsleiter (Phase 0.1 – Preis-Stufen, Floor-Clamping). /// 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)); } } }