Fix realized pnl on backtest save + add tests (not all passing)
This commit is contained in:
514
src/Managing.Domain.Tests/TraderAnalysisTests.cs
Normal file
514
src/Managing.Domain.Tests/TraderAnalysisTests.cs
Normal file
@@ -0,0 +1,514 @@
|
||||
using FluentAssertions;
|
||||
using Managing.Common;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Candles;
|
||||
using Managing.Domain.Indicators;
|
||||
using Managing.Domain.MoneyManagements;
|
||||
using Managing.Domain.Scenarios;
|
||||
using Managing.Domain.Shared.Helpers;
|
||||
using Managing.Domain.Statistics;
|
||||
using Managing.Domain.Strategies;
|
||||
using Managing.Domain.Strategies.Base;
|
||||
using Managing.Domain.Trades;
|
||||
using Xunit;
|
||||
using static Managing.Common.Enums;
|
||||
|
||||
namespace Managing.Domain.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for trader analysis methods in TradingBox.
|
||||
/// Covers trader evaluation, filtering, and analysis methods.
|
||||
/// </summary>
|
||||
public class TraderAnalysisTests : TradingBoxTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsAGoodTrader_WithAllCriteriaMet_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 35, // > 30
|
||||
TradeCount = 10, // > 8
|
||||
AverageWin = 100m,
|
||||
AverageLoss = -50m, // |AverageLoss| < AverageWin
|
||||
Pnl = 250m // > 0
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TradingBox.IsAGoodTrader(trader);
|
||||
|
||||
// Assert
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAGoodTrader_WithLowWinrate_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 25, // < 30
|
||||
TradeCount = 10,
|
||||
AverageWin = 100m,
|
||||
AverageLoss = -50m,
|
||||
Pnl = 250m
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TradingBox.IsAGoodTrader(trader);
|
||||
|
||||
// Assert
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAGoodTrader_WithInsufficientTrades_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 35,
|
||||
TradeCount = 5, // < 8
|
||||
AverageWin = 100m,
|
||||
AverageLoss = -50m,
|
||||
Pnl = 250m
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TradingBox.IsAGoodTrader(trader);
|
||||
|
||||
// Assert
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAGoodTrader_WithPoorRiskReward_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 35,
|
||||
TradeCount = 10,
|
||||
AverageWin = 50m,
|
||||
AverageLoss = -100m, // |AverageLoss| > AverageWin
|
||||
Pnl = 250m
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TradingBox.IsAGoodTrader(trader);
|
||||
|
||||
// Assert
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAGoodTrader_WithNegativePnL_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 35,
|
||||
TradeCount = 10,
|
||||
AverageWin = 100m,
|
||||
AverageLoss = -50m,
|
||||
Pnl = -50m // < 0
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TradingBox.IsAGoodTrader(trader);
|
||||
|
||||
// Assert
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsAGoodTrader_WithBoundaryValues_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 30, // Exactly 30
|
||||
TradeCount = 9, // Exactly 9
|
||||
AverageWin = 100m,
|
||||
AverageLoss = -99m, // |AverageLoss| < AverageWin
|
||||
Pnl = 1m // > 0
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TradingBox.IsAGoodTrader(trader);
|
||||
|
||||
// Assert
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsABadTrader_WithAllCriteriaMet_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 25, // < 30
|
||||
TradeCount = 10, // > 8
|
||||
AverageWin = 50m,
|
||||
AverageLoss = -200m, // |AverageLoss| * 3 > AverageWin
|
||||
Pnl = -500m // < 0
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TradingBox.IsABadTrader(trader);
|
||||
|
||||
// Assert
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsABadTrader_WithGoodWinrate_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 35, // > 30
|
||||
TradeCount = 10,
|
||||
AverageWin = 50m,
|
||||
AverageLoss = -200m,
|
||||
Pnl = -500m
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TradingBox.IsABadTrader(trader);
|
||||
|
||||
// Assert
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsABadTrader_WithInsufficientTrades_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 25,
|
||||
TradeCount = 5, // < 8
|
||||
AverageWin = 50m,
|
||||
AverageLoss = -200m,
|
||||
Pnl = -500m
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TradingBox.IsABadTrader(trader);
|
||||
|
||||
// Assert
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsABadTrader_WithGoodRiskReward_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 25,
|
||||
TradeCount = 10,
|
||||
AverageWin = 200m, // AverageWin > |AverageLoss| * 3
|
||||
AverageLoss = -50m,
|
||||
Pnl = -500m
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TradingBox.IsABadTrader(trader);
|
||||
|
||||
// Assert
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsABadTrader_WithPositivePnL_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 25,
|
||||
TradeCount = 10,
|
||||
AverageWin = 50m,
|
||||
AverageLoss = -200m,
|
||||
Pnl = 100m // > 0
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TradingBox.IsABadTrader(trader);
|
||||
|
||||
// Assert
|
||||
result.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsABadTrader_WithBoundaryValues_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 29, // < 30
|
||||
TradeCount = 9, // >= 8
|
||||
AverageWin = 50m,
|
||||
AverageLoss = -150m, // |AverageLoss| * 3 = 450 > AverageWin
|
||||
Pnl = -1m // < 0
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = TradingBox.IsABadTrader(trader);
|
||||
|
||||
// Assert
|
||||
result.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindBadTrader_WithEmptyList_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var traders = new List<Trader>();
|
||||
|
||||
// Act
|
||||
var result = traders.FindBadTrader();
|
||||
|
||||
// Assert
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindBadTrader_WithOnlyGoodTraders_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var traders = new List<Trader>
|
||||
{
|
||||
new Trader { Winrate = 35, TradeCount = 10, AverageWin = 100m, AverageLoss = -50m, Pnl = 250m },
|
||||
new Trader { Winrate = 40, TradeCount = 15, AverageWin = 150m, AverageLoss = -75m, Pnl = 500m }
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = traders.FindBadTrader();
|
||||
|
||||
// Assert
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindBadTrader_WithMixedTraders_ReturnsOnlyBadTraders()
|
||||
{
|
||||
// Arrange
|
||||
var goodTrader = new Trader
|
||||
{
|
||||
Winrate = 35,
|
||||
TradeCount = 10,
|
||||
AverageWin = 100m,
|
||||
AverageLoss = -50m,
|
||||
Pnl = 250m
|
||||
};
|
||||
|
||||
var badTrader1 = new Trader
|
||||
{
|
||||
Winrate = 25,
|
||||
TradeCount = 10,
|
||||
AverageWin = 50m,
|
||||
AverageLoss = -200m,
|
||||
Pnl = -500m
|
||||
};
|
||||
|
||||
var badTrader2 = new Trader
|
||||
{
|
||||
Winrate = 20,
|
||||
TradeCount = 12,
|
||||
AverageWin = 30m,
|
||||
AverageLoss = -150m,
|
||||
Pnl = -300m
|
||||
};
|
||||
|
||||
var traders = new List<Trader> { goodTrader, badTrader1, badTrader2 };
|
||||
|
||||
// Act
|
||||
var result = traders.FindBadTrader();
|
||||
|
||||
// Assert
|
||||
result.Should().HaveCount(2);
|
||||
result.Should().Contain(badTrader1);
|
||||
result.Should().Contain(badTrader2);
|
||||
result.Should().NotContain(goodTrader);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindGoodTrader_WithEmptyList_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var traders = new List<Trader>();
|
||||
|
||||
// Act
|
||||
var result = traders.FindGoodTrader();
|
||||
|
||||
// Assert
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindGoodTrader_WithOnlyBadTraders_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var traders = new List<Trader>
|
||||
{
|
||||
new Trader { Winrate = 25, TradeCount = 10, AverageWin = 50m, AverageLoss = -200m, Pnl = -500m },
|
||||
new Trader { Winrate = 20, TradeCount = 12, AverageWin = 30m, AverageLoss = -150m, Pnl = -300m }
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = traders.FindGoodTrader();
|
||||
|
||||
// Assert
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindGoodTrader_WithMixedTraders_ReturnsOnlyGoodTraders()
|
||||
{
|
||||
// Arrange
|
||||
var badTrader = new Trader
|
||||
{
|
||||
Winrate = 25,
|
||||
TradeCount = 10,
|
||||
AverageWin = 50m,
|
||||
AverageLoss = -200m,
|
||||
Pnl = -500m
|
||||
};
|
||||
|
||||
var goodTrader1 = new Trader
|
||||
{
|
||||
Winrate = 35,
|
||||
TradeCount = 10,
|
||||
AverageWin = 100m,
|
||||
AverageLoss = -50m,
|
||||
Pnl = 250m
|
||||
};
|
||||
|
||||
var goodTrader2 = new Trader
|
||||
{
|
||||
Winrate = 40,
|
||||
TradeCount = 15,
|
||||
AverageWin = 150m,
|
||||
AverageLoss = -75m,
|
||||
Pnl = 500m
|
||||
};
|
||||
|
||||
var traders = new List<Trader> { badTrader, goodTrader1, goodTrader2 };
|
||||
|
||||
// Act
|
||||
var result = traders.FindGoodTrader();
|
||||
|
||||
// Assert
|
||||
result.Should().HaveCount(2);
|
||||
result.Should().Contain(goodTrader1);
|
||||
result.Should().Contain(goodTrader2);
|
||||
result.Should().NotContain(badTrader);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapToTraders_WithAccounts_CreatesTradersWithCorrectAddresses()
|
||||
{
|
||||
// Arrange
|
||||
var account1 = new Account { Key = "0x123", Name = "Account1" };
|
||||
var account2 = new Account { Key = "0x456", Name = "Account2" };
|
||||
var accounts = new List<Account> { account1, account2 };
|
||||
|
||||
// Act
|
||||
var result = accounts.MapToTraders();
|
||||
|
||||
// Assert
|
||||
result.Should().HaveCount(2);
|
||||
result[0].Address.Should().Be("0x123");
|
||||
result[1].Address.Should().Be("0x456");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapToTraders_WithEmptyList_ReturnsEmptyList()
|
||||
{
|
||||
// Arrange
|
||||
var accounts = new List<Account>();
|
||||
|
||||
// Act
|
||||
var result = accounts.MapToTraders();
|
||||
|
||||
// Assert
|
||||
result.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(35, 10, 100, -50, 250, true)] // Good trader
|
||||
[InlineData(25, 10, 50, -200, -500, false)] // Bad trader
|
||||
[InlineData(30, 8, 100, -50, 100, true)] // Boundary good trader
|
||||
[InlineData(29, 9, 50, -150, -100, false)] // Boundary bad trader
|
||||
[InlineData(32, 7, 100, -50, 200, false)] // Insufficient trades
|
||||
[InlineData(28, 10, 200, -50, -100, false)] // Good RR but low winrate
|
||||
public void TraderEvaluation_TheoryTests(int winrate, int tradeCount, decimal avgWin, decimal avgLoss, decimal pnl, bool expectedGood)
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = winrate,
|
||||
TradeCount = tradeCount,
|
||||
AverageWin = avgWin,
|
||||
AverageLoss = avgLoss,
|
||||
Pnl = pnl
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
if (expectedGood)
|
||||
{
|
||||
TradingBox.IsAGoodTrader(trader).Should().BeTrue();
|
||||
TradingBox.IsABadTrader(trader).Should().BeFalse();
|
||||
}
|
||||
else
|
||||
{
|
||||
TradingBox.IsAGoodTrader(trader).Should().BeFalse();
|
||||
// Bad trader evaluation depends on multiple criteria
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TraderAnalysis_MethodsAreConsistent()
|
||||
{
|
||||
// Arrange
|
||||
var trader = new Trader
|
||||
{
|
||||
Winrate = 35,
|
||||
TradeCount = 10,
|
||||
AverageWin = 100m,
|
||||
AverageLoss = -50m,
|
||||
Pnl = 250m
|
||||
};
|
||||
|
||||
// Act
|
||||
var isGood = TradingBox.IsAGoodTrader(trader);
|
||||
var isBad = TradingBox.IsABadTrader(trader);
|
||||
|
||||
// Assert
|
||||
// A trader cannot be both good and bad
|
||||
(isGood && isBad).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindMethods_WorkTogetherCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var traders = new List<Trader>
|
||||
{
|
||||
new Trader { Winrate = 35, TradeCount = 10, AverageWin = 100m, AverageLoss = -50m, Pnl = 250m }, // Good
|
||||
new Trader { Winrate = 25, TradeCount = 10, AverageWin = 50m, AverageLoss = -200m, Pnl = -500m }, // Bad
|
||||
new Trader { Winrate = 32, TradeCount = 5, AverageWin = 75m, AverageLoss = -75m, Pnl = 0m } // Neither
|
||||
};
|
||||
|
||||
// Act
|
||||
var goodTraders = traders.FindGoodTrader();
|
||||
var badTraders = traders.FindBadTrader();
|
||||
|
||||
// Assert
|
||||
goodTraders.Should().HaveCount(1);
|
||||
badTraders.Should().HaveCount(1);
|
||||
goodTraders.First().Should().NotBe(badTraders.First());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user