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 0000000..957c526 Binary files /dev/null and b/Resources/system_time.png differ diff --git a/Ui/LauncherForm.Designer.cs b/Ui/LauncherForm.Designer.cs index 97b2b16..4fe2fe7 100644 --- a/Ui/LauncherForm.Designer.cs +++ b/Ui/LauncherForm.Designer.cs @@ -25,6 +25,7 @@ namespace PolyTraderSharp.Ui btn_settings = new ToolStripButton(); btn_terminal = new ToolStripButton(); btn_jobs = new ToolStripButton(); + btn_dashboard = new ToolStripButton(); toolstrip_quickbar = new ToolStrip(); btn_liveTrading = new ToolStripButton(); btn_demoTrading = new ToolStripButton(); @@ -37,9 +38,9 @@ namespace PolyTraderSharp.Ui toolstrip_quickbar.SuspendLayout(); statusStrip_info.SuspendLayout(); SuspendLayout(); - // + // // menuStrip - // + // menuStrip.ImageScalingSize = new Size(24, 24); menuStrip.Items.AddRange(new ToolStripItem[] { miDatei }); menuStrip.Location = new Point(0, 0); @@ -47,39 +48,39 @@ namespace PolyTraderSharp.Ui menuStrip.Padding = new Padding(9, 3, 0, 3); menuStrip.Size = new Size(2599, 35); menuStrip.TabIndex = 0; - // + // // miDatei - // + // miDatei.DropDownItems.AddRange(new ToolStripItem[] { miLegacy, miBeenden }); miDatei.Name = "miDatei"; miDatei.Size = new Size(69, 29); miDatei.Text = "Datei"; - // + // // miLegacy - // + // miLegacy.Name = "miLegacy"; miLegacy.Size = new Size(275, 34); miLegacy.Text = "Legacy-UI (alte Tabs)"; - // + // // miBeenden - // + // miBeenden.Name = "miBeenden"; miBeenden.Size = new Size(275, 34); miBeenden.Text = "Beenden"; - // + // // toolstrip_windows - // + // toolstrip_windows.AutoSize = false; toolstrip_windows.ImageScalingSize = new Size(64, 64); - toolstrip_windows.Items.AddRange(new ToolStripItem[] { btn_settings, btn_terminal, btn_jobs }); + toolstrip_windows.Items.AddRange(new ToolStripItem[] { btn_settings, btn_terminal, btn_jobs, btn_dashboard }); toolstrip_windows.Location = new Point(0, 35); toolstrip_windows.Name = "toolstrip_windows"; toolstrip_windows.Size = new Size(2599, 70); toolstrip_windows.TabIndex = 3; toolstrip_windows.Text = "toolStrip1"; - // + // // btn_settings - // + // btn_settings.BackColor = SystemColors.Control; btn_settings.Image = Properties.Resources.setting_tools; btn_settings.ImageScaling = ToolStripItemImageScaling.None; @@ -88,49 +89,58 @@ namespace PolyTraderSharp.Ui btn_settings.Size = new Size(80, 65); btn_settings.Text = "Settings"; btn_settings.TextImageRelation = TextImageRelation.ImageAboveText; - // + // // btn_terminal - // + // btn_terminal.Image = Properties.Resources.error_log; btn_terminal.ImageScaling = ToolStripItemImageScaling.None; btn_terminal.Name = "btn_terminal"; btn_terminal.Size = new Size(136, 65); btn_terminal.Text = "Terminal / Logs"; btn_terminal.TextImageRelation = TextImageRelation.ImageAboveText; - // + // // btn_jobs - // - btn_jobs.DisplayStyle = ToolStripItemDisplayStyle.Text; + // + btn_jobs.Image = Properties.Resources.system_time; + btn_jobs.ImageScaling = ToolStripItemImageScaling.None; btn_jobs.Name = "btn_jobs"; - btn_jobs.Size = new Size(100, 65); + btn_jobs.Size = new Size(106, 65); btn_jobs.Text = "Server Jobs"; + btn_jobs.TextImageRelation = TextImageRelation.ImageAboveText; // + // btn_dashboard + // + btn_dashboard.DisplayStyle = ToolStripItemDisplayStyle.Text; + btn_dashboard.Name = "btn_dashboard"; + btn_dashboard.Size = new Size(110, 65); + btn_dashboard.Text = "Dashboard"; + // // toolstrip_quickbar - // + // toolstrip_quickbar.ImageScalingSize = new Size(24, 24); toolstrip_quickbar.Items.AddRange(new ToolStripItem[] { btn_liveTrading, btn_demoTrading }); toolstrip_quickbar.Location = new Point(0, 105); toolstrip_quickbar.Name = "toolstrip_quickbar"; - toolstrip_quickbar.Size = new Size(2599, 28); + toolstrip_quickbar.Size = new Size(2599, 34); toolstrip_quickbar.TabIndex = 4; toolstrip_quickbar.Text = "toolStrip2"; - // + // // btn_liveTrading - // + // btn_liveTrading.DisplayStyle = ToolStripItemDisplayStyle.Text; btn_liveTrading.Name = "btn_liveTrading"; - btn_liveTrading.Size = new Size(200, 25); + btn_liveTrading.Size = new Size(137, 29); btn_liveTrading.Text = "LiveTrading (—)"; - // + // // btn_demoTrading - // + // btn_demoTrading.DisplayStyle = ToolStripItemDisplayStyle.Text; btn_demoTrading.Name = "btn_demoTrading"; - btn_demoTrading.Size = new Size(200, 25); + btn_demoTrading.Size = new Size(156, 29); btn_demoTrading.Text = "DemoTrading (—)"; - // + // // statusStrip_info - // + // statusStrip_info.BackColor = Color.Gainsboro; statusStrip_info.ImageScalingSize = new Size(24, 24); statusStrip_info.Items.AddRange(new ToolStripItem[] { lbl_trading, lbl_modules, lbl_ratelimit }); @@ -139,27 +149,27 @@ namespace PolyTraderSharp.Ui statusStrip_info.Size = new Size(2599, 32); statusStrip_info.TabIndex = 5; statusStrip_info.Text = "statusStrip1"; - // + // // lbl_trading - // + // lbl_trading.Name = "lbl_trading"; lbl_trading.Size = new Size(97, 25); lbl_trading.Text = "Trading: —"; - // + // // lbl_modules - // + // lbl_modules.Name = "lbl_modules"; lbl_modules.Size = new Size(100, 25); lbl_modules.Text = "Module: —"; - // + // // lbl_ratelimit - // + // lbl_ratelimit.Name = "lbl_ratelimit"; lbl_ratelimit.Size = new Size(66, 25); lbl_ratelimit.Text = "API: —"; - // + // // LauncherForm - // + // AutoScaleDimensions = new SizeF(10F, 25F); AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(2599, 165); @@ -194,6 +204,7 @@ namespace PolyTraderSharp.Ui private ToolStripButton btn_settings; private ToolStripButton btn_terminal; private ToolStripButton btn_jobs; + private ToolStripButton btn_dashboard; private ToolStrip toolstrip_quickbar; private ToolStripButton btn_liveTrading; private ToolStripButton btn_demoTrading; diff --git a/Ui/LauncherForm.cs b/Ui/LauncherForm.cs index e6a4c06..d6260c3 100644 --- a/Ui/LauncherForm.cs +++ b/Ui/LauncherForm.cs @@ -32,6 +32,7 @@ namespace PolyTraderSharp.Ui btn_settings.Click += (_, _) => 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(); + } +}