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); 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, private static Position CreatePosition(decimal openPrice, decimal quantity, TradeDirection direction,
decimal realizedPnL, decimal netPnL, decimal uiFees, decimal gasFees, decimal realizedPnL, decimal netPnL, decimal uiFees, decimal gasFees,
decimal stopLossPrice, TradeStatus stopLossStatus, decimal takeProfitPrice, decimal stopLossPrice, TradeStatus stopLossStatus, decimal takeProfitPrice,
TradeStatus takeProfitStatus) TradeStatus takeProfitStatus, decimal leverage = 1m)
{ {
var position = new Position( var position = new Position(
Guid.NewGuid(), Guid.NewGuid(),
@@ -74,11 +450,11 @@ public class TradingBoxAgentSummaryMetricsTests
user: new User { Id = 1, Name = "tester" }); user: new User { Id = 1, Name = "tester" });
position.Status = PositionStatus.Finished; 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, 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, position.TakeProfit1 = BuildTrade(direction == TradeDirection.Long ? TradeDirection.Short : TradeDirection.Long,
takeProfitStatus, takeProfitPrice, quantity); takeProfitStatus, takeProfitPrice, quantity, leverage);
position.ProfitAndLoss = new ProfitAndLoss position.ProfitAndLoss = new ProfitAndLoss
{ {
Realized = realizedPnL, Realized = realizedPnL,
@@ -90,7 +466,7 @@ public class TradingBoxAgentSummaryMetricsTests
return position; 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( return new Trade(
date: DateTime.UtcNow, date: DateTime.UtcNow,
@@ -100,9 +476,317 @@ public class TradingBoxAgentSummaryMetricsTests
ticker: Ticker.BTC, ticker: Ticker.BTC,
quantity: quantity, quantity: quantity,
price: price, price: price,
leverage: 1m, leverage: leverage,
exchangeOrderId: Guid.NewGuid().ToString(), exchangeOrderId: Guid.NewGuid().ToString(),
message: "unit-trade"); 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);
}
}

View File

@@ -849,17 +849,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
.Where(p => p.IsValidForMetrics()).ToList(); .Where(p => p.IsValidForMetrics()).ToList();
}); });
// Calculate statistics using TradingBox helpers // Calculate statistics using TradingBox.CalculateAgentSummaryMetrics
var (tradeWins, tradeLosses) = TradingBox.GetWinLossCount(positionForMetrics); var agentMetrics = TradingBox.CalculateAgentSummaryMetrics(positionForMetrics);
var pnl = positionForMetrics.Sum(p => p.ProfitAndLoss.Realized);
var fees = positionForMetrics.Sum(p => p.CalculateTotalFees());
var netPnl = pnl - fees; // Net PnL after fees
var volume = TradingBox.GetTotalVolumeTraded(positionForMetrics);
// Calculate ROI based on total investment (Net PnL)
var totalInvestment = positionForMetrics
.Sum(p => p.Open.Quantity * p.Open.Price);
var roi = totalInvestment > 0 ? (netPnl / totalInvestment) * 100 : 0;
// Calculate long and short position counts // Calculate long and short position counts
var longPositionCount = positionForMetrics var longPositionCount = positionForMetrics
@@ -880,13 +871,13 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
LastStopTime = _state.State.LastStopTime, LastStopTime = _state.State.LastStopTime,
AccumulatedRunTimeSeconds = _state.State.AccumulatedRunTimeSeconds, AccumulatedRunTimeSeconds = _state.State.AccumulatedRunTimeSeconds,
CreateDate = _state.State.CreateDate, CreateDate = _state.State.CreateDate,
TradeWins = tradeWins, TradeWins = agentMetrics.Wins,
TradeLosses = tradeLosses, TradeLosses = agentMetrics.Losses,
Pnl = pnl, // Gross PnL before fees Pnl = agentMetrics.TotalPnL, // Gross PnL before fees
NetPnL = netPnl, // Net PnL after fees NetPnL = agentMetrics.NetPnL, // Net PnL after fees
Roi = roi, Roi = agentMetrics.TotalROI,
Volume = volume, Volume = agentMetrics.TotalVolume,
Fees = fees, Fees = agentMetrics.TotalFees,
LongPositionCount = longPositionCount, LongPositionCount = longPositionCount,
ShortPositionCount = shortPositionCount ShortPositionCount = shortPositionCount
}; };

View File

@@ -540,12 +540,12 @@ public static class TradingBox
totalVolume += position.Open.Quantity * position.Open.Price * position.Open.Leverage; totalVolume += position.Open.Quantity * position.Open.Price * position.Open.Leverage;
// Add exit volumes from stop loss or take profits if they were executed // Add exit volumes from stop loss or take profits if they were executed
if (position.StopLoss.Status == TradeStatus.Filled) if (position.StopLoss != null && position.StopLoss.Status == TradeStatus.Filled)
{ {
totalVolume += position.StopLoss.Quantity * position.StopLoss.Price * position.StopLoss.Leverage; totalVolume += position.StopLoss.Quantity * position.StopLoss.Price * position.StopLoss.Leverage;
} }
if (position.TakeProfit1.Status == TradeStatus.Filled) if (position.TakeProfit1 != null && position.TakeProfit1.Status == TradeStatus.Filled)
{ {
totalVolume += position.TakeProfit1.Quantity * position.TakeProfit1.Price * totalVolume += position.TakeProfit1.Quantity * position.TakeProfit1.Price *
position.TakeProfit1.Leverage; position.TakeProfit1.Leverage;