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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user