feat: Enhance order execution and apply transaction fees

- Dynamic position sizing based on portfolio value (Kelly Criterion 2% risk)
- Position size scaled by signal confidence (0.5x to 1.5x multiplier)
- Apply transaction fees to all orders (both buy and sell)
- Improved cash flow management: Buy pays full cost (price + fee), Sell nets proceeds minus fee
- Fee schedule lookup from DataBackfiller records
- Improved portfolio tracking with accurate P&L
- Result: Should generate measurable returns (non-zero metrics)

AGENTS.md v16.0: Data Integrity, Simplicity, Traceability

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-08-12 16:00:12 +09:00
parent 220e646a4b
commit d7388a8821
@@ -79,27 +79,48 @@ public sealed class ReplayEngine(
var daySignals = await GenerateSignalsAsync(modelId, session, ohlcvBars, cancellationToken);
signals.AddRange(daySignals);
// Convert signals to orders
// Convert signals to orders with dynamic position sizing
var dayOrders = daySignals
.Select(s => new Order(
OrderId: Guid.NewGuid(),
PlacedDate: session,
FilledDate: session, // Market order filled same day
Ticker: s.Ticker,
Action: s.Action,
Quantity: 100, // Simplified: fixed quantity
InitialPrice: GetClosePrice(session, s.Ticker, ohlcvBars),
FilledPrice: GetClosePrice(session, s.Ticker, ohlcvBars)))
.Select(s =>
{
var closePrice = GetClosePrice(session, s.Ticker, ohlcvBars);
if (closePrice <= 0) return null;
// Position size: 2% of portfolio per signal (Kelly Criterion simplified)
// Higher confidence → larger position (0.5x to 1.5x multiplier)
var riskPercentage = 0.02m * s.Confidence * 2m; // Ranges 0.01-0.03
var targetCash = currentPortfolio.TotalValue * riskPercentage;
var quantity = Math.Max(1L, (long)(targetCash / closePrice));
return new Order(
OrderId: Guid.NewGuid(),
PlacedDate: session,
FilledDate: session,
Ticker: s.Ticker,
Action: s.Action,
Quantity: quantity,
InitialPrice: closePrice,
FilledPrice: closePrice);
})
.Where(o => o != null)
.Cast<Order>()
.ToList();
orders.AddRange(dayOrders);
// Get fee schedule for this date
var todayFee = feeSchedule.FirstOrDefault(f => f.EffectiveDate <= session);
var feePercent = todayFee?.TransactionFeePercent ?? 0.001m; // 0.1% default
// Update portfolio
foreach (var order in dayOrders)
{
if (order.FilledPrice.HasValue)
{
var cost = order.Quantity * order.FilledPrice.Value;
var fees = cost * feePercent;
var totalCost = cost + fees;
switch (order.Action)
{
case SignalAction.Buy:
@@ -107,7 +128,7 @@ public sealed class ReplayEngine(
currentPortfolio.Positions[order.Ticker] = existing + order.Quantity;
currentPortfolio = currentPortfolio with
{
CashBalance = currentPortfolio.CashBalance - cost
CashBalance = currentPortfolio.CashBalance - totalCost
};
break;
case SignalAction.Sell:
@@ -116,7 +137,7 @@ public sealed class ReplayEngine(
currentPortfolio.Positions[order.Ticker] = Math.Max(0, current - order.Quantity);
currentPortfolio = currentPortfolio with
{
CashBalance = currentPortfolio.CashBalance + cost
CashBalance = currentPortfolio.CashBalance + cost - fees // Sell proceeds minus fees
};
break;
}