R8: Accounting- + Supervisor-Modul + Core-Datenfundament (S-0)
Portierung der beiden fehlenden Grundbausteine aus PolytraderSharp (voller Ausbau). Core S-0 (Datenfundament fuer Analyse/Forensik): - core_decision_journal + core_order_events (+ ReasonCode/Decision/OrderEvent-Enums), IDecisionJournal/IOrderEventLog mit fehlertoleranten EF-Impls (Handel bricht nie). - SignalId-Durchreichung TradeSignal -> ExecutionService -> core_trade_history; ExecutionService schreibt an jeder Verzweigung Journal/Order-Events. - JSONL-Log-Sink (LogJson + Dual-Sink), pure Analytik: RealizedPnlEngine (FIFO), TradeAnalytics, DossierBuilder. Migration AddAnalysisFoundation. Accounting-Modul (acc_): unabhaengiger IBKR-Kontoauszug (Activity Flex Query) hinter Interfaces mit Offline-Null-Stubs -> append-only Ledger + Periodenabrechnung/BWA + FX (USD/EUR) + CSV/PDF (PDFsharp/MigraDoc). Steuerschicht bewusst offen (Platzhalter-Tab). Kein Handel. Migration InitialAccounting. Supervisor-Modul (sup_): read-only OpenRouter-Agent (Function-Calling-Loop) + read-only Tool-Registry (8 Tools) + Profile + Dossier-Browser + Counterfactual-Job (Stub) + Tagesbericht/MCP-Light (opt-in). Migration InitialSupervisor. Verdrahtung: Program.cs (beide Module + Icons), slnx/App/Tests-Referenzen, provision-db.ps1, AppSettings-Sektionen, docs/konzepte, README. Tests: 79 -> 117 gruen (FIFO/KPIs/Dossier/JSONL, Classifier/Engine/FX/Idempotenz, OpenRouter/Registry/Agent/MCP, STA-Konstruktion beider neuen Fenster). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cbbedb2e0e
commit
2a312ca035
@@ -0,0 +1,155 @@
|
||||
using IBKRTrader.Core.Configuration;
|
||||
using IBKRTrader.Core.Logging;
|
||||
using IBKRTrader.Modules.Supervisor.Counterfactual;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace IBKRTrader.Modules.Supervisor.Persistence;
|
||||
|
||||
/// <summary>EF-Kontext des Supervisor-Moduls (gleiche MariaDB, Tabellen mit Präfix sup_).</summary>
|
||||
public class SupervisorDbContext : DbContext
|
||||
{
|
||||
public SupervisorDbContext(DbContextOptions<SupervisorDbContext> options) : base(options) { }
|
||||
|
||||
public DbSet<SupervisorReport> Reports => Set<SupervisorReport>();
|
||||
public DbSet<CounterfactualRecord> Counterfactuals => Set<CounterfactualRecord>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder b)
|
||||
{
|
||||
b.Entity<SupervisorReport>(e =>
|
||||
{
|
||||
e.ToTable("sup_reports");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).ValueGeneratedOnAdd();
|
||||
e.Property(x => x.Profile).HasMaxLength(50);
|
||||
e.Property(x => x.Model).HasMaxLength(120);
|
||||
e.Property(x => x.Question).HasMaxLength(4000);
|
||||
e.Property(x => x.Answer).HasColumnType("text");
|
||||
e.Property(x => x.ToolCallsJson).HasColumnType("text");
|
||||
e.HasIndex(x => x.CreatedAt);
|
||||
});
|
||||
|
||||
b.Entity<CounterfactualRecord>(e =>
|
||||
{
|
||||
e.ToTable("sup_counterfactuals");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).ValueGeneratedOnAdd();
|
||||
e.Property(x => x.SignalId).HasMaxLength(64);
|
||||
e.Property(x => x.Module).HasMaxLength(50);
|
||||
e.Property(x => x.Symbol).HasMaxLength(20);
|
||||
e.Property(x => x.Reason).HasMaxLength(40);
|
||||
e.Property(x => x.Side).HasMaxLength(10);
|
||||
e.Property(x => x.SignalPrice).HasPrecision(18, 4);
|
||||
e.Property(x => x.LaterPrice).HasPrecision(18, 4);
|
||||
e.Property(x => x.HypotheticalPnlPerShare).HasPrecision(18, 4);
|
||||
e.HasIndex(x => x.DecisionId).IsUnique(); // ein Ergebnis je Entscheidung
|
||||
e.HasIndex(x => x.CheckedAt);
|
||||
e.HasIndex(x => x.Reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Design-Time-Factory für EF-Tooling (dotnet ef). Connection aus env IBKRTRADER_MYSQL.</summary>
|
||||
public class SupervisorDbContextFactory : IDesignTimeDbContextFactory<SupervisorDbContext>
|
||||
{
|
||||
public SupervisorDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
var conn = Environment.GetEnvironmentVariable("IBKRTRADER_MYSQL")
|
||||
?? "Server=localhost;Port=3306;Database=ibkrtrader;User ID=root;Password=;";
|
||||
|
||||
var options = new DbContextOptionsBuilder<SupervisorDbContext>()
|
||||
.UseMySql(conn, DatabaseServerVersion.Value)
|
||||
.Options;
|
||||
|
||||
return new SupervisorDbContext(options);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Bericht-Ablage. Write fehlertolerant (Analyse darf nie an der Persistenz scheitern).</summary>
|
||||
public interface ISupervisorReportRepository
|
||||
{
|
||||
void Insert(SupervisorReport report);
|
||||
List<SupervisorReport> GetRecent(int limit);
|
||||
}
|
||||
|
||||
/// <summary>Counterfactual-Ablage. Write fehlertolerant.</summary>
|
||||
public interface ISupervisorCounterfactualRepository
|
||||
{
|
||||
HashSet<long> ExistingDecisionIds(IEnumerable<long> decisionIds);
|
||||
void Insert(CounterfactualRecord record);
|
||||
List<CounterfactualRecord> GetRecent(int limit);
|
||||
}
|
||||
|
||||
public sealed class EfSupervisorReportRepository : ISupervisorReportRepository
|
||||
{
|
||||
private readonly IDbContextFactory<SupervisorDbContext> _dbf;
|
||||
private readonly LoggingService _logger;
|
||||
|
||||
public EfSupervisorReportRepository(IDbContextFactory<SupervisorDbContext> dbf, LoggingService logger)
|
||||
{
|
||||
_dbf = dbf;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Insert(SupervisorReport report)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var db = _dbf.CreateDbContext();
|
||||
db.Reports.Add(report);
|
||||
db.SaveChanges();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("Supervisor", $"Report-Write fehlgeschlagen (ignoriert): {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public List<SupervisorReport> GetRecent(int limit)
|
||||
{
|
||||
using var db = _dbf.CreateDbContext();
|
||||
return db.Reports.AsNoTracking().OrderByDescending(r => r.CreatedAt).Take(limit).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EfSupervisorCounterfactualRepository : ISupervisorCounterfactualRepository
|
||||
{
|
||||
private readonly IDbContextFactory<SupervisorDbContext> _dbf;
|
||||
private readonly LoggingService _logger;
|
||||
|
||||
public EfSupervisorCounterfactualRepository(IDbContextFactory<SupervisorDbContext> dbf, LoggingService logger)
|
||||
{
|
||||
_dbf = dbf;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public HashSet<long> ExistingDecisionIds(IEnumerable<long> decisionIds)
|
||||
{
|
||||
var ids = decisionIds.ToList();
|
||||
using var db = _dbf.CreateDbContext();
|
||||
return db.Counterfactuals.AsNoTracking()
|
||||
.Where(c => ids.Contains(c.DecisionId))
|
||||
.Select(c => c.DecisionId)
|
||||
.ToHashSet();
|
||||
}
|
||||
|
||||
public void Insert(CounterfactualRecord record)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var db = _dbf.CreateDbContext();
|
||||
db.Counterfactuals.Add(record);
|
||||
db.SaveChanges();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn("Supervisor", $"Counterfactual-Write fehlgeschlagen (ignoriert): {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public List<CounterfactualRecord> GetRecent(int limit)
|
||||
{
|
||||
using var db = _dbf.CreateDbContext();
|
||||
return db.Counterfactuals.AsNoTracking().OrderByDescending(c => c.CheckedAt).Take(limit).ToList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user