Supervisor S-0a/b: Entscheidungsjournal, Order-Events, SignalId, JSONL-Logs
Datenfundament fuer die KI-Analyse (docs/konzepte/KONZEPT-Modul-Supervisor.md, Phase S-0). Sofortnutzen auch ohne KI: abfragbare Rejects, rekonstruierbare Order-Ketten. - core_decision_journal (DecisionRecord): JEDE Handelsentscheidung strukturiert - Decision (Executed/Rejected/Skipped/Failed) + ReasonCode-Enum (als String, erweiterbar) + Kontext-JSON (kulturinvariant) + MarketSlug/EndDate (counterfactual-ready). - core_order_events (OrderEventRecord): Order-Lifecycle als Daten (Placed/PlaceFailed/ LadderStart/LadderStep/FloorReplaced/DustAbort ...) inkl. CLOB-Response. - SignalId (GUID) auf CopySignal, durchgereicht bis ClosedTrade + Core-TradeRecord (Dual-Write) und in die Leiter (ExitLadderState.SignalId) -> Dossier-Korrelation. - CopyTradingEngine: ~20 Entscheidungs-Sites journalisieren zusaetzlich zum Freitext-Log (Mode/SellOnly/H3/MaxBuyPrice/PerMaster/TimeLimit/Budget/Balance/Minimum/SpamBlock/ Leiter/Ownership/NotFound/Teilverkauf/Inkonsistenz/Grace/Demo-Fill/-Close/Live-BUY). - SellLadderService: Order-Events fuer Start/Step/Floor-Replace/Dust; IOrderEventLog injiziert. - TerminalLogger: JSONL-Sink (eine Datei/Tag, LogJson pur) + CorrelationId-Support. Dual-Sink. - Journal-Writes fehlertolerant (brechen den Geld-Pfad nie). - Migrationen offline generiert UND angewendet (rein additiv): Core (Widen F1 + Journal), CopyTrading (SignalId), RF (Initial) - DB war bis AddMarketTakerFeeBps migriert, verifiziert. - Launcher-Designer: Account-Spalten-Instanziierung wiederhergestellt (war bei Designer- Reserialisierung verloren gegangen -> NullReference im Smoke). Tests: +5 (LogJson, Journal-Roundtrips, SignalId) + Journal-Assertions in Engine-Tests. Build 0 Fehler, 336 Tests gruen, --smoke-ui komplett gruen (alle Views inkl. RF gegen echte Tabellen). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
eac47369f0
commit
bf3e012f8b
@@ -24,6 +24,10 @@ namespace PolyTrader.Core.DependencyInjection
|
||||
services.AddSingleton<IPositionRepository, EfPositionRepository>();
|
||||
services.AddSingleton<ITradeLogRepository, EfTradeLogRepository>();
|
||||
|
||||
// S-0 (Supervisor-Konzept): Entscheidungsjournal + Order-Lifecycle-Events.
|
||||
services.AddSingleton<IDecisionJournal, EfDecisionJournal>();
|
||||
services.AddSingleton<IOrderEventLog, EfOrderEventLog>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,13 @@ namespace PolyTraderSharp.Models
|
||||
{
|
||||
public class CopySignal
|
||||
{
|
||||
/// <summary>
|
||||
/// Korrelations-ID (S-0, Supervisor-Konzept): verbindet dieses Signal mit allen daraus
|
||||
/// entstehenden Entscheidungen (core_decision_journal), Orders (core_order_events) und
|
||||
/// dem ClosedTrade. Wird bei Erzeugung automatisch vergeben.
|
||||
/// </summary>
|
||||
public string SignalId { get; set; } = Guid.NewGuid().ToString("N");
|
||||
|
||||
public int SourceTradeId { get; set; }
|
||||
public int TraderId { get; set; }
|
||||
public string MarketSlug { get; set; } = string.Empty;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
|
||||
namespace PolyTraderSharp.Models
|
||||
{
|
||||
/// <summary>Ausgang einer Handelsentscheidung im Entscheidungsjournal.</summary>
|
||||
public enum TradeDecision
|
||||
{
|
||||
Executed, // Aktion ausgeführt (Order platziert / Demo-Fill / Leiter gestartet)
|
||||
Rejected, // aktiv abgelehnt (Risk-/Plausibilitätsregel)
|
||||
Skipped, // bewusst übersprungen (z. B. ExitPending, Spam-Blockade, Modus)
|
||||
Failed // versucht, aber fehlgeschlagen (z. B. Order-Fehler)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strukturierter Grund einer Entscheidung (statt Freitext). Wird als STRING persistiert –
|
||||
/// neue Werte können gefahrlos ergänzt werden. Die Codes decken die heutigen
|
||||
/// TradeReasoning-/Reject-Stellen von Engine, Leiter und Monitor ab.
|
||||
/// </summary>
|
||||
public enum DecisionReason
|
||||
{
|
||||
None = 0,
|
||||
|
||||
// ----- Modus / Zustand -----
|
||||
ModeInactive, // Live-/Demo-Trading deaktiviert
|
||||
SellOnlyModeBuyBlocked, // SellOnly-Modus blockiert BUY
|
||||
TraderInactive, // Master nicht gefunden / inaktiv
|
||||
AccountInactive, // Account nicht gefunden / inaktiv
|
||||
|
||||
// ----- BUY-Pfad -----
|
||||
MaxBuyPriceExceeded, // Signalpreis über MaxBuyPrice
|
||||
ExitPendingBuySkip, // H3: SELL-Leiter aktiv – kein Zukauf
|
||||
TimeWindowLimitReached, // Zeitfenster-Budget (6h/24h/72h/None) erschöpft
|
||||
MarketBudgetExhausted, // PerMarket-Budget erschöpft
|
||||
PerMasterLimitReached, // PerMaster-Budget erschöpft
|
||||
InsufficientBalance, // verfügbares Guthaben reicht nicht
|
||||
BelowPolymarketMinimum, // Order unter Minimum (Shares/USDC)
|
||||
MarketExpiredOrTooClose, // EndDate-Filter
|
||||
DuplicateOrPendingOrder, // bereits offene/pending Order
|
||||
|
||||
// ----- SELL-Pfad -----
|
||||
SellSpamBlock, // SELL <20s nach letztem SELL
|
||||
LadderAlreadyActive, // Eskalationsleiter läuft bereits
|
||||
PositionNotFound, // keine passende Position im Portfolio
|
||||
OwnershipMismatch, // Position gehört anderem Trader (Safety)
|
||||
PartialSellBelowThreshold, // Teilverkauf unter Signifikanz-Schwelle
|
||||
MasterPositionInconsistent, // Master hält laut Tracking 0 Shares
|
||||
SyncGracePeriod, // kein Tracking + Haltezeit < Schonfrist
|
||||
|
||||
// ----- Ausführung -----
|
||||
OrderPlaced, // Order erfolgreich platziert
|
||||
OrderFailed, // CLOB-Fehler beim Platzieren
|
||||
DemoFilled, // Demo-Fill gebucht
|
||||
DemoClosed, // Demo-Position geschlossen
|
||||
LadderStarted, // SELL-Leiter gestartet
|
||||
LadderStartFailed, // Leiter-Startorder fehlgeschlagen
|
||||
LadderDustAbort, // H4: Dust-Rest unter Minimum – Leiter beendet
|
||||
ProfitTargetTriggered, // Take-Profit hat Exit ausgelöst
|
||||
SystemResolutionClose // System-Close bei Marktauflösung (TraderId==0)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Eine Zeile im Entscheidungsjournal (core_decision_journal): JEDE Handelsentscheidung –
|
||||
/// ausgeführt, abgelehnt oder übersprungen – strukturiert und abfragbar. Grundlage für
|
||||
/// Supervisor-Analysen („warum (nicht) gehandelt?") und Counterfactual-Auswertungen
|
||||
/// (MarketSlug/EndDate sind dafür bewusst enthalten). Siehe docs/konzepte/KONZEPT-Modul-Supervisor.md.
|
||||
/// </summary>
|
||||
public class DecisionRecord
|
||||
{
|
||||
public long Id { get; set; } // DB-Autoincrement
|
||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>Korrelation: verbindet Signal → Entscheidungen → Orders → ClosedTrade.</summary>
|
||||
public string SignalId { get; set; } = string.Empty;
|
||||
|
||||
public string ModuleName { get; set; } = string.Empty;
|
||||
public int AccountId { get; set; }
|
||||
public bool IsDemo { get; set; }
|
||||
public int SourceTraderId { get; set; }
|
||||
|
||||
public string TokenId { get; set; } = string.Empty;
|
||||
public string MarketSlug { get; set; } = string.Empty; // counterfactual-ready
|
||||
public string MarketQuestion { get; set; } = string.Empty;
|
||||
public string Side { get; set; } = string.Empty; // BUY/SELL
|
||||
public decimal SignalPrice { get; set; }
|
||||
public DateTime? MarketEndDate { get; set; } // counterfactual-ready
|
||||
|
||||
public TradeDecision Decision { get; set; }
|
||||
public DecisionReason Reason { get; set; }
|
||||
|
||||
/// <summary>Kompakte Kontext-Zahlen als JSON (Limitwerte, Budgets, berechnete Größen …).</summary>
|
||||
public string ContextJson { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Menschlicher Begründungstext (wie bisher im Log).</summary>
|
||||
public string Message { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
|
||||
namespace PolyTraderSharp.Models
|
||||
{
|
||||
/// <summary>Art eines Order-Lifecycle-Ereignisses (als String persistiert – erweiterbar).</summary>
|
||||
public enum OrderEventType
|
||||
{
|
||||
Placed, // Order ans CLOB gesendet, Ergebnis in Response
|
||||
PlaceFailed, // Platzierung fehlgeschlagen
|
||||
Cancelled, // Order storniert (inkl. Grund in DetailsJson)
|
||||
LadderStart, // erste GTC-Order der SELL-Leiter
|
||||
LadderStep, // Nachpreisen eine Stufe tiefer
|
||||
FloorHold, // Floor erreicht, Order ruht
|
||||
FloorReplaced, // K1b: fehlende Floor-Order neu platziert
|
||||
DustAbort, // H4: Leiter wegen Dust-Rest beendet
|
||||
StartupReconcileCancel, // K2: verwaiste Order beim Start storniert
|
||||
StaleCleanupCancel // Stale-Order-Cleanup hat storniert
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ein Order-Lifecycle-Ereignis (core_order_events): Platzierungen, CLOB-Antworten, Cancels,
|
||||
/// Leiter-Stufen – als Daten statt nur als Log. Zusammen mit dem Entscheidungsjournal ergibt das
|
||||
/// die vollständige, rekonstruierbare Kette je Signal (Dossier).
|
||||
/// </summary>
|
||||
public class OrderEventRecord
|
||||
{
|
||||
public long Id { get; set; } // DB-Autoincrement
|
||||
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public string SignalId { get; set; } = string.Empty;
|
||||
public string ModuleName { get; set; } = string.Empty;
|
||||
public int AccountId { get; set; }
|
||||
public string TokenId { get; set; } = string.Empty;
|
||||
|
||||
public OrderEventType EventType { get; set; }
|
||||
public string Side { get; set; } = string.Empty; // BUY/SELL
|
||||
public decimal Price { get; set; }
|
||||
public decimal AmountUsd { get; set; }
|
||||
public string OrderType { get; set; } = string.Empty; // GTC/GTD/FOK/MARKET
|
||||
|
||||
/// <summary>CLOB-Antwort ("OK" oder Fehlertext) bzw. Ergebnis der Aktion.</summary>
|
||||
public string Response { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Zusatzkontext als kompaktes JSON (z. B. Leiter-Stufe, Floor, Timeout-Grund).</summary>
|
||||
public string DetailsJson { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -34,5 +34,8 @@ namespace PolyTraderSharp.Models
|
||||
public DateTime OpenedAt { get; set; }
|
||||
public DateTime ClosedAt { get; set; }
|
||||
public string ExitReason { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Korrelation zum Entscheidungsjournal/Order-Events (leer bei Alt-Daten).</summary>
|
||||
public string SignalId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace PolyTrader.Core.Persistence.Ef
|
||||
public DbSet<Position> Positions => Set<Position>();
|
||||
public DbSet<MarketData> Markets => Set<MarketData>();
|
||||
public DbSet<TradeRecord> TradeLog => Set<TradeRecord>();
|
||||
public DbSet<DecisionRecord> DecisionJournal => Set<DecisionRecord>();
|
||||
public DbSet<OrderEventRecord> OrderEvents => Set<OrderEventRecord>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder b)
|
||||
{
|
||||
@@ -93,6 +95,52 @@ namespace PolyTrader.Core.Persistence.Ef
|
||||
e.HasIndex(x => x.ClosedAt);
|
||||
e.HasIndex(x => x.ModuleName);
|
||||
e.HasIndex(x => x.AccountId);
|
||||
e.Property(x => x.SignalId).HasMaxLength(40); // Korrelation zum Entscheidungsjournal
|
||||
});
|
||||
|
||||
// Entscheidungsjournal (S-0, Supervisor-Konzept): jede Handelsentscheidung strukturiert.
|
||||
b.Entity<DecisionRecord>(e =>
|
||||
{
|
||||
e.ToTable("core_decision_journal");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).ValueGeneratedOnAdd();
|
||||
e.Property(x => x.SignalId).HasMaxLength(40);
|
||||
e.Property(x => x.ModuleName).HasMaxLength(64);
|
||||
e.Property(x => x.TokenId).HasMaxLength(120);
|
||||
e.Property(x => x.MarketSlug).HasMaxLength(300);
|
||||
e.Property(x => x.MarketQuestion).HasMaxLength(1000);
|
||||
e.Property(x => x.Side).HasMaxLength(10);
|
||||
e.Property(x => x.SignalPrice).HasPrecision(18, 6);
|
||||
// Enums als String: lesbar in der DB, gefahrlos erweiterbar.
|
||||
e.Property(x => x.Decision).HasConversion<string>().HasMaxLength(20);
|
||||
e.Property(x => x.Reason).HasConversion<string>().HasMaxLength(50);
|
||||
e.Property(x => x.ContextJson).HasMaxLength(2000);
|
||||
e.Property(x => x.Message).HasMaxLength(2000);
|
||||
e.HasIndex(x => x.Timestamp);
|
||||
e.HasIndex(x => x.SignalId);
|
||||
e.HasIndex(x => new { x.AccountId, x.TokenId });
|
||||
e.HasIndex(x => x.Reason);
|
||||
});
|
||||
|
||||
// Order-Lifecycle-Events (S-0): Platzierungen/Antworten/Cancels/Leiter-Stufen als Daten.
|
||||
b.Entity<OrderEventRecord>(e =>
|
||||
{
|
||||
e.ToTable("core_order_events");
|
||||
e.HasKey(x => x.Id);
|
||||
e.Property(x => x.Id).ValueGeneratedOnAdd();
|
||||
e.Property(x => x.SignalId).HasMaxLength(40);
|
||||
e.Property(x => x.ModuleName).HasMaxLength(64);
|
||||
e.Property(x => x.TokenId).HasMaxLength(120);
|
||||
e.Property(x => x.EventType).HasConversion<string>().HasMaxLength(30);
|
||||
e.Property(x => x.Side).HasMaxLength(10);
|
||||
e.Property(x => x.Price).HasPrecision(18, 6);
|
||||
e.Property(x => x.AmountUsd).HasPrecision(18, 6);
|
||||
e.Property(x => x.OrderType).HasMaxLength(10);
|
||||
e.Property(x => x.Response).HasMaxLength(1000);
|
||||
e.Property(x => x.DetailsJson).HasMaxLength(2000);
|
||||
e.HasIndex(x => x.Timestamp);
|
||||
e.HasIndex(x => x.SignalId);
|
||||
e.HasIndex(x => new { x.AccountId, x.TokenId });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using PolyTraderSharp.Models;
|
||||
|
||||
namespace PolyTrader.Core.Persistence.Ef
|
||||
{
|
||||
/// <summary>
|
||||
/// EF-Implementierung des Entscheidungsjournals. Write ist bewusst fehlertolerant:
|
||||
/// ein Journal-/DB-Fehler darf den Geld-Pfad (Engine/Leiter) niemals brechen –
|
||||
/// er wird auf der Konsole gemeldet und verworfen.
|
||||
/// </summary>
|
||||
public class EfDecisionJournal : IDecisionJournal
|
||||
{
|
||||
private readonly IDbContextFactory<CoreDbContext> _factory;
|
||||
public EfDecisionJournal(IDbContextFactory<CoreDbContext> factory) => _factory = factory;
|
||||
|
||||
public void Write(DecisionRecord record)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var ctx = _factory.CreateDbContext();
|
||||
ctx.DecisionJournal.Add(record);
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[DecisionJournal] Write fehlgeschlagen (ignoriert): {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public List<DecisionRecord> Query(Expression<Func<DecisionRecord, bool>> predicate, int limit = 1000)
|
||||
{
|
||||
using var ctx = _factory.CreateDbContext();
|
||||
return ctx.DecisionJournal.AsNoTracking()
|
||||
.Where(predicate)
|
||||
.OrderByDescending(r => r.Timestamp)
|
||||
.Take(limit)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>EF-Implementierung des Order-Lifecycle-Logs (gleiche Robustheits-Garantie).</summary>
|
||||
public class EfOrderEventLog : IOrderEventLog
|
||||
{
|
||||
private readonly IDbContextFactory<CoreDbContext> _factory;
|
||||
public EfOrderEventLog(IDbContextFactory<CoreDbContext> factory) => _factory = factory;
|
||||
|
||||
public void Write(OrderEventRecord record)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var ctx = _factory.CreateDbContext();
|
||||
ctx.OrderEvents.Add(record);
|
||||
ctx.SaveChanges();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[OrderEventLog] Write fehlgeschlagen (ignoriert): {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public List<OrderEventRecord> Query(Expression<Func<OrderEventRecord, bool>> predicate, int limit = 1000)
|
||||
{
|
||||
using var ctx = _factory.CreateDbContext();
|
||||
return ctx.OrderEvents.AsNoTracking()
|
||||
.Where(predicate)
|
||||
.OrderByDescending(r => r.Timestamp)
|
||||
.Take(limit)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
+481
@@ -0,0 +1,481 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using PolyTrader.Core.Persistence.Ef;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PolyTrader.Core.Persistence.Ef.Migrations
|
||||
{
|
||||
[DbContext(typeof(CoreDbContext))]
|
||||
[Migration("20260716101647_AddDecisionJournalAndOrderEvents")]
|
||||
partial class AddDecisionJournalAndOrderEvents
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.13")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 64);
|
||||
|
||||
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("PolyTraderSharp.Models.AccountState", b =>
|
||||
{
|
||||
b.Property<int>("AccountId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ApiKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("varchar(256)");
|
||||
|
||||
b.Property<string>("ApiPassphrase")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("varchar(512)");
|
||||
|
||||
b.Property<string>("ApiSecret")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("varchar(512)");
|
||||
|
||||
b.Property<decimal>("AvailableBalance")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<bool>("CloseOnlyMode")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("HasOpenLimitOrders")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<bool>("IsDemo")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)");
|
||||
|
||||
b.Property<string>("PayoutAddress")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("varchar(128)");
|
||||
|
||||
b.Property<decimal>("PayoutLimitUsd")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("PrivateKey")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("varchar(512)");
|
||||
|
||||
b.Property<decimal>("TotalBalance")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("WalletAddress")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("varchar(128)");
|
||||
|
||||
b.HasKey("AccountId");
|
||||
|
||||
b.ToTable("core_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PolyTraderSharp.Models.DecisionRecord", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<int>("AccountId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContextJson")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("varchar(2000)");
|
||||
|
||||
b.Property<string>("Decision")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<bool>("IsDemo")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTime?>("MarketEndDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("MarketQuestion")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("varchar(1000)");
|
||||
|
||||
b.Property<string>("MarketSlug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("varchar(300)");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("varchar(2000)");
|
||||
|
||||
b.Property<string>("ModuleName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<string>("Side")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<string>("SignalId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("varchar(40)");
|
||||
|
||||
b.Property<decimal>("SignalPrice")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<int>("SourceTraderId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Reason");
|
||||
|
||||
b.HasIndex("SignalId");
|
||||
|
||||
b.HasIndex("Timestamp");
|
||||
|
||||
b.HasIndex("AccountId", "TokenId");
|
||||
|
||||
b.ToTable("core_decision_journal", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PolyTraderSharp.Models.MarketData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.Property<bool>("Active")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)");
|
||||
|
||||
b.Property<string>("ClobTokenIds")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("Closed")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("ConditionId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.Property<DateTime?>("EndDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<bool>("NegRisk")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("Outcomes")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Question")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("varchar(1000)");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("varchar(300)");
|
||||
|
||||
b.Property<int>("TakerFeeBps")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("core_markets", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PolyTraderSharp.Models.OrderEventRecord", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<int>("AccountId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("AmountUsd")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("DetailsJson")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("varchar(2000)");
|
||||
|
||||
b.Property<string>("EventType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("varchar(30)");
|
||||
|
||||
b.Property<string>("ModuleName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<string>("OrderType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("Response")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("varchar(1000)");
|
||||
|
||||
b.Property<string>("Side")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<string>("SignalId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("varchar(40)");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SignalId");
|
||||
|
||||
b.HasIndex("Timestamp");
|
||||
|
||||
b.HasIndex("AccountId", "TokenId");
|
||||
|
||||
b.ToTable("core_order_events", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PolyTraderSharp.Models.Position", b =>
|
||||
{
|
||||
b.Property<int>("AccountId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IsDemo")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.Property<decimal>("AmountUsd")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("ConditionId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.Property<decimal>("CurrentPrice")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<decimal>("CurrentValueUsd")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<decimal>("EntryPrice")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<DateTime?>("ExpiryDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("MarketQuestion")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("varchar(1000)");
|
||||
|
||||
b.Property<string>("MarketSlug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("varchar(300)");
|
||||
|
||||
b.Property<DateTime>("OpenedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Outcome")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)");
|
||||
|
||||
b.Property<string>("Side")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<decimal>("Size")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("SourceTraderAddress")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("varchar(128)");
|
||||
|
||||
b.Property<int>("SourceTraderId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("SourceTraderName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)");
|
||||
|
||||
b.HasKey("AccountId", "IsDemo", "TokenId");
|
||||
|
||||
b.ToTable("core_positions", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PolyTraderSharp.Models.TradeRecord", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<int>("AccountId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("ClosedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<decimal>("EntryPrice")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<decimal>("ExitPrice")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("ExitReason")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)");
|
||||
|
||||
b.Property<bool>("IsDemo")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("MarketQuestion")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("varchar(1000)");
|
||||
|
||||
b.Property<string>("ModuleName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<DateTime>("OpenedAt")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("Outcome")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("varchar(200)");
|
||||
|
||||
b.Property<decimal>("PnlPercent")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<decimal>("RealizedPnl")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("Side")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<string>("SignalId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("varchar(40)");
|
||||
|
||||
b.Property<decimal>("Size")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AccountId");
|
||||
|
||||
b.HasIndex("ClosedAt");
|
||||
|
||||
b.HasIndex("ModuleName");
|
||||
|
||||
b.ToTable("core_trade_log", (string)null);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PolyTrader.Core.Persistence.Ef.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDecisionJournalAndOrderEvents : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "SignalId",
|
||||
table: "core_trade_log",
|
||||
type: "varchar(40)",
|
||||
maxLength: 40,
|
||||
nullable: false,
|
||||
defaultValue: "")
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "core_decision_journal",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Timestamp = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
SignalId = table.Column<string>(type: "varchar(40)", maxLength: 40, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ModuleName = table.Column<string>(type: "varchar(64)", maxLength: 64, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
AccountId = table.Column<int>(type: "int", nullable: false),
|
||||
IsDemo = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||
SourceTraderId = table.Column<int>(type: "int", nullable: false),
|
||||
TokenId = table.Column<string>(type: "varchar(120)", maxLength: 120, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
MarketSlug = table.Column<string>(type: "varchar(300)", maxLength: 300, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
MarketQuestion = table.Column<string>(type: "varchar(1000)", maxLength: 1000, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Side = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
SignalPrice = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||
MarketEndDate = table.Column<DateTime>(type: "datetime(6)", nullable: true),
|
||||
Decision = table.Column<string>(type: "varchar(20)", maxLength: 20, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Reason = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ContextJson = table.Column<string>(type: "varchar(2000)", maxLength: 2000, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Message = table.Column<string>(type: "varchar(2000)", maxLength: 2000, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_core_decision_journal", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "core_order_events",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||
Timestamp = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
SignalId = table.Column<string>(type: "varchar(40)", maxLength: 40, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ModuleName = table.Column<string>(type: "varchar(64)", maxLength: 64, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
AccountId = table.Column<int>(type: "int", nullable: false),
|
||||
TokenId = table.Column<string>(type: "varchar(120)", maxLength: 120, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
EventType = table.Column<string>(type: "varchar(30)", maxLength: 30, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Side = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Price = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||
AmountUsd = table.Column<decimal>(type: "decimal(18,6)", precision: 18, scale: 6, nullable: false),
|
||||
OrderType = table.Column<string>(type: "varchar(10)", maxLength: 10, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
Response = table.Column<string>(type: "varchar(1000)", maxLength: 1000, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
DetailsJson = table.Column<string>(type: "varchar(2000)", maxLength: 2000, nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_core_order_events", x => x.Id);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_core_decision_journal_AccountId_TokenId",
|
||||
table: "core_decision_journal",
|
||||
columns: new[] { "AccountId", "TokenId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_core_decision_journal_Reason",
|
||||
table: "core_decision_journal",
|
||||
column: "Reason");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_core_decision_journal_SignalId",
|
||||
table: "core_decision_journal",
|
||||
column: "SignalId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_core_decision_journal_Timestamp",
|
||||
table: "core_decision_journal",
|
||||
column: "Timestamp");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_core_order_events_AccountId_TokenId",
|
||||
table: "core_order_events",
|
||||
columns: new[] { "AccountId", "TokenId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_core_order_events_SignalId",
|
||||
table: "core_order_events",
|
||||
column: "SignalId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_core_order_events_Timestamp",
|
||||
table: "core_order_events",
|
||||
column: "Timestamp");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "core_decision_journal");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "core_order_events");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SignalId",
|
||||
table: "core_trade_log");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,96 @@ namespace PolyTrader.Core.Persistence.Ef.Migrations
|
||||
b.ToTable("core_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PolyTraderSharp.Models.DecisionRecord", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<int>("AccountId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContextJson")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("varchar(2000)");
|
||||
|
||||
b.Property<string>("Decision")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("varchar(20)");
|
||||
|
||||
b.Property<bool>("IsDemo")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<DateTime?>("MarketEndDate")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("MarketQuestion")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("varchar(1000)");
|
||||
|
||||
b.Property<string>("MarketSlug")
|
||||
.IsRequired()
|
||||
.HasMaxLength(300)
|
||||
.HasColumnType("varchar(300)");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("varchar(2000)");
|
||||
|
||||
b.Property<string>("ModuleName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<string>("Reason")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("varchar(50)");
|
||||
|
||||
b.Property<string>("Side")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<string>("SignalId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("varchar(40)");
|
||||
|
||||
b.Property<decimal>("SignalPrice")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<int>("SourceTraderId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Reason");
|
||||
|
||||
b.HasIndex("SignalId");
|
||||
|
||||
b.HasIndex("Timestamp");
|
||||
|
||||
b.HasIndex("AccountId", "TokenId");
|
||||
|
||||
b.ToTable("core_decision_journal", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PolyTraderSharp.Models.MarketData", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
@@ -145,6 +235,79 @@ namespace PolyTrader.Core.Persistence.Ef.Migrations
|
||||
b.ToTable("core_markets", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PolyTraderSharp.Models.OrderEventRecord", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<int>("AccountId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<decimal>("AmountUsd")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("DetailsJson")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("varchar(2000)");
|
||||
|
||||
b.Property<string>("EventType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(30)
|
||||
.HasColumnType("varchar(30)");
|
||||
|
||||
b.Property<string>("ModuleName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("varchar(64)");
|
||||
|
||||
b.Property<string>("OrderType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
b.Property<string>("Response")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("varchar(1000)");
|
||||
|
||||
b.Property<string>("Side")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<string>("SignalId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("varchar(40)");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<string>("TokenId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(120)
|
||||
.HasColumnType("varchar(120)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SignalId");
|
||||
|
||||
b.HasIndex("Timestamp");
|
||||
|
||||
b.HasIndex("AccountId", "TokenId");
|
||||
|
||||
b.ToTable("core_order_events", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("PolyTraderSharp.Models.Position", b =>
|
||||
{
|
||||
b.Property<int>("AccountId")
|
||||
@@ -285,6 +448,11 @@ namespace PolyTrader.Core.Persistence.Ef.Migrations
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("varchar(10)");
|
||||
|
||||
b.Property<string>("SignalId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("varchar(40)");
|
||||
|
||||
b.Property<decimal>("Size")
|
||||
.HasPrecision(18, 6)
|
||||
.HasColumnType("decimal(18,6)");
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using PolyTraderSharp.Models;
|
||||
|
||||
namespace PolyTrader.Core.Persistence
|
||||
{
|
||||
/// <summary>
|
||||
/// Entscheidungsjournal (core_decision_journal). Write darf den Trading-Pfad NIEMALS brechen –
|
||||
/// Implementierungen fangen Persistenzfehler ab (Log statt Exception).
|
||||
/// </summary>
|
||||
public interface IDecisionJournal
|
||||
{
|
||||
void Write(DecisionRecord record);
|
||||
List<DecisionRecord> Query(Expression<Func<DecisionRecord, bool>> predicate, int limit = 1000);
|
||||
}
|
||||
|
||||
/// <summary>Order-Lifecycle-Log (core_order_events). Gleiche Robustheits-Garantie wie das Journal.</summary>
|
||||
public interface IOrderEventLog
|
||||
{
|
||||
void Write(OrderEventRecord record);
|
||||
List<OrderEventRecord> Query(Expression<Func<OrderEventRecord, bool>> predicate, int limit = 1000);
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,39 @@ namespace PolyTraderSharp.Services
|
||||
public LogLevel Level { get; }
|
||||
public DateTime Timestamp { get; }
|
||||
|
||||
public LogMessageEventArgs(string message, LogLevel level)
|
||||
/// <summary>Korrelations-ID (z. B. SignalId) für die Log-Forensik; leer wenn ohne Kontext.</summary>
|
||||
public string CorrelationId { get; }
|
||||
|
||||
public LogMessageEventArgs(string message, LogLevel level, string correlationId = "")
|
||||
{
|
||||
Message = message;
|
||||
Level = level;
|
||||
Timestamp = DateTime.Now;
|
||||
CorrelationId = correlationId ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reiner JSONL-Formatter für Log-Events (S-0, Supervisor-Konzept): eine JSON-Zeile je Event –
|
||||
/// append-fähig, streambar, maschinen-/KI-lesbar. Statisch und seiteneffektfrei → unit-getestet.
|
||||
/// </summary>
|
||||
public static class LogJson
|
||||
{
|
||||
public static string Format(LogMessageEventArgs e)
|
||||
{
|
||||
var obj = new
|
||||
{
|
||||
ts = e.Timestamp.ToString("yyyy-MM-ddTHH:mm:ss.fffzzz"),
|
||||
level = e.Level.ToString(),
|
||||
cid = string.IsNullOrEmpty(e.CorrelationId) ? null : e.CorrelationId,
|
||||
msg = e.Message
|
||||
};
|
||||
return System.Text.Json.JsonSerializer.Serialize(obj,
|
||||
new System.Text.Json.JsonSerializerOptions
|
||||
{
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +90,11 @@ namespace PolyTraderSharp.Services
|
||||
string logLine = $"[{e.Timestamp:HH:mm:ss}] {safeMsg}{Environment.NewLine}";
|
||||
|
||||
await File.AppendAllTextAsync(fullPath, logLine);
|
||||
|
||||
// S-0: zusätzlich JSONL (eine Datei je Tag, alle Level) – maschinen-/KI-lesbar,
|
||||
// Grundlage für den Log Viewer. Dual-Sink; Text-Sink später abschaltbar.
|
||||
string jsonlPath = Path.Combine(_logsDirectory, $"{e.Timestamp:yyyy-MM-dd}.jsonl");
|
||||
await File.AppendAllTextAsync(jsonlPath, LogJson.Format(e) + Environment.NewLine);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -70,9 +103,9 @@ namespace PolyTraderSharp.Services
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(string message, LogLevel level = LogLevel.Info)
|
||||
public void Log(string message, LogLevel level = LogLevel.Info, string correlationId = "")
|
||||
{
|
||||
var e = new LogMessageEventArgs(message, level);
|
||||
var e = new LogMessageEventArgs(message, level, correlationId);
|
||||
lock (_lock)
|
||||
{
|
||||
_history.Add(e);
|
||||
|
||||
Reference in New Issue
Block a user