@backtest-kit/ui 13.5.0 → 14.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +189 -170
  2. package/build/modules/frontend/build/assets/{Article-C7I5Jc2B.js → Article-CJq_3XCa.js} +1 -1
  3. package/build/modules/frontend/build/assets/{Background-xwzJo61H.js → Background-xfzkOWU4.js} +1 -1
  4. package/build/modules/frontend/build/assets/{IconPhoto-DrGLW-rX.js → IconPhoto-C03KsEBk.js} +1 -1
  5. package/build/modules/frontend/build/assets/{KeyboardArrowLeft-BpxUzr7-.js → KeyboardArrowLeft-bxIYZMcN.js} +1 -1
  6. package/build/modules/frontend/build/assets/{PictureAsPdfOutlined-BTv5MH3G.js → PictureAsPdfOutlined-BF2HVAsQ.js} +1 -1
  7. package/build/modules/frontend/build/assets/{Refresh-DixrXBVq.js → Refresh-DdJstAqE.js} +1 -1
  8. package/build/modules/frontend/build/assets/emitters-Bc8GPUEm.js +1 -0
  9. package/build/modules/frontend/build/assets/{hasRouteMatch-C0K-M4jy.js → hasRouteMatch-Cp7g01zK.js} +1 -1
  10. package/build/modules/frontend/build/assets/{html2canvas-Xo2SFHzw.js → html2canvas-Dt1QJQhu.js} +1 -1
  11. package/build/modules/frontend/build/assets/{index-D6fbkGDu.js → index-BNUzSs1I.js} +1 -1
  12. package/build/modules/frontend/build/assets/{index-CwqHMGtu.js → index-C1E7RvS-.js} +1 -1
  13. package/build/modules/frontend/build/assets/{index-DFbdj6KX.js → index-C2Y1SjOE.js} +1 -1
  14. package/build/modules/frontend/build/assets/{index-CtY-cILX.js → index-CPb9eGRj.js} +1 -1
  15. package/build/modules/frontend/build/assets/{index-wMYoZqi-.js → index-D4Et3yR7.js} +1 -1
  16. package/build/modules/frontend/build/assets/{index-DeRxtBVy.js → index-D6o_GY5U.js} +1 -1
  17. package/build/modules/frontend/build/assets/{index-BO8_0_6f.js → index-D9Uifv6Q.js} +1 -1
  18. package/build/modules/frontend/build/assets/{index-DP-GjNGC.js → index-DVm8eb9u.js} +1 -1
  19. package/build/modules/frontend/build/assets/{index-kQlTYlpK.js → index-Djrg7_-q.js} +8 -8
  20. package/build/modules/frontend/build/assets/{index-wq7wFKoe.js → index-Dv74783d.js} +1 -1
  21. package/build/modules/frontend/build/assets/{index-D7cEguAr.js → index-FvAL5guC.js} +1 -1
  22. package/build/modules/frontend/build/assets/{index-S9fsmR5b.js → index-YIWBv-S7.js} +1 -1
  23. package/build/modules/frontend/build/assets/{index-Giwjzoy7.js → index-qAqr3b-l.js} +1 -1
  24. package/build/modules/frontend/build/assets/{index.es-BiwqIRwO.js → index.es-DKkSWzSw.js} +1 -1
  25. package/build/modules/frontend/build/assets/{markdownit-By9-eJJI.js → markdownit-DMpqEulr.js} +1 -1
  26. package/build/modules/frontend/build/index.html +1 -1
  27. package/package.json +5 -5
  28. package/build/modules/frontend/build/assets/emitters-ByKSsASU.js +0 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # 📊 @backtest-kit/ui
4
4
 
5
- > Full-stack UI framework for visualizing cryptocurrency trading signals, backtests, and real-time market data. Combines a Node.js backend server with a React dashboard - all in one package.
5
+ > The web cockpit for [backtest-kit](https://www.npmjs.com/package/backtest-kit). A self-hosted dashboard that turns a running backtest or live session into screens you can read: portfolio cards, KPI boards, candlestick charts with signal overlays, a notification feed, a strategy heatmap, markdown reports, a dump-file explorer, a live manual-control panel, and an in-browser Pine Script editor.
6
6
 
7
7
  ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot16.png)
8
8
 
@@ -14,241 +14,260 @@ Interactive dashboard for backtest-kit with signal visualization, candle charts,
14
14
 
15
15
  📚 **[Backtest Kit Docs](https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html)** | 🌟 **[GitHub](https://github.com/tripolskypetr/backtest-kit)**
16
16
 
17
- > **New to backtest-kit?** The fastest way to get a real, production-ready setup is to clone the [reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example) — a fully working news-sentiment AI trading system with LLM forecasting, multi-timeframe data, and a documented February 2026 backtest. Start there instead of from scratch.
17
+ > **New here?** The fastest real setup is to clone the [reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example) — a working news-sentiment AI trading system with LLM forecasting, multi-timeframe data, and a documented February 2026 backtest. Start there, not from scratch.
18
18
 
19
- ## ✨ Features
19
+ ```bash
20
+ npm install @backtest-kit/ui backtest-kit
21
+ ```
20
22
 
21
- - 📈 **Interactive Charts**: Candlestick visualization with Lightweight Charts (1m, 15m, 1h timeframes)
22
- - 🎯 **Signal Tracking**: View opened, closed, scheduled, and cancelled signals with full details
23
- - 📊 **Risk Analysis**: Monitor risk rejections and position management
24
- - 🔔 **Notifications**: Real-time notification system for all trading events
25
- - 💹 **Trailing & Breakeven**: Visualize trailing stop/take and breakeven events
26
- - 🌐 **Multi-Exchange**: Support for 100+ exchanges via CCXT integration
27
- - 🎨 **Material Design**: Beautiful UI with MUI 5 and Mantine components
28
- - 🌍 **i18n Ready**: Internationalization support built-in
23
+ ```typescript
24
+ import { serve } from "@backtest-kit/ui";
25
+ serve("0.0.0.0", 60050); // http://localhost:60050
26
+ ```
29
27
 
30
- ## 📋 What It Does
28
+ One call boots the server and serves the dashboard. Everything below describes **what each screen shows you** — the layout, the data on it, and what you can do there.
31
29
 
32
- `@backtest-kit/ui` provides both backend API and frontend dashboard:
30
+ ---
33
31
 
34
- | Component | Description |
35
- |-----------|-------------|
36
- | **`serve()`** | Start HTTP server with REST API endpoints |
37
- | **`getRouter()`** | Get expressjs-compatible router for custom middleware integration |
32
+ ## The shell
38
33
 
39
- ## 🚀 Installation
34
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot19.png)
40
35
 
