Add test for dailysnapshot

This commit is contained in:
2025-11-14 19:42:52 +07:00
parent 479fcca662
commit b60295fcb2
3 changed files with 702 additions and 27 deletions

View File

@@ -51,10 +51,386 @@ public class TradingBoxAgentSummaryMetricsTests
Assert.Equal(1, metrics.Losses);
}
[Fact]
public void CalculateAgentSummaryMetrics_WithEmptyPositions_ReturnsZeroMetrics()
{
var positions = new List<Position>();
var metrics = TradingBox.CalculateAgentSummaryMetrics(positions);
Assert.Equal(0m, metrics.TotalPnL);
Assert.Equal(0m, metrics.NetPnL);
Assert.Equal(0m, metrics.TotalROI);
Assert.Equal(0m, metrics.TotalVolume);
Assert.Equal(0, metrics.Wins);
Assert.Equal(0, metrics.Losses);
Assert.Equal(0m, metrics.TotalFees);
Assert.Equal(0m, metrics.Collateral);
}
[Fact]
public void CalculateAgentSummaryMetrics_WithNullPositions_ReturnsZeroMetrics()
{
var metrics = TradingBox.CalculateAgentSummaryMetrics(null);
Assert.Equal(0m, metrics.TotalPnL);
Assert.Equal(0m, metrics.NetPnL);
Assert.Equal(0m, metrics.TotalROI);
Assert.Equal(0m, metrics.TotalVolume);
Assert.Equal(0, metrics.Wins);
Assert.Equal(0, metrics.Losses);
Assert.Equal(0m, metrics.TotalFees);
Assert.Equal(0m, metrics.Collateral);
}
[Fact]
public void CalculateAgentSummaryMetrics_WithInvalidPositions_ExcludesThemFromCalculations()
{
var positions = new List<Position>
{
CreatePosition(
openPrice: 100m,
quantity: 2m,
direction: TradeDirection.Long,
realizedPnL: 10m,
netPnL: 8m,
uiFees: 1m,
gasFees: 1m,
stopLossPrice: 95m,
stopLossStatus: TradeStatus.Filled,
takeProfitPrice: 110m,
takeProfitStatus: TradeStatus.Filled),
CreateInvalidPosition() // This should be excluded
};
var metrics = TradingBox.CalculateAgentSummaryMetrics(positions);
// Should only include the valid position
Assert.Equal(10m, metrics.TotalPnL);
Assert.Equal(2m, metrics.TotalFees);
Assert.Equal(8m, metrics.NetPnL);
Assert.Equal(8m / 200m * 100m, metrics.TotalROI); // 8 / 200 * 100
Assert.Equal(610m, metrics.TotalVolume); // 100*2*1 + 95*2*1 + 110*2*1 = 200 + 190 + 220
Assert.Equal(200m, metrics.Collateral); // 100 * 2
Assert.Equal(1, metrics.Wins);
Assert.Equal(0, metrics.Losses);
}
[Fact]
public void CalculateAgentSummaryMetrics_WithCanceledPositions_ExcludesThemFromCalculations()
{
var positions = new List<Position>
{
CreatePosition(
openPrice: 100m,
quantity: 2m,
direction: TradeDirection.Long,
realizedPnL: 10m,
netPnL: 8m,
uiFees: 1m,
gasFees: 1m,
stopLossPrice: 95m,
stopLossStatus: TradeStatus.Filled,
takeProfitPrice: 110m,
takeProfitStatus: TradeStatus.Filled),
CreateCanceledPosition()
};
var metrics = TradingBox.CalculateAgentSummaryMetrics(positions);
// Should only include the finished position
Assert.Equal(10m, metrics.TotalPnL);
Assert.Equal(2m, metrics.TotalFees);
Assert.Equal(8m, metrics.NetPnL);
Assert.Equal(8m / 200m * 100m, metrics.TotalROI);
Assert.Equal(610m, metrics.TotalVolume); // 100*2*1 + 95*2*1 + 110*2*1 = 200 + 190 + 220
Assert.Equal(200m, metrics.Collateral);
Assert.Equal(1, metrics.Wins);
Assert.Equal(0, metrics.Losses);
}
[Fact]
public void CalculateAgentSummaryMetrics_WithRejectedPositions_ExcludesThemFromCalculations()
{
var positions = new List<Position>
{
CreatePosition(
openPrice: 100m,
quantity: 2m,
direction: TradeDirection.Long,
realizedPnL: 10m,
netPnL: 8m,
uiFees: 1m,
gasFees: 1m,
stopLossPrice: 95m,
stopLossStatus: TradeStatus.Filled,
takeProfitPrice: 110m,
takeProfitStatus: TradeStatus.Filled),
CreateRejectedPosition()
};
var metrics = TradingBox.CalculateAgentSummaryMetrics(positions);
// Should only include the finished position
Assert.Equal(10m, metrics.TotalPnL);
Assert.Equal(2m, metrics.TotalFees);
Assert.Equal(8m, metrics.NetPnL);
Assert.Equal(8m / 200m * 100m, metrics.TotalROI);
Assert.Equal(610m, metrics.TotalVolume); // 100*2*1 + 95*2*1 + 110*2*1 = 200 + 190 + 220
Assert.Equal(200m, metrics.Collateral);
Assert.Equal(1, metrics.Wins);
Assert.Equal(0, metrics.Losses);
}
[Fact]
public void CalculateAgentSummaryMetrics_WithZeroPnL_HandlesCorrectly()
{
var positions = new List<Position>
{
CreatePosition(
openPrice: 100m,
quantity: 2m,
direction: TradeDirection.Long,
realizedPnL: 0m,
netPnL: -1m, // Loss due to fees
uiFees: 0.5m,
gasFees: 0.5m,
stopLossPrice: 95m,
stopLossStatus: TradeStatus.Cancelled,
takeProfitPrice: 110m,
takeProfitStatus: TradeStatus.Cancelled)
};
var metrics = TradingBox.CalculateAgentSummaryMetrics(positions);
Assert.Equal(0m, metrics.TotalPnL);
Assert.Equal(1m, metrics.TotalFees);
Assert.Equal(-1m, metrics.NetPnL);
Assert.Equal(-1m / 200m * 100m, metrics.TotalROI); // -1 / 200 * 100
Assert.Equal(200m, metrics.TotalVolume); // Only opening volume
Assert.Equal(200m, metrics.Collateral);
Assert.Equal(0, metrics.Wins);
Assert.Equal(1, metrics.Losses); // Net PnL <= 0
}
[Fact]
public void CalculateAgentSummaryMetrics_WithZeroCollateral_HandlesDivisionByZero()
{
var positions = new List<Position>
{
CreatePositionWithZeroCollateral()
};
var metrics = TradingBox.CalculateAgentSummaryMetrics(positions);
Assert.Equal(10m, metrics.TotalPnL);
Assert.Equal(1m, metrics.TotalFees);
Assert.Equal(9m, metrics.NetPnL);
Assert.Equal(0m, metrics.TotalROI); // ROI = 0 when collateral = 0 to avoid division by zero
Assert.Equal(10m, metrics.TotalVolume); // 0 * 1 * 1 (open) + 10 * 1 * 1 (close)
Assert.Equal(0m, metrics.Collateral); // Zero collateral
Assert.Equal(1, metrics.Wins);
Assert.Equal(0, metrics.Losses);
}
[Fact]
public void CalculateAgentSummaryMetrics_WithAllWinningPositions_CalculatesCorrectWinRate()
{
var positions = new List<Position>
{
CreatePosition(
openPrice: 100m,
quantity: 1m,
direction: TradeDirection.Long,
realizedPnL: 5m,
netPnL: 4m,
uiFees: 0.5m,
gasFees: 0.5m,
stopLossPrice: 95m,
stopLossStatus: TradeStatus.Cancelled,
takeProfitPrice: 110m,
takeProfitStatus: TradeStatus.Filled),
CreatePosition(
openPrice: 200m,
quantity: 1m,
direction: TradeDirection.Short,
realizedPnL: 8m,
netPnL: 7m,
uiFees: 0.5m,
gasFees: 0.5m,
stopLossPrice: 210m,
stopLossStatus: TradeStatus.Cancelled,
takeProfitPrice: 190m,
takeProfitStatus: TradeStatus.Filled)
};
var metrics = TradingBox.CalculateAgentSummaryMetrics(positions);
Assert.Equal(13m, metrics.TotalPnL);
Assert.Equal(2m, metrics.TotalFees);
Assert.Equal(11m, metrics.NetPnL);
Assert.Equal(11m / 300m * 100m, metrics.TotalROI); // 11 / 300 * 100
Assert.Equal(600m, metrics.TotalVolume); // 100*1*1 + 110*1*1 + 200*1*1 + 190*1*1 = 210 + 390 = 600
Assert.Equal(300m, metrics.Collateral); // 100*1 + 200*1
Assert.Equal(2, metrics.Wins);
Assert.Equal(0, metrics.Losses);
}
[Fact]
public void CalculateAgentSummaryMetrics_WithAllLosingPositions_CalculatesCorrectWinRate()
{
var positions = new List<Position>
{
CreatePosition(
openPrice: 100m,
quantity: 1m,
direction: TradeDirection.Long,
realizedPnL: -5m,
netPnL: -6m,
uiFees: 0.5m,
gasFees: 0.5m,
stopLossPrice: 95m,
stopLossStatus: TradeStatus.Filled,
takeProfitPrice: 110m,
takeProfitStatus: TradeStatus.Cancelled),
CreatePosition(
openPrice: 200m,
quantity: 1m,
direction: TradeDirection.Short,
realizedPnL: -3m,
netPnL: -4m,
uiFees: 0.5m,
gasFees: 0.5m,
stopLossPrice: 210m,
stopLossStatus: TradeStatus.Filled,
takeProfitPrice: 190m,
takeProfitStatus: TradeStatus.Cancelled)
};
var metrics = TradingBox.CalculateAgentSummaryMetrics(positions);
Assert.Equal(-8m, metrics.TotalPnL);
Assert.Equal(2m, metrics.TotalFees);
Assert.Equal(-10m, metrics.NetPnL);
Assert.Equal(-10m / 300m * 100m, metrics.TotalROI); // -10 / 300 * 100
Assert.Equal(605m, metrics.TotalVolume); // 100*1 + 95*1 + 200*1 + 210*1 = 195 + 410 = 605
Assert.Equal(300m, metrics.Collateral); // 100*1 + 200*1
Assert.Equal(0, metrics.Wins);
Assert.Equal(2, metrics.Losses);
}
[Fact]
public void CalculateAgentSummaryMetrics_WithHighLeverage_IncludesLeverageInVolume()
{
var positions = new List<Position>
{
CreatePositionWithLeverage(
openPrice: 100m,
quantity: 1m,
leverage: 5m,
direction: TradeDirection.Long,
realizedPnL: 10m,
netPnL: 9m,
uiFees: 1m,
gasFees: 1m,
stopLossPrice: 95m,
stopLossStatus: TradeStatus.Cancelled,
takeProfitPrice: 110m,
takeProfitStatus: TradeStatus.Filled)
};
var metrics = TradingBox.CalculateAgentSummaryMetrics(positions);
Assert.Equal(10m, metrics.TotalPnL);
Assert.Equal(2m, metrics.TotalFees);
Assert.Equal(8m, metrics.NetPnL);
Assert.Equal(8m, metrics.TotalROI); // 8 / 100 * 100
Assert.Equal(1050m, metrics.TotalVolume); // (100*1*5) + (110*1*5) = 500 + 550
Assert.Equal(100m, metrics.Collateral); // 100 * 1 (quantity, not leveraged)
Assert.Equal(1, metrics.Wins);
Assert.Equal(0, metrics.Losses);
}
[Fact]
public void CalculateAgentSummaryMetrics_WithZeroFees_CalculatesCorrectly()
{
var positions = new List<Position>
{
CreatePosition(
openPrice: 100m,
quantity: 1m,
direction: TradeDirection.Long,
realizedPnL: 10m,
netPnL: 10m, // No fees deducted
uiFees: 0m,
gasFees: 0m,
stopLossPrice: 95m,
stopLossStatus: TradeStatus.Cancelled,
takeProfitPrice: 110m,
takeProfitStatus: TradeStatus.Filled)
};
var metrics = TradingBox.CalculateAgentSummaryMetrics(positions);
Assert.Equal(10m, metrics.TotalPnL);
Assert.Equal(0m, metrics.TotalFees);
Assert.Equal(10m, metrics.NetPnL);
Assert.Equal(10m, metrics.TotalROI);
Assert.Equal(210m, metrics.TotalVolume);
Assert.Equal(100m, metrics.Collateral);
Assert.Equal(1, metrics.Wins);
Assert.Equal(0, metrics.Losses);
}
[Fact]
public void CalculateAgentSummaryMetrics_WithMultipleAssets_AggregatesCorrectly()
{
var positions = new List<Position>
{
CreatePositionWithAsset(
openPrice: 50000m,
quantity: 0.1m,
direction: TradeDirection.Long,
asset: Ticker.BTC,
realizedPnL: 100m,
netPnL: 98m,
uiFees: 1m,
gasFees: 1m,
stopLossPrice: 47500m,
stopLossStatus: TradeStatus.Cancelled,
takeProfitPrice: 52500m,
takeProfitStatus: TradeStatus.Filled),
CreatePositionWithAsset(
openPrice: 3000m,
quantity: 1m,
direction: TradeDirection.Short,
asset: Ticker.ETH,
realizedPnL: 50m,
netPnL: 48m,
uiFees: 1m,
gasFees: 1m,
stopLossPrice: 3150m,
stopLossStatus: TradeStatus.Cancelled,
takeProfitPrice: 2850m,
takeProfitStatus: TradeStatus.Filled)
};
var metrics = TradingBox.CalculateAgentSummaryMetrics(positions);
Assert.Equal(150m, metrics.TotalPnL);
Assert.Equal(4m, metrics.TotalFees);
Assert.Equal(146m, metrics.NetPnL);
Assert.Equal(146m / 8000m * 100m, metrics.TotalROI); // 146 / 8000 * 100
Assert.Equal(10250m + 5850m, metrics.TotalVolume); // BTC: 5000+5250, ETH: 3000+2850
Assert.Equal(5000m + 3000m, metrics.Collateral); // 50000*0.1 + 3000*1
Assert.Equal(2, metrics.Wins);
Assert.Equal(0, metrics.Losses);
}
private static Position CreatePosition(decimal openPrice, decimal quantity, TradeDirection direction,
decimal realizedPnL, decimal netPnL, decimal uiFees, decimal gasFees,
decimal stopLossPrice, TradeStatus stopLossStatus, decimal takeProfitPrice,
TradeStatus takeProfitStatus)
TradeStatus takeProfitStatus, decimal leverage = 1m)
{
var position = new Position(
Guid.NewGuid(),
@@ -74,11 +450,11 @@ public class TradingBoxAgentSummaryMetricsTests
user: new User { Id = 1, Name = "tester" });
position.Status = PositionStatus.Finished;
position.Open = BuildTrade(direction, TradeStatus.Filled, openPrice, quantity);
position.Open = BuildTrade(direction, TradeStatus.Filled, openPrice, quantity, leverage);
position.StopLoss = BuildTrade(direction == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long,
stopLossStatus, stopLossPrice, quantity);
stopLossStatus, stopLossPrice, quantity, leverage);
position.TakeProfit1 = BuildTrade(direction == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long,
takeProfitStatus, takeProfitPrice, quantity);
takeProfitStatus, takeProfitPrice, quantity, leverage);
position.ProfitAndLoss = new ProfitAndLoss
{
Realized = realizedPnL,
@@ -90,7 +466,7 @@ public class TradingBoxAgentSummaryMetricsTests
return position;
}
private static Trade BuildTrade(TradeDirection direction, TradeStatus status, decimal price, decimal quantity)
private static Trade BuildTrade(TradeDirection direction, TradeStatus status, decimal price, decimal quantity, decimal leverage = 1m)
{
return new Trade(
date: DateTime.UtcNow,
@@ -100,9 +476,317 @@ public class TradingBoxAgentSummaryMetricsTests
ticker: Ticker.BTC,
quantity: quantity,
price: price,
leverage: 1m,
leverage: leverage,
exchangeOrderId: Guid.NewGuid().ToString(),
message: "unit-trade");
}
}
private static Position CreateInvalidPosition()
{
var position = new Position(
Guid.NewGuid(),
accountId: 1,
originDirection: TradeDirection.Long,
ticker: Ticker.BTC,
moneyManagement: new LightMoneyManagement
{
Name = "unit-test",
Timeframe = Timeframe.OneHour,
StopLoss = 0.02m,
TakeProfit = 0.04m,
Leverage = 1m
},
initiator: PositionInitiator.User,
date: DateTime.UtcNow,
user: new User { Id = 1, Name = "tester" });
position.Status = PositionStatus.New; // New positions are not valid for metrics
return position;
}
private static Position CreateCanceledPosition()
{
var position = new Position(
Guid.NewGuid(),
accountId: 1,
originDirection: TradeDirection.Long,
ticker: Ticker.BTC,
moneyManagement: new LightMoneyManagement
{
Name = "unit-test",
Timeframe = Timeframe.OneHour,
StopLoss = 0.02m,
TakeProfit = 0.04m,
Leverage = 1m
},
initiator: PositionInitiator.User,
date: DateTime.UtcNow,
user: new User { Id = 1, Name = "tester" });
position.Status = PositionStatus.Canceled; // Canceled positions are not valid for metrics
return position;
}
private static Position CreateRejectedPosition()
{
var position = new Position(
Guid.NewGuid(),
accountId: 1,
originDirection: TradeDirection.Long,
ticker: Ticker.BTC,
moneyManagement: new LightMoneyManagement
{
Name = "unit-test",
Timeframe = Timeframe.OneHour,
StopLoss = 0.02m,
TakeProfit = 0.04m,
Leverage = 1m
},
initiator: PositionInitiator.User,
date: DateTime.UtcNow,
user: new User { Id = 1, Name = "tester" });
position.Status = PositionStatus.Rejected; // Rejected positions are not valid for metrics
return position;
}
private static Position CreatePositionWithZeroCollateral()
{
var position = new Position(
Guid.NewGuid(),
accountId: 1,
originDirection: TradeDirection.Long,
ticker: Ticker.BTC,
moneyManagement: new LightMoneyManagement
{
Name = "unit-test",
Timeframe = Timeframe.OneHour,
StopLoss = 0.02m,
TakeProfit = 0.04m,
Leverage = 1m
},
initiator: PositionInitiator.User,
date: DateTime.UtcNow,
user: new User { Id = 1, Name = "tester" });
position.Status = PositionStatus.Finished;
position.Open = BuildTrade(TradeDirection.Long, TradeStatus.Filled, 0m, 1m); // Zero price = zero collateral
position.TakeProfit1 = BuildTrade(TradeDirection.Short, TradeStatus.Filled, 10m, 1m);
position.ProfitAndLoss = new ProfitAndLoss
{
Realized = 10m,
Net = 9m
};
position.UiFees = 0.5m;
position.GasFees = 0.5m;
return position;
}
private static Position CreatePositionWithLeverage(decimal openPrice, decimal quantity, decimal leverage,
TradeDirection direction, decimal realizedPnL, decimal netPnL, decimal uiFees, decimal gasFees,
decimal stopLossPrice, TradeStatus stopLossStatus, decimal takeProfitPrice, TradeStatus takeProfitStatus)
{
var position = new Position(
Guid.NewGuid(),
accountId: 1,
originDirection: direction,
ticker: Ticker.BTC,
moneyManagement: new LightMoneyManagement
{
Name = "unit-test",
Timeframe = Timeframe.OneHour,
StopLoss = 0.02m,
TakeProfit = 0.04m,
Leverage = leverage
},
initiator: PositionInitiator.User,
date: DateTime.UtcNow,
user: new User { Id = 1, Name = "tester" });
position.Status = PositionStatus.Finished;
position.Open = BuildTrade(direction, TradeStatus.Filled, openPrice, quantity, leverage);
position.StopLoss = BuildTrade(direction == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long,
stopLossStatus, stopLossPrice, quantity, leverage);
position.TakeProfit1 = BuildTrade(direction == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long,
takeProfitStatus, takeProfitPrice, quantity, leverage);
position.ProfitAndLoss = new ProfitAndLoss
{
Realized = realizedPnL,
Net = netPnL
};
position.UiFees = uiFees;
position.GasFees = gasFees;
return position;
}
private static Position CreatePositionWithAsset(decimal openPrice, decimal quantity, TradeDirection direction,
Ticker asset, decimal realizedPnL, decimal netPnL, decimal uiFees, decimal gasFees,
decimal stopLossPrice, TradeStatus stopLossStatus, decimal takeProfitPrice, TradeStatus takeProfitStatus)
{
var position = new Position(
Guid.NewGuid(),
accountId: 1,
originDirection: direction,
ticker: asset,
moneyManagement: new LightMoneyManagement
{
Name = "unit-test",
Timeframe = Timeframe.OneHour,
StopLoss = 0.02m,
TakeProfit = 0.04m,
Leverage = 1m
},
initiator: PositionInitiator.User,
date: DateTime.UtcNow,
user: new User { Id = 1, Name = "tester" });
position.Status = PositionStatus.Finished;
position.Open = new Trade(
date: DateTime.UtcNow,
direction: direction,
status: TradeStatus.Filled,
tradeType: TradeType.Market,
ticker: asset,
quantity: quantity,
price: openPrice,
leverage: 1m,
exchangeOrderId: Guid.NewGuid().ToString(),
message: "unit-trade");
position.StopLoss = new Trade(
date: DateTime.UtcNow,
direction: direction == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long,
status: stopLossStatus,
tradeType: TradeType.Market,
ticker: asset,
quantity: quantity,
price: stopLossPrice,
leverage: 1m,
exchangeOrderId: Guid.NewGuid().ToString(),
message: "unit-trade");
position.TakeProfit1 = new Trade(
date: DateTime.UtcNow,
direction: direction == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long,
status: takeProfitStatus,
tradeType: TradeType.Market,
ticker: asset,
quantity: quantity,
price: takeProfitPrice,
leverage: 1m,
exchangeOrderId: Guid.NewGuid().ToString(),
message: "unit-trade");
position.ProfitAndLoss = new ProfitAndLoss
{
Realized = realizedPnL,
Net = netPnL
};
position.UiFees = uiFees;
position.GasFees = gasFees;
return position;
}
[Fact]
public void CalculateAgentSummaryMetrics_WithHighLeverage_CalculatesCorrectVolumeAndCollateral()
{
// Create positions with high leverage manually
var btcPosition = new Position(
Guid.NewGuid(),
accountId: 1,
originDirection: TradeDirection.Long,
ticker: Ticker.BTC,
moneyManagement: new LightMoneyManagement
{
Name = "high-leverage-test",
Timeframe = Timeframe.OneHour,
StopLoss = 0.02m,
TakeProfit = 0.04m,
Leverage = 10m // High leverage
},
initiator: PositionInitiator.User,
date: DateTime.UtcNow,
user: new User { Id = 1, Name = "tester" });
btcPosition.Status = PositionStatus.Finished;
btcPosition.Open = BuildTrade(TradeDirection.Long, TradeStatus.Filled, 50000m, 0.1m, 10m); // With leverage
btcPosition.StopLoss = BuildTrade(TradeDirection.Short, TradeStatus.Cancelled, 47500m, 0.1m, 10m);
btcPosition.TakeProfit1 = BuildTrade(TradeDirection.Short, TradeStatus.Filled, 52500m, 0.1m, 10m);
btcPosition.ProfitAndLoss = new ProfitAndLoss { Realized = 1000m, Net = 990m };
btcPosition.UiFees = 5m;
btcPosition.GasFees = 5m;
var ethPosition = new Position(
Guid.NewGuid(),
accountId: 1,
originDirection: TradeDirection.Short,
ticker: Ticker.ETH,
moneyManagement: new LightMoneyManagement
{
Name = "high-leverage-test",
Timeframe = Timeframe.OneHour,
StopLoss = 0.02m,
TakeProfit = 0.04m,
Leverage = 5m // Moderate leverage
},
initiator: PositionInitiator.User,
date: DateTime.UtcNow,
user: new User { Id = 1, Name = "tester" });
ethPosition.Status = PositionStatus.Finished;
ethPosition.Open = new Trade(
date: DateTime.UtcNow,
direction: TradeDirection.Short,
status: TradeStatus.Filled,
tradeType: TradeType.Market,
ticker: Ticker.ETH,
quantity: 1m,
price: 3000m,
leverage: 5m,
exchangeOrderId: Guid.NewGuid().ToString(),
message: "unit-trade");
ethPosition.StopLoss = new Trade(
date: DateTime.UtcNow,
direction: TradeDirection.Long,
status: TradeStatus.Cancelled,
tradeType: TradeType.Market,
ticker: Ticker.ETH,
quantity: 1m,
price: 3150m,
leverage: 5m,
exchangeOrderId: Guid.NewGuid().ToString(),
message: "unit-trade");
ethPosition.TakeProfit1 = new Trade(
date: DateTime.UtcNow,
direction: TradeDirection.Long,
status: TradeStatus.Filled,
tradeType: TradeType.Market,
ticker: Ticker.ETH,
quantity: 1m,
price: 2850m,
leverage: 5m,
exchangeOrderId: Guid.NewGuid().ToString(),
message: "unit-trade");
ethPosition.ProfitAndLoss = new ProfitAndLoss { Realized = 300m, Net = 295m };
ethPosition.UiFees = 2.5m;
ethPosition.GasFees = 2.5m;
var positions = new List<Position> { btcPosition, ethPosition };
var metrics = TradingBox.CalculateAgentSummaryMetrics(positions);
Assert.Equal(1300m, metrics.TotalPnL); // 1000 + 300
Assert.Equal(15m, metrics.TotalFees); // 5+5 + 2.5+2.5
Assert.Equal(1285m, metrics.NetPnL); // 1300 - 15
Assert.Equal(1285m / 8000m * 100m, metrics.TotalROI); // 1285 / 8000 * 100
// Volume calculations with leverage: price * quantity * leverage
// BTC: (50000 * 0.1 * 10) + (52500 * 0.1 * 10) = 50000 + 52500 = 102500
// ETH: (3000 * 1 * 5) + (2850 * 1 * 5) = 15000 + 14250 = 29250
Assert.Equal(102500m + 29250m, metrics.TotalVolume); // 131750
// Collateral is price * quantity (without leverage)
Assert.Equal(5000m + 3000m, metrics.Collateral); // 8000
Assert.Equal(2, metrics.Wins);
Assert.Equal(0, metrics.Losses);
}
}