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); var daySignals = await GenerateSignalsAsync(modelId, session, ohlcvBars, cancellationToken);
signals.AddRange(daySignals); signals.AddRange(daySignals);
// Convert signals to orders // Convert signals to orders with dynamic position sizing
var dayOrders = daySignals var dayOrders = daySignals
.Select(s => new Order( .Select(s =>
OrderId: Guid.NewGuid(), {
PlacedDate: session, var closePrice = GetClosePrice(session, s.Ticker, ohlcvBars);
FilledDate: session, // Market order filled same day if (closePrice <= 0) return null;
Ticker: s.Ticker,
Action: s.Action, // Position size: 2% of portfolio per signal (Kelly Criterion simplified)
Quantity: 100, // Simplified: fixed quantity // Higher confidence → larger position (0.5x to 1.5x multiplier)
InitialPrice: GetClosePrice(session, s.Ticker, ohlcvBars), var riskPercentage = 0.02m * s.Confidence * 2m; // Ranges 0.01-0.03
FilledPrice: GetClosePrice(session, s.Ticker, ohlcvBars))) 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(); .ToList();
orders.AddRange(dayOrders); 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 // Update portfolio
foreach (var order in dayOrders) foreach (var order in dayOrders)
{ {
if (order.FilledPrice.HasValue) if (order.FilledPrice.HasValue)
{ {
var cost = order.Quantity * order.FilledPrice.Value; var cost = order.Quantity * order.FilledPrice.Value;
var fees = cost * feePercent;
var totalCost = cost + fees;
switch (order.Action) switch (order.Action)
{ {
case SignalAction.Buy: case SignalAction.Buy:
@@ -107,7 +128,7 @@ public sealed class ReplayEngine(
currentPortfolio.Positions[order.Ticker] = existing + order.Quantity; currentPortfolio.Positions[order.Ticker] = existing + order.Quantity;
currentPortfolio = currentPortfolio with currentPortfolio = currentPortfolio with
{ {
CashBalance = currentPortfolio.CashBalance - cost CashBalance = currentPortfolio.CashBalance - totalCost
}; };
break; break;
case SignalAction.Sell: case SignalAction.Sell:
@@ -116,7 +137,7 @@ public sealed class ReplayEngine(
currentPortfolio.Positions[order.Ticker] = Math.Max(0, current - order.Quantity); currentPortfolio.Positions[order.Ticker] = Math.Max(0, current - order.Quantity);
currentPortfolio = currentPortfolio with currentPortfolio = currentPortfolio with
{ {
CashBalance = currentPortfolio.CashBalance + cost CashBalance = currentPortfolio.CashBalance + cost - fees // Sell proceeds minus fees
}; };
break; break;
} }