41
- ```bash
42
- npm install @backtest-kit/ui backtest-kit ccxt
43
- ```
36
+ Every page sits inside a common frame: a top **app bar** with the logo (click to toggle home), a horizontally-scrollable row of section tabs, a live **notification bell**, a fullscreen toggle, and a GitHub link. A thin **progress bar** under the app bar animates whenever a screen is loading data. Below the bar, most screens open with a **breadcrumb strip** (Main › Section › …) that doubles as the action bar — refresh, download, print, and mode-switch buttons live there.
44
37
 
45
- ## 📖 Usage
38
+ The home screen (`/main`) is a **launchpad**: three labelled groups of large colored tiles, each tile a doorway to one section, with a status banner up top summarizing the engine. The groups:
46
39
 
47
- ### Quick Start - Launch Dashboard
40
+ - **Application** Portfolio Overview, PNL Performance, System Logs
41
+ - **Live** — Notifications, Pending Status, Dump Explorer
42
+ - **Other** — Markdown Reports, Price Charts, Heatmap
48
43
 
49
- ```typescript
50
- import { serve } from '@backtest-kit/ui';
44
+ ---
51
45
 
52
- // Start the UI server
53
- serve('0.0.0.0', 60050);
46
+ ## Portfolio Overview `/overview`
54
47
 
55
- // Dashboard available at http://localhost:60050
56
- ```
48
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot1.png)
57
49
 
58
- ### Custom Logger Integration
50
+ Closed trading signals, grouped by symbol, split across **Backtest** and **Live** tabs at the top.
59
51
 
60
- ```typescript
61
- import { setLogger } from '@backtest-kit/ui';
52
+ <details>
53
+ <summary>What's on the page</summary>
62
54
 
63
- setLogger({
64
- log: (msg) => console.log(`[UI] ${msg}`),
65
- warn: (msg) => console.warn(`[UI] ${msg}`),
66
- error: (msg) => console.error(`[UI] ${msg}`),
67
- });
68
- ```
55
+ Each symbol gets a section of cards; every card is one closed signal showing position type (long/short), entry price, take-profit and stop-loss levels, and the realized **PNL in both amount and percent**. Where a position used dollar-cost averaging or partial closes, the card displays the **DCA entry count** and **partial-close count**. A Backtest/Live tab toggle switches the dataset; the list supports **JSON export** and manual refresh. Layout is a scrollable tabbed container with a decorative background.
69
56
 
70
- ## 📐 Dashboard Revenue Math
57
+ </details>
71
58
 
72
- The **Revenue** metrics on the dashboard are calculated in **dollar terms** by summing the `pnlCost` field from all closed signals within each time window.
59
+ ## PNL Performance (Dashboard) `/dashboard` · `/dashboard/:mode`
73
60
 
74
- ### Dollar PnL formula
61
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot13.png)
75
62
 
76
- ```
77
- revenue[window] = Σ signal.pnl.pnlCost (for all closed signals in that window)
78
- ```
63
+ The KPI board. Aggregates trading performance **across all symbols** for the chosen mode.
79
64
 
80
- `pnlCost` is computed by the backend (`toProfitLossDto`) as:
65
+ <details>
66
+ <summary>What's on the page</summary>
81
67
 
82
- ```
83
- pnlCost = (pnlPercentage / 100) × pnlEntries
84
- ```
68
+ Inside a breadcrumb header (`KPI BACKTEST` / `KPI LIVE`) the body is a declarative field grid (`dashboard_fields`) of live widgets fed by four aggregated measures pulled per-symbol and summed:
85
69
 
86
- | Field | Source | Description |
87
- |-------|--------|-------------|
88
- | `pnl.pnlCost` | `IStorageSignalRow` | Absolute P&L in USD the only value summed for revenue |
89
- | `pnl.pnlPercentage` | `IStorageSignalRow` | Percentage P&L (accounts for DCA-weighted entry price, slippage, and fees) |
90
- | `pnl.pnlEntries` | `IStorageSignalRow` | Total invested capital in USD sum of all entry costs (`Σ entry.cost`) |
70
+ - **Four revenue cards** (`SingleValueWidget`) Today / Yesterday / 7 days / 31 days, each showing profit in USDT with a trade-count caption, **color-coded** red (loss) / green (profit) / orange (flat).
71
+ - **Trade-performance donut** (`SpeedDonutWidget`) — Failed vs. Successful vs. Total signal counts.
72
+ - **Daily-trades chart** (`ChartWidget`) a per-day series of total / resolved / rejected trades.
73
+ - **Success-rate panel** (`SuccessRateWidget`) per symbol (icon + display name), broken into take-profit / stop-loss / close-resolved / close-rejected counts.
74
+ - **Signal grid** (`SignalGridWidget`) the paginated signal table for the active mode.
91
75
 
