Phase 5-UI: Generischer Core-Trade-Log + modulübergreifendes Dashboard

- Core: TradeRecord (Modell) + ITradeLogRepository (+ Mongo-Impl, Collection
  "trade_log"), in AddCorePersistence registriert. Realisiert den generischen,
  modulneutralen Trade-Log aus Entscheidung #2.
- Dual-Write: PersistenceService schreibt geschlossene Copy-Trades zusaetzlich als
  generischen TradeRecord (ModuleName="CopyTrading").
- Neue DashboardView (UserControl + Designer): zeigt die letzten Trades ALLER
  Module (GetRecent) mit Konto-Aufloesung + Kurzauswertung (Gesamt-PnL, Anzahl,
  Aufschluesselung je Modul). btn_dashboard im Launcher, View "core.dashboard".
- Build 0 Fehler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-07-02 12:16:20 +02:00
co-authored by Claude Opus 4.8
parent 8e2a92ad2e
commit 4f6c7fdb86
13 changed files with 500 additions and 41 deletions
@@ -15,6 +15,7 @@ namespace PolyTrader.Core.DependencyInjection
services.AddSingleton<IAccountRepository, MongoAccountRepository>();
services.AddSingleton<IMarketRepository, MongoMarketRepository>();
services.AddSingleton<IPositionRepository, MongoPositionRepository>();
services.AddSingleton<ITradeLogRepository, MongoTradeLogRepository>();
return services;
}
}
+39
View File
@@ -0,0 +1,39 @@
using System;
namespace PolyTraderSharp.Models
{
/// <summary>
/// Generischer, modulübergreifender Trade-Log-Eintrag (Collection "trade_log").
/// Jedes Modul, das Trades abschließt, schreibt hier einen neutralen Eintrag —
/// so kann das Core-Dashboard die Trades ALLER Module gemeinsam auswerten.
/// Modulspezifische Details (z.B. Copytrading-SourceTrader) bleiben im jeweiligen
/// Modul-Log (z.B. ClosedTrade).
/// </summary>
public class TradeRecord
{
[MongoDB.Bson.Serialization.Attributes.BsonId]
public string Id { get; set; } = MongoDB.Bson.ObjectId.GenerateNewId().ToString();
/// <summary>Herkunftsmodul, z.B. "CopyTrading".</summary>
public string ModuleName { get; set; } = string.Empty;
public int AccountId { get; set; }
public bool IsDemo { get; set; }
public string TokenId { get; set; } = string.Empty;
public string MarketQuestion { get; set; } = string.Empty;
public string Outcome { get; set; } = string.Empty;
public string Side { get; set; } = string.Empty;
public decimal EntryPrice { get; set; }
public decimal ExitPrice { get; set; }
public decimal Size { get; set; }
public decimal RealizedPnl { get; set; }
public decimal PnlPercent { get; set; }
public DateTime OpenedAt { get; set; }
public DateTime ClosedAt { get; set; }
public string ExitReason { get; set; } = string.Empty;
}
}
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using PolyTraderSharp.Models;
namespace PolyTrader.Core.Persistence
{
/// <summary>
/// Generischer, modulübergreifender Trade-Log (Collection "trade_log").
/// Speist das Core-Dashboard mit den Trades aller Module.
/// </summary>
public interface ITradeLogRepository
{
void EnsureIndexes();
void Insert(TradeRecord record);
/// <summary>Die neuesten Einträge über alle Module (nach ClosedAt absteigend).</summary>
List<TradeRecord> GetRecent(int limit);
List<TradeRecord> Find(Expression<Func<TradeRecord, bool>> predicate);
}
}
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using MongoDB.Driver;
using PolyTraderSharp.Models;
namespace PolyTrader.Core.Persistence.Mongo
{
public class MongoTradeLogRepository : ITradeLogRepository
{
private readonly IMongoCollection<TradeRecord> _col;
public MongoTradeLogRepository(IMongoDatabase db)
{
_col = db.GetCollection<TradeRecord>("trade_log");
}
public void EnsureIndexes()
{
try
{
_col.Indexes.CreateOne(new CreateIndexModel<TradeRecord>(
Builders<TradeRecord>.IndexKeys.Descending(x => x.ClosedAt)));
_col.Indexes.CreateOne(new CreateIndexModel<TradeRecord>(
Builders<TradeRecord>.IndexKeys.Ascending(x => x.ModuleName)));
_col.Indexes.CreateOne(new CreateIndexModel<TradeRecord>(
Builders<TradeRecord>.IndexKeys.Ascending(x => x.AccountId)));
}
catch { }
}
public void Insert(TradeRecord record) => _col.InsertOne(record);
public List<TradeRecord> GetRecent(int limit) =>
_col.Find(_ => true).SortByDescending(x => x.ClosedAt).Limit(limit).ToList();
public List<TradeRecord> Find(Expression<Func<TradeRecord, bool>> predicate) =>
_col.Find(predicate).ToList();
}
}