From 4f6c7fdb86169fee578dbb96304dd05dfb445260 Mon Sep 17 00:00:00 2001 From: Richard Date: Thu, 2 Jul 2026 12:16:20 +0200 Subject: [PATCH] =?UTF-8?q?Phase=205-UI:=20Generischer=20Core-Trade-Log=20?= =?UTF-8?q?+=20modul=C3=BCbergreifendes=20Dashboard?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- Program.cs | 17 ++ Properties/Resources.Designer.cs | 10 + Properties/Resources.resx | 7 +- Resources/system_time.png | Bin 0 -> 1627 bytes Ui/LauncherForm.Designer.cs | 85 ++++---- Ui/LauncherForm.cs | 1 + Ui/Views/DashboardView.Designer.cs | 202 ++++++++++++++++++ Ui/Views/DashboardView.cs | 88 ++++++++ services/PersistenceService.cs | 28 ++- .../ServiceCollectionExtensions.cs | 1 + src/PolyTrader.Core/Models/TradeRecord.cs | 39 ++++ .../Persistence/ITradeLogRepository.cs | 22 ++ .../Mongo/MongoTradeLogRepository.cs | 41 ++++ 13 files changed, 500 insertions(+), 41 deletions(-) create mode 100644 Resources/system_time.png create mode 100644 Ui/Views/DashboardView.Designer.cs create mode 100644 Ui/Views/DashboardView.cs create mode 100644 src/PolyTrader.Core/Models/TradeRecord.cs create mode 100644 src/PolyTrader.Core/Persistence/ITradeLogRepository.cs create mode 100644 src/PolyTrader.Core/Persistence/Mongo/MongoTradeLogRepository.cs diff --git a/Program.cs b/Program.cs index 6688087..e0a6de4 100644 --- a/Program.cs +++ b/Program.cs @@ -176,6 +176,23 @@ internal static class Program return view; } }); + uiHost.RegisterView(new PolyTrader.Core.Modularity.ModuleView + { + Id = "core.dashboard", + Title = "Dashboard", + Group = "Core", + Order = 5, + PreferredWidth = 1200, + PreferredHeight = 650, + CreateControl = () => + { + var view = new PolyTraderSharp.Ui.Views.DashboardView(); + view.Initialize( + viewServices.GetRequiredService(), + viewServices.GetRequiredService()); + return view; + } + }); // TODO Phase 5-UI: registrierte IPolyTraderModule durchlaufen und module.RegisterUi(uiHost) aufrufen. var launcher = AppHost.Services.GetRequiredService(); diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index b93d4b0..b005fbd 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -200,6 +200,16 @@ namespace PolyTraderSharp.Properties { } } + /// + /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap system_time { + get { + object obj = ResourceManager.GetObject("system_time", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Sucht eine lokalisierte Ressource vom Typ System.Drawing.Bitmap. /// diff --git a/Properties/Resources.resx b/Properties/Resources.resx index fdb4711..cad3e1e 100644 --- a/Properties/Resources.resx +++ b/Properties/Resources.resx @@ -145,6 +145,9 @@ ..\Resources\coins_in_hand.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\error_log.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\Resources\add.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a @@ -169,7 +172,7 @@ ..\Resources\money_dollar.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - - ..\Resources\error_log.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\Resources\system_time.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a \ No newline at end of file diff --git a/Resources/system_time.png b/Resources/system_time.png new file mode 100644 index 0000000000000000000000000000000000000000..957c526a373b824cf6d58ed603313f646cf78865 GIT binary patch literal 1627 zcmV-h2Bi6kP)%rZDr(mHq`9%A*uZEeu!JN_xO}AvEiJ9Z zJGbV%N<-R24KRK5)eprw1r%K?&MwDhUE0#(<=o$~$5lI+(2$#Ab{xvAj* zDdM&;_Te!r@XAby0lKCMe_p*LNu11IF5oEiOYLQ7B*#Wf$^gD`mF|-7{)>av)cXl5 zZoXn+T!fGlE>S9kf2PD4+aPVQnHO+ncAM*=7m>xD`6D7A`?=sTqR8{r+wOJW& zjg&1-zJZ0w#D1L|GsAyOl~~4&TBq$UR?VM@__)}uk&%%Iv|0z3^*bDvwma>c%a0!0 z+}+)^c4JQV5nmr4NJ4kV_eprNq| z&CR#4bny~I$IM1UeLWHq6LG8gHfsN2Q>gU=V{NYKjLrAW9=yhDlLI zX)+}(6C#veM1+UKcCQn+Z?_;TGXt}uqeO*OUw=jP{LbS%ZzH54Y5l%$_vi68!?K~O ztKtfhVx|q{fCNusk_OR&tf2QsD*}UNQdw4y$5T@g5D*{^VK$p_wV^@uE|-gpRmA-H z+B(clN~Cd1HfeZ=TJI19RMl|17jwb4QITcn3S=XQS}+XB0!_BS>2!*c%x>>TT->9g zl(Sd@5Ey91)oa%f925wv)k21u#C(5$Khy!0_EIgL;$`R+cq5uVLW7}TSIr#kIu#Ft zHh(sK4qUo~UfP_~H=i~qI~x}A0PZt0GLV~_i#@w{qpYNqIMG1sWc1nXWVAlG_k9vd z>{6P5(7oYM+Ub%L@1#a&hNhR-a8OLHN8`BJJA-Mv%o$EsyXun%-2JuMlkvzMSX z!=SifrBmM|4-nr8o)FHPyFE)2<2-M%Sj54I7X!l#5Lr!Ag@V$mQ^;KTBuXl)k+s6z zXRdex#TC`aSdog7iW+1tUyK3Cj1F4|A|fJi`t(_Wj@e`qXslVYM)YRIB*uB3gH&f{ zC)(TFjxwmj5KuKY?(0@Rh4Nod;pOyYC_g^DU;lg>%Bs#FXJa<5-?%AA77`kY!lJ`y zYi$!c%FD||&+j}Q68exptm*o7e7bYT4w`cKewajV`8cmV^M&Pb4!8#6fxl2Jvn$@S zq4n~gSoU-pd5eMOmR9l#yQg5Fh!Yfq=%`sxOfssgPvhL#Gw&81Dm*~5cld_9Bs3iM zo|>aSpG+HfUMC|gk`3^UnI7kyTfP+gIwQ0;fj~?EHeXg6@UC+rKzS&TE;PZSkkK%I(**AoEHtip&9{68? Z0RVpDU5WYh$T OpenView("core.settings"); btn_terminal.Click += (_, _) => OpenView("core.terminal"); btn_jobs.Click += (_, _) => OpenView("core.jobs"); + btn_dashboard.Click += (_, _) => OpenView("core.dashboard"); btn_liveTrading.Click += (_, _) => CycleLiveTrading(); btn_demoTrading.Click += (_, _) => CycleDemoTrading(); miLegacy.Click += (_, _) => OpenLegacy(); diff --git a/Ui/Views/DashboardView.Designer.cs b/Ui/Views/DashboardView.Designer.cs new file mode 100644 index 0000000..428f1bd --- /dev/null +++ b/Ui/Views/DashboardView.Designer.cs @@ -0,0 +1,202 @@ +namespace PolyTraderSharp.Ui.Views +{ + partial class DashboardView + { + private System.ComponentModel.IContainer components = null; + + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Vom Komponenten-Designer generierter Code + + private void InitializeComponent() + { + this.pnlTop = new System.Windows.Forms.Panel(); + this.lblSummary = new System.Windows.Forms.Label(); + this.btnRefresh = new System.Windows.Forms.Button(); + this.dgvTrades = new System.Windows.Forms.DataGridView(); + this.colModule = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colAccount = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colMarket = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colOutcome = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colSide = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colEntry = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colExit = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colSize = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colPnl = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colPnlPct = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.colClosedAt = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.pnlTop.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.dgvTrades)).BeginInit(); + this.SuspendLayout(); + // + // pnlTop + // + this.pnlTop.Controls.Add(this.lblSummary); + this.pnlTop.Controls.Add(this.btnRefresh); + this.pnlTop.Dock = System.Windows.Forms.DockStyle.Top; + this.pnlTop.Location = new System.Drawing.Point(0, 0); + this.pnlTop.Name = "pnlTop"; + this.pnlTop.Size = new System.Drawing.Size(1100, 40); + this.pnlTop.TabIndex = 0; + // + // lblSummary + // + this.lblSummary.AutoSize = true; + this.lblSummary.Location = new System.Drawing.Point(148, 12); + this.lblSummary.Name = "lblSummary"; + this.lblSummary.Size = new System.Drawing.Size(63, 15); + this.lblSummary.TabIndex = 1; + this.lblSummary.Text = "—"; + // + // btnRefresh + // + this.btnRefresh.Location = new System.Drawing.Point(8, 6); + this.btnRefresh.Name = "btnRefresh"; + this.btnRefresh.Size = new System.Drawing.Size(130, 28); + this.btnRefresh.TabIndex = 0; + this.btnRefresh.Text = "Aktualisieren"; + this.btnRefresh.UseVisualStyleBackColor = true; + // + // dgvTrades + // + this.dgvTrades.AllowUserToAddRows = false; + this.dgvTrades.AllowUserToDeleteRows = false; + this.dgvTrades.AutoGenerateColumns = false; + this.dgvTrades.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dgvTrades.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + this.colModule, + this.colAccount, + this.colMarket, + this.colOutcome, + this.colSide, + this.colEntry, + this.colExit, + this.colSize, + this.colPnl, + this.colPnlPct, + this.colClosedAt}); + this.dgvTrades.Dock = System.Windows.Forms.DockStyle.Fill; + this.dgvTrades.Location = new System.Drawing.Point(0, 40); + this.dgvTrades.Name = "dgvTrades"; + this.dgvTrades.ReadOnly = true; + this.dgvTrades.RowHeadersVisible = false; + this.dgvTrades.Size = new System.Drawing.Size(1100, 560); + this.dgvTrades.TabIndex = 1; + // + // colModule + // + this.colModule.DataPropertyName = "Module"; + this.colModule.HeaderText = "Modul"; + this.colModule.Name = "colModule"; + this.colModule.Width = 110; + // + // colAccount + // + this.colAccount.DataPropertyName = "Account"; + this.colAccount.HeaderText = "Konto"; + this.colAccount.Name = "colAccount"; + this.colAccount.Width = 130; + // + // colMarket + // + this.colMarket.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.colMarket.DataPropertyName = "Market"; + this.colMarket.HeaderText = "Markt"; + this.colMarket.Name = "colMarket"; + // + // colOutcome + // + this.colOutcome.DataPropertyName = "Outcome"; + this.colOutcome.HeaderText = "Outcome"; + this.colOutcome.Name = "colOutcome"; + this.colOutcome.Width = 80; + // + // colSide + // + this.colSide.DataPropertyName = "Side"; + this.colSide.HeaderText = "Side"; + this.colSide.Name = "colSide"; + this.colSide.Width = 60; + // + // colEntry + // + this.colEntry.DataPropertyName = "EntryPrice"; + this.colEntry.HeaderText = "Entry"; + this.colEntry.Name = "colEntry"; + this.colEntry.Width = 70; + // + // colExit + // + this.colExit.DataPropertyName = "ExitPrice"; + this.colExit.HeaderText = "Exit"; + this.colExit.Name = "colExit"; + this.colExit.Width = 70; + // + // colSize + // + this.colSize.DataPropertyName = "Size"; + this.colSize.HeaderText = "Size"; + this.colSize.Name = "colSize"; + this.colSize.Width = 70; + // + // colPnl + // + this.colPnl.DataPropertyName = "RealizedPnl"; + this.colPnl.HeaderText = "PnL"; + this.colPnl.Name = "colPnl"; + this.colPnl.Width = 80; + // + // colPnlPct + // + this.colPnlPct.DataPropertyName = "PnlPercent"; + this.colPnlPct.HeaderText = "PnL %"; + this.colPnlPct.Name = "colPnlPct"; + this.colPnlPct.Width = 70; + // + // colClosedAt + // + this.colClosedAt.DataPropertyName = "ClosedAt"; + this.colClosedAt.HeaderText = "Geschlossen"; + this.colClosedAt.Name = "colClosedAt"; + this.colClosedAt.Width = 140; + // + // DashboardView + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.dgvTrades); + this.Controls.Add(this.pnlTop); + this.Name = "DashboardView"; + this.Size = new System.Drawing.Size(1100, 600); + this.pnlTop.ResumeLayout(false); + this.pnlTop.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.dgvTrades)).EndInit(); + this.ResumeLayout(false); + } + + #endregion + + private System.Windows.Forms.Panel pnlTop; + private System.Windows.Forms.Button btnRefresh; + private System.Windows.Forms.Label lblSummary; + private System.Windows.Forms.DataGridView dgvTrades; + private System.Windows.Forms.DataGridViewTextBoxColumn colModule; + private System.Windows.Forms.DataGridViewTextBoxColumn colAccount; + private System.Windows.Forms.DataGridViewTextBoxColumn colMarket; + private System.Windows.Forms.DataGridViewTextBoxColumn colOutcome; + private System.Windows.Forms.DataGridViewTextBoxColumn colSide; + private System.Windows.Forms.DataGridViewTextBoxColumn colEntry; + private System.Windows.Forms.DataGridViewTextBoxColumn colExit; + private System.Windows.Forms.DataGridViewTextBoxColumn colSize; + private System.Windows.Forms.DataGridViewTextBoxColumn colPnl; + private System.Windows.Forms.DataGridViewTextBoxColumn colPnlPct; + private System.Windows.Forms.DataGridViewTextBoxColumn colClosedAt; + } +} diff --git a/Ui/Views/DashboardView.cs b/Ui/Views/DashboardView.cs new file mode 100644 index 0000000..a1bbcc6 --- /dev/null +++ b/Ui/Views/DashboardView.cs @@ -0,0 +1,88 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Windows.Forms; +using PolyTrader.Core.Persistence; + +namespace PolyTraderSharp.Ui.Views +{ + /// + /// Modulübergreifendes Dashboard: zeigt die letzten Trades ALLER Module aus dem + /// generischen Core-Trade-Log (ITradeLogRepository) und eine Kurzauswertung. + /// Designbar (DashboardView.Designer.cs). + /// + public partial class DashboardView : UserControl + { + private ITradeLogRepository? _tradeLog; + private TradingState? _state; + + public DashboardView() + { + InitializeComponent(); + colEntry.DefaultCellStyle.Format = "F3"; + colExit.DefaultCellStyle.Format = "F3"; + colSize.DefaultCellStyle.Format = "F2"; + colPnl.DefaultCellStyle.Format = "F2"; + colPnlPct.DefaultCellStyle.Format = "F1"; + colClosedAt.DefaultCellStyle.Format = "dd.MM.yyyy HH:mm"; + btnRefresh.Click += (_, _) => LoadData(); + } + + public void Initialize(ITradeLogRepository tradeLog, TradingState state) + { + _tradeLog = tradeLog; + _state = state; + LoadData(); + } + + private void LoadData() + { + if (_tradeLog == null) return; + + var rows = _tradeLog.GetRecent(300).Select(r => new DashboardTradeRow + { + Module = r.ModuleName, + Account = ResolveAccount(r.AccountId, r.IsDemo), + Market = r.MarketQuestion, + Outcome = r.Outcome, + Side = r.Side, + EntryPrice = r.EntryPrice, + ExitPrice = r.ExitPrice, + Size = r.Size, + RealizedPnl = r.RealizedPnl, + PnlPercent = r.PnlPercent, + ClosedAt = r.ClosedAt + }).ToList(); + + dgvTrades.DataSource = new BindingList(rows); + + decimal totalPnl = rows.Sum(x => x.RealizedPnl); + var perModule = rows.GroupBy(x => x.Module).Select(g => $"{g.Key}: {g.Count()}"); + lblSummary.Text = $"{rows.Count} Trades | PnL gesamt: {totalPnl:F2} USDC | {string.Join(" · ", perModule)}"; + } + + private string ResolveAccount(int accountId, bool isDemo) + { + string suffix = isDemo ? " (Demo)" : ""; + if (_state != null && _state.Accounts.TryGetValue(accountId, out var acc) && !string.IsNullOrEmpty(acc.Name)) + return acc.Name + suffix; + return $"#{accountId}{suffix}"; + } + } + + /// Anzeige-Zeile für das Dashboard-Grid (Account bereits zu Name aufgelöst). + public class DashboardTradeRow + { + public string Module { get; set; } = string.Empty; + public string Account { get; set; } = string.Empty; + public string Market { 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 ClosedAt { get; set; } + } +} diff --git a/services/PersistenceService.cs b/services/PersistenceService.cs index 4230435..c736204 100644 --- a/services/PersistenceService.cs +++ b/services/PersistenceService.cs @@ -1,6 +1,7 @@ using System.Threading.Channels; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using PolyTrader.Core.Persistence; using PolyTrader.Modules.CopyTrading.Persistence; using PolyTraderSharp.Models; @@ -10,13 +11,15 @@ namespace PolyTraderSharp.Services { private readonly ChannelReader _tradeReader; private readonly ICopyTradeLogRepository _tradeLog; + private readonly ITradeLogRepository _coreTradeLog; private readonly TerminalLogger _logger; private readonly JobStatusRow _jobStatus; - public PersistenceService(ChannelReader tradeReader, ICopyTradeLogRepository tradeLog, TerminalLogger logger, JobManager jobManager) + public PersistenceService(ChannelReader tradeReader, ICopyTradeLogRepository tradeLog, ITradeLogRepository coreTradeLog, TerminalLogger logger, JobManager jobManager) { _tradeReader = tradeReader; _tradeLog = tradeLog; + _coreTradeLog = coreTradeLog; _logger = logger; _jobStatus = new JobStatusRow @@ -43,6 +46,7 @@ namespace PolyTraderSharp.Services // One-time index setup (moved out of hot loop) _tradeLog.EnsureIndexes(); + _coreTradeLog.EnsureIndexes(); // We do a loop waiting for items in the channel await foreach(var trade in _tradeReader.ReadAllAsync(stoppingToken)) @@ -72,7 +76,27 @@ namespace PolyTraderSharp.Services } _tradeLog.Insert(trade); - + + // Dual-Write: generischer, modulübergreifender Core-Trade-Log (fürs Dashboard). + _coreTradeLog.Insert(new TradeRecord + { + ModuleName = "CopyTrading", + AccountId = trade.AccountId, + IsDemo = trade.IsDemo, + TokenId = trade.TokenId, + MarketQuestion = trade.MarketQuestion, + Outcome = trade.Outcome, + Side = trade.Side, + EntryPrice = trade.EntryPrice, + ExitPrice = trade.ExitPrice, + Size = trade.Size, + RealizedPnl = trade.RealizedPnl, + PnlPercent = trade.PnlPercent, + OpenedAt = trade.OpenedAt, + ClosedAt = trade.ClosedAt, + ExitReason = trade.ExitReason + }); + _logger.Debug($"Saved ClosedTrade {trade.TradeId} to MongoDB"); _jobStatus.LastRun = DateTime.Now; } diff --git a/src/PolyTrader.Core/DependencyInjection/ServiceCollectionExtensions.cs b/src/PolyTrader.Core/DependencyInjection/ServiceCollectionExtensions.cs index 5357ddf..28c884b 100644 --- a/src/PolyTrader.Core/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/PolyTrader.Core/DependencyInjection/ServiceCollectionExtensions.cs @@ -15,6 +15,7 @@ namespace PolyTrader.Core.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); return services; } } diff --git a/src/PolyTrader.Core/Models/TradeRecord.cs b/src/PolyTrader.Core/Models/TradeRecord.cs new file mode 100644 index 0000000..360fb3b --- /dev/null +++ b/src/PolyTrader.Core/Models/TradeRecord.cs @@ -0,0 +1,39 @@ +using System; + +namespace PolyTraderSharp.Models +{ + /// + /// 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). + /// + public class TradeRecord + { + [MongoDB.Bson.Serialization.Attributes.BsonId] + public string Id { get; set; } = MongoDB.Bson.ObjectId.GenerateNewId().ToString(); + + /// Herkunftsmodul, z.B. "CopyTrading". + 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; + } +} diff --git a/src/PolyTrader.Core/Persistence/ITradeLogRepository.cs b/src/PolyTrader.Core/Persistence/ITradeLogRepository.cs new file mode 100644 index 0000000..b76e2bb --- /dev/null +++ b/src/PolyTrader.Core/Persistence/ITradeLogRepository.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using PolyTraderSharp.Models; + +namespace PolyTrader.Core.Persistence +{ + /// + /// Generischer, modulübergreifender Trade-Log (Collection "trade_log"). + /// Speist das Core-Dashboard mit den Trades aller Module. + /// + public interface ITradeLogRepository + { + void EnsureIndexes(); + void Insert(TradeRecord record); + + /// Die neuesten Einträge über alle Module (nach ClosedAt absteigend). + List GetRecent(int limit); + + List Find(Expression> predicate); + } +} diff --git a/src/PolyTrader.Core/Persistence/Mongo/MongoTradeLogRepository.cs b/src/PolyTrader.Core/Persistence/Mongo/MongoTradeLogRepository.cs new file mode 100644 index 0000000..aeb93d7 --- /dev/null +++ b/src/PolyTrader.Core/Persistence/Mongo/MongoTradeLogRepository.cs @@ -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 _col; + + public MongoTradeLogRepository(IMongoDatabase db) + { + _col = db.GetCollection("trade_log"); + } + + public void EnsureIndexes() + { + try + { + _col.Indexes.CreateOne(new CreateIndexModel( + Builders.IndexKeys.Descending(x => x.ClosedAt))); + _col.Indexes.CreateOne(new CreateIndexModel( + Builders.IndexKeys.Ascending(x => x.ModuleName))); + _col.Indexes.CreateOne(new CreateIndexModel( + Builders.IndexKeys.Ascending(x => x.AccountId))); + } + catch { } + } + + public void Insert(TradeRecord record) => _col.InsertOne(record); + + public List GetRecent(int limit) => + _col.Find(_ => true).SortByDescending(x => x.ClosedAt).Limit(limit).ToList(); + + public List Find(Expression> predicate) => + _col.Find(predicate).ToList(); + } +}