92
- **Example** (1 DCA entry at $100, position closed +5%):
76
+ The breadcrumb actions: **Download** (exports the raw signals as a timestamped JSON blob), **Switch to LIVE / BACKTEST** (jumps between modes), and **Refresh manually** (clears the signal cache and reloads). A modal loader covers the board while it recomputes.
93
77
 
94
- | DCA entries | `pnlEntries` | `pnlPercentage` | `pnlCost` |
95
- |:-----------:|-------------:|----------------:|----------:|
96
- | 1 | $100 | 5 % | +$5.00 |
97
- | 2 | $200 | 5 % | +$10.00 |
98
- | 3 | $300 | 5 % | +$15.00 |
78
+ </details>
99
79
 
100
- ### Time windows
80
+ ## System Logs — `/logs`
101
81
 
102
- The anchor point depends on execution mode:
82
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot14.png)
103
83
 
104
- - **Backtest mode** latest `updatedAt` across all closed signals (time windows are relative to the end of the run)
105
- - **Live mode** — `Date.now()` (wall-clock time)
84
+ A virtualized, filterable feed of the engine's runtime log.
106
85
 
107
- | Window | Range |
108
- |--------|-------|
109
- | Today | `>= startOf(anchorDay)` |
110
- | Yesterday | `[anchorDay − 1d, anchorDay)` |
111
- | 7 days | `>= anchorDay − 7d` |
112
- | 31 days | `>= anchorDay − 31d` |
86
+ <details>
87
+ <summary>What's on the page</summary>
113
88
 
114
- Revenue and signal count are tracked separately for each window and aggregated across all symbols on the Dashboard.
89
+ Each entry is a row with a **type badge** (Debug / Info / Warn / Log), the log **topic**, a **timestamp**, and the raw JSON arguments rendered in monospace. A search prompt filters by **keyword or regex**. The whole log exports as a JSON file. Virtualized so it stays smooth over very long histories.
115
90
 
116
- ## 📐 Position PNL Math
91
+ </details>
117
92
 
118
- ### Effective entry price (DCA-weighted)
93
+ ## Notifications `/notifications`
119
94
 
120
- When multiple DCA entries exist, the effective open price is a **cost-weighted harmonic mean**:
95
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot46.png)
121
96
 
122
- ```
123
- effectivePrice = Σcost / Σ(cost / price)
124
- ```
97
+ The event feed for the entire signal lifecycle.
125
98
 
126
- This is the correct formula for fixed-dollar entries (not simple average), because buying $100 worth at different prices gives different coin quantities.
99
+ <details>
100
+ <summary>What's on the page</summary>
127
101
 
128
- ### Partial closes (PP/PL)
102
+ Color-coded cards, one per event — **opens, closes, schedules, errors** — each showing symbol, position, PNL, and the entry / exit / TP / SL prices. **Infinite-scroll** pagination loads more as you go; clicking a card opens a **detailed modal** for that event. Manual refresh pulls the latest activity. The same notifications drive the bell badge in the app bar.
129
103
 
130
- Each partial stores a `costBasisAtClose` snapshot — the running dollar cost-basis **before** that partial fired. This avoids replaying the full entry history on every call.
104
+ </details>
131
105
 
132
- **Cost-basis replay:**
106
+ ## Pending Status — `/status` → `/status/:id` → `/status/:id/control`
133
107
 
134
- ```
135
- for each partial[i]:
136
- closedDollar += (percent[i] / 100) × costBasisAtClose[i]
137
- remainingCostBasis = costBasisAtClose[i] × (1 - percent[i] / 100)
108
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot18.png)
138
109
 
139
- # DCA entries added AFTER the last partial are appended:
140
- remainingCostBasis += Σ entry.cost for entries[lastEntryCount..]
110
+ A three-level drill-down for *live* positions: list detail → manual control.
141
111
 
142
- totalClosedPercent = closedDollar / totalInvested × 100
143
- ```
112
+ <details>
113
+ <summary>What's on the page</summary>
144
114
 
145
- **Effective price through partials** is computed iteratively so that a partial sell does not change the entry price of the remaining coins:
115
+ **List (`/status`)** active signals grouped by strategy, rendered as a grid of strategy buttons; click one to inspect it.
146
116
 
147
- ```
148
- # partial[0]:
149
- effPrice = costBasisAtClose[0] / Σ(cost/price for entries[0..cnt[0]])
150
-
151
- # partial[j]:
152
- remainingCB = prev.costBasisAtClose × (1 - prev.percent / 100)
153
- oldCoins = remainingCB / effPrice ← coins still held
154
- newCoins = Σ(cost/price for DCA entries between j-1 and j)
155
- effPrice = (remainingCB + newCost) / (oldCoins + newCoins)
156
- ```
117
+ **Detail (`/status/:id`)** — the full state of one pending signal as a structured field view (`status_fields`): entry, exit, effective (blended) price, DCA entry count and partial-close count, and live PnL. Header actions: **Manual Control**, **Print** (renders the signal's fields to a downloadable PDF/markdown), **Download** (JSON), **Refresh**. Empty states read *"Loading…"* or *"No pending signal."*
157
118
 
158
- ### toProfitLossDtoweighted PNL with slippage & fees
119
+ **Manual Control (`/status/:id/control`)** an operator panel (`RecordView`, expand-all) where you intervene in a live position by hand through four **multi-step wizard modals**: **Open Pending**, **Average Buy**, **Close Pending**, and **Breakeven**. Each is launched from the panel via an emitter, walks you through *Briefing → Input → Submit*, fires the commit, and reloads the record. Export to JSON is available. (See **The modal system** below for how these are built.)
159
120
 
160
- **Without partials:**
121
+ </details>
161
122
 
162
- ```
163
- priceOpenSlip = effectivePrice × (1 ± slippage)
164
- priceCloseSlip = priceClose × (1 ∓ slippage)
123
+ ## Markdown Reports — `/report`
165
124
 
166
- pnlPercentage = (priceCloseSlip - priceOpenSlip) / priceOpenSlip × 100
167
- fee = CC_PERCENT_FEE × (1 + priceCloseSlip / priceOpenSlip)
168
- pnlPercentage -= fee
169
- ```
125
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot20.png)
170
126
 
171
- **With partials dollar-weighted sum:**
127
+ Rendered strategy-performance reports for Backtest and Live runs.
172
128
 
173
- ```
174
- weight[i] = (percent[i] / 100 × costBasisAtClose[i]) / totalInvested
129
+ <details>
130
+ <summary>What's on the page</summary>
175
131
 
176
- totalWeightedPnl = Σ weight[i] × pnl[i] # each partial at its own effectivePrice
177
- + remainingWeight × pnlRemaining # rest closed at final priceClose
132
+ A grid of strategy buttons, **grouped by type and sorted by signal volume**; selecting one renders its markdown report inline. Each report downloads as **markdown, PDF, or raw JSON**. Manual refresh regenerates the content. This is the human-facing view of the engine's analytics (strategy / breakeven / risk / partial / drawdown / schedule / performance / sync reports).
178
133
 
179
- fee = CC_PERCENT_FEE # open (once)
180
- + Σ CC_PERCENT_FEE × weight[i] × (closeSlip[i] / openSlip[i]) # per partial
181
- + CC_PERCENT_FEE × remainingWeight × (closeSlip / openSlip) # final close
134
+ </details>
182
135
 
183
- pnlPercentage = totalWeightedPnl - fee
184
- pnlCost = pnlPercentage / 100 × totalInvested
185
- ```
136
+ ## Price Charts `/price_chart` → `/price_chart/:symbol` → `/price_chart/:symbol/:interval`
186
137
 
187
- | Field | Description |
188
- |-------|-------------|
189
- | `totalInvested` | `Σ entry.cost` (or `CC_POSITION_ENTRY_COST` if no `_entry`) |
190
- | `weight[i]` | Real dollar share of each partial relative to `totalInvested` |
191
- | `effectivePrice` at partial `i` | Computed via iterative `costBasisAtClose` replay up to `partials[i]` |
192
- | `priceOpen` in result | `getEffectivePriceOpen(signal)` — DCA-weighted harmonic mean across all entries |
138
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot33.png)
193
139
 
194
- ## 🖥️ Dashboard Views
140
+ Interactive candlesticks powered by TradingView Lightweight Charts.
195
141
 
196
- The frontend provides specialized views for different trading events:
142
+ <details>
143
+ <summary>What's on the page</summary>
197
144
 
198
- | View | Description |
199
- |------|-------------|
200
- | **Signal Opened** | Entry details with chart visualization |
201
- | **Signal Closed** | Exit details with PnL analysis |
202
- | **Signal Scheduled** | Pending orders awaiting activation |
203
- | **Signal Cancelled** | Cancelled orders with reasons |
204
- | **Risk Rejection** | Signals rejected by risk management |
205
- | **Partial Profit/Loss** | Partial position closures |
206
- | **Trailing Stop/Take** | Trailing adjustments visualization |
207
- | **Breakeven** | Breakeven level adjustments |
145
+ You navigate **by symbol, then by interval** (1m / 15m / 1h) to view price history. The chart overlays the active signal's lines: **entry**, **take-profit (green)**, and **stop-loss (red)**. Supports chart-image export and clicking through to signal detail. Built on the shared `ChartWidget`.
208
146
 
209
- Each view includes:
210
- - 📋 Detailed information form
211
- - 📈 1m, 15m, 1h candlestick charts
212
- - 📥 JSON export for all data
147
+ </details>
213
148
 
214
- ## 💡 Why Use @backtest-kit/ui?
149
+ ## Heatmap `/heat`
215
150
 
216
- Instead of building custom dashboards:
151
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot31.png)
217
152
 
218
- **Without backtest-kit**
153
+ A color-coded performance matrix across every tracked symbol.
219
154
 
220
- ```typescript
221
- // Without @backtest-kit/ui
222
- // Build your own React app
223
- // Implement chart components
224
- // Create signal visualization
225
- // Handle notifications
226
- // Write API endpoints
227
- // ... weeks of development
228
- ```
155
+ <details>
156
+ <summary>What's on the page</summary>
229
157
 
230
- **With backtest-kit**
158
+ Cells are colored by performance; each shows **win rate, profit factor, Sharpe ratio** and other aggregated metrics per symbol. The whole heatmap exports as **JSON, markdown report, or PDF**, with manual refresh to recalculate the statistics. (The same heat report download lives on the home screen's action bar.)
231
159
 
232
- ```typescript
233
- // ✅ With @backtest-kit/ui
234
- import { serve } from '@backtest-kit/ui';
160
+ </details>
235
161
 
236
- serve(); // Full dashboard ready!
237
- ```
162
+ ## Dump Explorer `/dump` · `/dump/:search`
163
+
164
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot12.png)
165
+
166
+ A file browser for everything the engine wrote to disk.
167
+
168
+ <details>
169
+ <summary>What's on the page</summary>
170
+
171
+ A **tree-structured** browser of backtest output and artifact files. Icons mark file type — image, JSON, plain text, or generic. Clicking a file opens a **full-screen preview modal**. Keyword search filters the tree; manual refresh rebuilds it. Backed by the `FileTreeWidget`.
172
+
173
+ </details>
174
+
175
+ ## About & Setup — `/about` · `/about/setup`
176
+
177
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot36.png)
178
+
179
+ Framework information (`/about`) and a **Setup** view (`/about/setup`) showing the resolved configuration / environment of the running instance.
180
+
181
+ ---
182
+
183
+ ## Widgets — the reusable pieces screens are built from
184
+
185
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot8.png)
186
+
187
+ <details>
188
+ <summary>The building blocks</summary>
189
+
190
+ - **ChartWidget** — Lightweight Charts candlestick/line rendering with signal overlays (Price Charts, signal detail).
191
+ - **SignalGridWidget** — the paginated signal table (offset pagination + async item iterator).
192
+ - **StatusWidget** — live workload status tiles.
193
+ - **SuccessRateWidget** / **SpeedDonutWidget** — donut gauges for win-rate and throughput.
194
+ - **SingleValueWidget** / **IndicatorValueWidget** — single-KPI and indicator-value cards.
195
+ - **AveragingWidget** — DCA-ladder / effective-price visualization.
196
+ - **PartialWidget** — partial-close breakdown.
197
+ - **FileTreeWidget** — the dump-explorer tree.
198
+
199
+ Shared chrome lives in `components/common/`: `AppHeader`, a `Markdown` renderer, the `CodeEditor`, `NotificationView`, `Tooltip`, `IconPhoto`, `ErrorView`, decorative `Background` / `BottomImage`. Providers (`AlertProvider`, `ErrorProvider`, `LayoutModalProvider`, `StatusInfo`, `Translate`) wrap the app for alerts, error boundaries, modal stacking, the status banner, and i18n.
200
+
201
+ </details>
202
+
203
+ ## The modal system — a window manager with navigation history
204
+
205
+ ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot26.png)
206
+
207
+ The dashboard is, at its core, a **modal window manager**. Two complementary mechanisms sit on top of [react-declarative](https://github.com/react-declarative)'s `useModalManager` / `useWizardModal`, both of which keep their own **navigation history** so modals stack, go back, and close as a managed stack rather than ad-hoc dialogs.
208
+
209
+ ### 1. Global modal registry — tabbed detail modals on a managed stack
210
+
211
+ One provider wraps the whole app and registers **~25 detail modals** — one per signal-lifecycle event and commit type — each wired to a `layoutService` subject. Anywhere in the app, pushing to a subject (e.g. `pickSignalSubject.next(signal)`) `push`es the matching modal onto the **managed stack**; because the stack tracks depth, opening a modal *from inside* another (e.g. clicking a related signal) stacks it, and the title-bar **Back** arrow `pop`s you to the previous one. `closeModalSubject` / `ctx.clear()` tears the whole stack down centrally. The provider also hosts global **prompt**, **alert**, and **document-download/preview** (`useOpenDocument`) flows.
212
+
213
+ These are the modals that open when you click a notification card, a signal in a grid, or a risk event — every point in a position's life has a dedicated, fielded modal, each with the same Back / Print / Search / Copy / **ActionMenu** / Close title bar and the same StockChart candle tabs.
214
+
215
+ <details>
216
+ <summary>What's registered (every modal hook)</summary>
217
+
218
+ Each hook below is a `pick*` opener subscribed to its `layoutService.pick*Subject`; the hook renders a modal that displays the event's DTO through its `assets/*_fields.tsx` schema, inside the tabbed shell + `<ActionMenu />` title bar described above:
219
+
220
+ | Hook | Opens a modal for |
221
+ |---|---|
222
+ | `useSignalView` | a generic signal (`signal_fields`) |
223
+ | `useRiskView` | a risk rejection (`risk_fields`) |
224
+ | `useSignalOpenedView` / `useSignalClosedView` / `useSignalScheduledView` / `useSignalCancelledView` | the four signal-notification kinds |
225
+ | `useSignalNotifyView` | an info notification |
226
+ | `useSignalSyncOpenView` / `useSignalSyncCloseView` | broker-sync open/close |
227
+ | `useActivateScheduledView` | a scheduled signal activating |
228
+ | `useAverageBuyCommitView` | a DCA-rung commit |
229
+ | `useClosePendingView` / `useCancelScheduledView` | close / cancel commits |
230
+ | `usePartialProfitAvailableView` / `usePartialProfitCommitView` | partial-profit available + committed |
231
+ | `usePartialLossAvailableView` / `usePartialLossCommitView` | partial-loss available + committed |
232
+ | `useBreakevenAvailableView` / `useBreakevenCommitView` | breakeven available + committed |
233
+ | `useTrailingStopView` / `useTrailingTakeView` | trailing stop / take |
234
+ | `useDumpContentView` | a dump-file preview |
235
+
236
+ These are the modals that open when you click a notification card, a signal in a grid, or a risk event — every point in a position's life has a dedicated, fielded modal, each with the same Back / Print / Search / Copy / **ActionMenu** / Close title bar.
237
+
238
+ </details>
239
+
240
+ ### 2. Manual-control wizard modals (per-operation, own history)
241
+
242
+ The four operations on the Manual Control screen are **multi-step wizards**, each built with `useWizardModal` over a private `createMemoryHistory()` — so each modal has its *own* back/forward navigation through three routed steps.
243
+
244
+ <details>
245
+ <summary>The wizard anatomy (shared by all four)</summary>
246
+
247
+ Folders: `useOpenPendingModal`, `useAverageBuyModal`, `useClosePendingModal`, `useBreakevenModal`. Each contains `useXModal.tsx` (the `useWizardModal` config — title, fullscreen size request, close button, `onSubmit` → reload), `routes.tsx` (three `IWizardModal` routes), `steps.ts` (the stepper labels), `view/{BriefView,FormView,SubmitView}.tsx`, and `components/StatusCard.tsx`. The page (`ControlView`) subscribes to four emitters (`commitOpenPendingEmitter`, `commitAverageBuyEmitter`, `commitClosePendingEmitter`, `commitBreakevenEmitter`) and calls `pickData()` to launch the matching wizard.
248
+
249
+ The three steps (Open Position example):
250
+
251
+ 1. **Brief** — fetches and shows the symbol + current effective price (`controlViewService.getAveragePrice`) with a *"verify the symbol AND the price"* warning. `Next → /form`, `Close` dismisses.
252
+ 2. **Form** — collects direction (long/short), amount in USDT, and a note; **live-computes coin quantity** = amount ÷ price as a read-only field; warns that the position opens *immediately*. `Back → /brief`, `Next → /submit`.
253
+ 3. **Submit** — fires the commit (`controlViewService.commitOpenPending` / `commitAverageBuy` / `commitClosePending` / `commitBreakeven`) and shows a `StatusCard` in **loading → success / error** state; `Close` finalizes (`beginSave`) and the underlying record reloads.
254
+
255
+ Average Buy, Close Pending, and Breakeven follow the same Brief→Form→Submit shape with operation-specific form fields and StatusCard copy.
256
+
257
+ </details>
238
258
 
239
- **Benefits:**
259
+ ## Field schemas (`assets/*_fields.tsx`) — every DTO rendered as a real form
240
260
 
241
- - 📊 Production-ready trading dashboard out of the box
242
- - 📈 Professional chart visualization with price lines and markers
243
- - 🔔 Complete notification system for all trading events
244
- - 🎨 Beautiful Material Design interface
245
- - ⚡ Fast development - focus on strategy, not UI
246
- - 🛡️ Full TypeScript support
261
+ The 24 files in `assets/` are the **declarative field schemas** that drive both the detail pages and every modal above. Each exports a default `TypedField[]` array (react-declarative), so a DTO is shown with typed, labeled, validated, grouped fields — and, where useful, live widgets — instead of a raw JSON dump. Nothing in a signal's life is hidden from the operator.
247
262
 
248
- ## 🤝 Contribute
263
+ <details>
264
+ <summary>The schemas and who imports them</summary>
249
265
 
250
- Fork/PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
266
+ - **`dashboard_fields`** (→ `DashboardPage`) — not a text form but a **widget grid**: revenue cards, donut, daily-trades chart, success-rate panel, signal grid (see PNL Performance above).
267
+ - **`signal_fields`** (→ `StatusView` detail + print, `useSignalView`) and **`status_fields`** (→ `StatusView`) — the full pending/closed signal record.
268
+ - **`risk_fields`** (→ `useRiskView`), **`setup_fields`** (→ About/Setup view).
269
+ - **Lifecycle/commit schemas** (each → its `use*View` hook in the global registry): `signal_opened_fields`, `signal_closed_fields`, `signal_scheduled_fields`, `signal_cancelled_fields`, `signal_notify_fields`, `signal_sync_open_fields`, `signal_sync_close_fields`, `activate_scheduled_fields`, `average_buy_commit_fields`, `close_pending_commit_fields`, `cancel_scheduled_commit_fields`, `breakeven_available_fields`, `breakeven_commit_fields`, `partial_profit_available_fields`, `partial_profit_commit_fields`, `partial_loss_available_fields`, `partial_loss_commit_fields`, `trailing_stop_fields`, `trailing_take_fields`.
251
270
 
252
- ## 📜 License
271
+ These are the largest source files in the package (most 27–37 KB) precisely because they describe every field of every lifecycle DTO — the schemas *are* the UI's knowledge of the engine's data model.
253
272
 
254
- MIT © [tripolskypetr](https://github.com/tripolskypetr)
273
+ </details>
@@ -1 +1 @@
1
- import{b as c,f as v}from"./index-kQlTYlpK.js";const h=c(v("path",{d:"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"}),"Article");export{h as A};
1
+ import{b as c,f as v}from"./index-Djrg7_-q.js";const h=c(v("path",{d:"M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"}),"Article");export{h as A};
@@ -1 +1 @@
1
- import{f as n,S as o}from"./index-kQlTYlpK.js";const d=()=>n(o,{children:"\n body {\n background-color: #ddd !important;\n }\n "});export{d as B};
1
+ import{f as n,S as o}from"./index-Djrg7_-q.js";const d=()=>n(o,{children:"\n body {\n background-color: #ddd !important;\n }\n "});export{d as B};
@@ -1 +1 @@
1
- import{f as e,i as t,B as r,V as s}from"./index-kQlTYlpK.js";const o=({className:o,symbol:i,style:a,sx:n})=>e(s,{children:async()=>{try{const s=(await t.symbolGlobalService.getSymbolMap())[i],l=(null==s?void 0:s.logo)||(null==s?void 0:s.icon),c=(null==s?void 0:s.color)||"#ccc";return e(r,{className:o,sx:{position:"relative",width:24,height:24,borderRadius:"50%",display:"flex",alignItems:"center",justifyContent:"center",background:l?"transparent":c,...n},style:a,children:l?e("img",{loading:"lazy",crossOrigin:"anonymous",src:l,alt:i,style:{width:"100%",height:"100%",borderRadius:"50%",objectFit:"contain"},onError:e=>{const t=e.target,r=t.parentElement;r&&(r.style.background=c,t.style.display="none")}}):e(r,{sx:{width:"60%",height:"60%",borderRadius:"50%",backgroundColor:"rgba(255, 255, 255, 0.2)"}})})}catch(s){return e(r,{className:o,sx:{position:"relative",width:24,height:24,borderRadius:"50%",display:"flex",alignItems:"center",justifyContent:"center",background:"#ccc",...n},style:a,children:e(r,{sx:{width:"60%",height:"60%",borderRadius:"50%",backgroundColor:"rgba(255, 255, 255, 0.2)"}})})}}});export{o as I};
1
+ import{f as e,i as t,B as r,V as s}from"./index-Djrg7_-q.js";const o=({className:o,symbol:i,style:a,sx:n})=>e(s,{children:async()=>{try{const s=(await t.symbolGlobalService.getSymbolMap())[i],l=(null==s?void 0:s.logo)||(null==s?void 0:s.icon),c=(null==s?void 0:s.color)||"#ccc";return e(r,{className:o,sx:{position:"relative",width:24,height:24,borderRadius:"50%",display:"flex",alignItems:"center",justifyContent:"center",background:l?"transparent":c,...n},style:a,children:l?e("img",{loading:"lazy",crossOrigin:"anonymous",src:l,alt:i,style:{width:"100%",height:"100%",borderRadius:"50%",objectFit:"contain"},onError:e=>{const t=e.target,r=t.parentElement;r&&(r.style.background=c,t.style.display="none")}}):e(r,{sx:{width:"60%",height:"60%",borderRadius:"50%",backgroundColor:"rgba(255, 255, 255, 0.2)"}})})}catch(s){return e(r,{className:o,sx:{position:"relative",width:24,height:24,borderRadius:"50%",display:"flex",alignItems:"center",justifyContent:"center",background:"#ccc",...n},style:a,children:e(r,{sx:{width:"60%",height:"60%",borderRadius:"50%",backgroundColor:"rgba(255, 255, 255, 0.2)"}})})}}});export{o as I};
@@ -1 +1 @@
1
- import{b as o,f as r}from"./index-kQlTYlpK.js";const a=o(r("path",{d:"M15.41 16.59 10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"}),"KeyboardArrowLeft");export{a as K};
1
+ import{b as o,f as r}from"./index-Djrg7_-q.js";const a=o(r("path",{d:"M15.41 16.59 10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z"}),"KeyboardArrowLeft");export{a as K};
@@ -1 +1 @@
1
- import{b as h,f as c}from"./index-kQlTYlpK.js";const v=h(c("path",{d:"M20 2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H8V4h12v12zM4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm12 6V9c0-.55-.45-1-1-1h-2v5h2c.55 0 1-.45 1-1zm-2-3h1v3h-1V9zm4 2h1v-1h-1V9h1V8h-2v5h1zm-8 0h1c.55 0 1-.45 1-1V9c0-.55-.45-1-1-1H9v5h1v-2zm0-2h1v1h-1V9z"}),"PictureAsPdfOutlined");export{v as P};
1
+ import{b as h,f as c}from"./index-Djrg7_-q.js";const v=h(c("path",{d:"M20 2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H8V4h12v12zM4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm12 6V9c0-.55-.45-1-1-1h-2v5h2c.55 0 1-.45 1-1zm-2-3h1v3h-1V9zm4 2h1v-1h-1V9h1V8h-2v5h1zm-8 0h1c.55 0 1-.45 1-1V9c0-.55-.45-1-1-1H9v5h1v-2zm0-2h1v1h-1V9z"}),"PictureAsPdfOutlined");export{v as P};
@@ -1 +1 @@
1
- import{bI as e,d as t,r as a,_ as s,f as i,bJ as n,j as o,bK as r,k as d,bL as m,g as u,u as p,s as x,b as c}from"./index-kQlTYlpK.js";const h=e(),b=["className","component","disableGutters","fixed","maxWidth","classes"],l=m(),f=h("div",{name:"MuiContainer",slot:"Root",overridesResolver:(e,t)=>{const{ownerState:a}=e;return[t.root,t[`maxWidth${o(String(a.maxWidth))}`],a.fixed&&t.fixed,a.disableGutters&&t.disableGutters]}}),g=e=>r({props:e,name:"MuiContainer",defaultTheme:l});const W=function(e={}){const{createStyledComponent:r=f,useThemeProps:m=g,componentName:p="MuiContainer"}=e,x=r(({theme:e,ownerState:a})=>t({width:"100%",marginLeft:"auto",boxSizing:"border-box",marginRight:"auto",display:"block"},!a.disableGutters&&{paddingLeft:e.spacing(2),paddingRight:e.spacing(2),[e.breakpoints.up("sm")]:{paddingLeft:e.spacing(3),paddingRight:e.spacing(3)}}),({theme:e,ownerState:t})=>t.fixed&&Object.keys(e.breakpoints.values).reduce((t,a)=>{const s=a,i=e.breakpoints.values[s];return 0!==i&&(t[e.breakpoints.up(s)]={maxWidth:`${i}${e.breakpoints.unit}`}),t},{}),({theme:e,ownerState:a})=>t({},"xs"===a.maxWidth&&{[e.breakpoints.up("xs")]:{maxWidth:Math.max(e.breakpoints.values.xs,444)}},a.maxWidth&&"xs"!==a.maxWidth&&{[e.breakpoints.up(a.maxWidth)]:{maxWidth:`${e.breakpoints.values[a.maxWidth]}${e.breakpoints.unit}`}}));return a.forwardRef(function(e,a){const r=m(e),{className:c,component:h="div",disableGutters:l=!1,fixed:f=!1,maxWidth:g="lg"}=r,W=s(r,b),k=t({},r,{component:h,disableGutters:l,fixed:f,maxWidth:g}),S=((e,t)=>{const{classes:a,fixed:s,disableGutters:i,maxWidth:n}=e,r={root:["root",n&&`maxWidth${o(String(n))}`,s&&"fixed",i&&"disableGutters"]};return d(r,e=>u(t,e),a)})(k,p);return i(x,t({as:h,ownerState:k,className:n(S.root,c),ref:a},W))})}({createStyledComponent:x("div",{name:"MuiContainer",slot:"Root",overridesResolver:(e,t)=>{const{ownerState:a}=e;return[t.root,t[`maxWidth${o(String(a.maxWidth))}`],a.fixed&&t.fixed,a.disableGutters&&t.disableGutters]}}),useThemeProps:e=>p({props:e,name:"MuiContainer"})}),k=c(i("path",{d:"M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"}),"Refresh");export{W as C,k as R};
1
+ import{bI as e,d as t,r as a,_ as s,f as i,bJ as n,j as o,bK as r,k as d,bL as m,g as u,u as p,s as x,b as c}from"./index-Djrg7_-q.js";const h=e(),b=["className","component","disableGutters","fixed","maxWidth","classes"],l=m(),f=h("div",{name:"MuiContainer",slot:"Root",overridesResolver:(e,t)=>{const{ownerState:a}=e;return[t.root,t[`maxWidth${o(String(a.maxWidth))}`],a.fixed&&t.fixed,a.disableGutters&&t.disableGutters]}}),g=e=>r({props:e,name:"MuiContainer",defaultTheme:l});const W=function(e={}){const{createStyledComponent:r=f,useThemeProps:m=g,componentName:p="MuiContainer"}=e,x=r(({theme:e,ownerState:a})=>t({width:"100%",marginLeft:"auto",boxSizing:"border-box",marginRight:"auto",display:"block"},!a.disableGutters&&{paddingLeft:e.spacing(2),paddingRight:e.spacing(2),[e.breakpoints.up("sm")]:{paddingLeft:e.spacing(3),paddingRight:e.spacing(3)}}),({theme:e,ownerState:t})=>t.fixed&&Object.keys(e.breakpoints.values).reduce((t,a)=>{const s=a,i=e.breakpoints.values[s];return 0!==i&&(t[e.breakpoints.up(s)]={maxWidth:`${i}${e.breakpoints.unit}`}),t},{}),({theme:e,ownerState:a})=>t({},"xs"===a.maxWidth&&{[e.breakpoints.up("xs")]:{maxWidth:Math.max(e.breakpoints.values.xs,444)}},a.maxWidth&&"xs"!==a.maxWidth&&{[e.breakpoints.up(a.maxWidth)]:{maxWidth:`${e.breakpoints.values[a.maxWidth]}${e.breakpoints.unit}`}}));return a.forwardRef(function(e,a){const r=m(e),{className:c,component:h="div",disableGutters:l=!1,fixed:f=!1,maxWidth:g="lg"}=r,W=s(r,b),k=t({},r,{component:h,disableGutters:l,fixed:f,maxWidth:g}),S=((e,t)=>{const{classes:a,fixed:s,disableGutters:i,maxWidth:n}=e,r={root:["root",n&&`maxWidth${o(String(n))}`,s&&"fixed",i&&"disableGutters"]};return d(r,e=>u(t,e),a)})(k,p);return i(x,t({as:h,ownerState:k,className:n(S.root,c),ref:a},W))})}({createStyledComponent:x("div",{name:"MuiContainer",slot:"Root",overridesResolver:(e,t)=>{const{ownerState:a}=e;return[t.root,t[`maxWidth${o(String(a.maxWidth))}`],a.fixed&&t.fixed,a.disableGutters&&t.disableGutters]}}),useThemeProps:e=>p({props:e,name:"MuiContainer"})}),k=c(i("path",{d:"M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"}),"Refresh");export{W as C,k as R};
@@ -0,0 +1 @@
1
+ import{a8 as o}from"./index-Djrg7_-q.js";const r=new o;export{r};
@@ -1 +1 @@
1
- import{i as a,bG as e,bH as t}from"./index-kQlTYlpK.js";const i=(i,o=a.routerService.location.pathname)=>!!e(t.filter(({path:a})=>i.includes(a)),o);export{i as h};
1
+ import{i as a,bG as e,bH as t}from"./index-Djrg7_-q.js";const i=(i,o=a.routerService.location.pathname)=>!!e(t.filter(({path:a})=>i.includes(a)),o);export{i as h};
@@ -1 +1 @@
1
- import{bM as e,bO as t}from"./index-kQlTYlpK.js";function r(e,t){for(var r=0;r<t.length;r++){const o=t[r];if("string"!=typeof o&&!Array.isArray(o))for(const t in o)if("default"!==t&&!(t in e)){const r=Object.getOwnPropertyDescriptor(o,t);r&&Object.defineProperty(e,t,r.get?r:{enumerable:!0,get:()=>o[t]})}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}var o=t();const n=r({__proto__:null,default:e(o)},[o]);export{n as h};
1
+ import{bM as e,bO as t}from"./index-Djrg7_-q.js";function r(e,t){for(var r=0;r<t.length;r++){const o=t[r];if("string"!=typeof o&&!Array.isArray(o))for(const t in o)if("default"!==t&&!(t in e)){const r=Object.getOwnPropertyDescriptor(o,t);r&&Object.defineProperty(e,t,r.get?r:{enumerable:!0,get:()=>o[t]})}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}var o=t();const n=r({__proto__:null,default:e(o)},[o]);export{n as h};