@backtest-kit/ui 12.4.0 → 12.6.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.
- package/README.md +254 -254
- package/build/index.cjs +8 -3
- package/build/index.mjs +8 -3
- package/build/modules/frontend/build/3rdparty/ace_1.4.12/ace.js +21337 -21337
- package/build/modules/frontend/build/3rdparty/ace_1.4.12/mode-javascript.js +797 -797
- package/build/modules/frontend/build/3rdparty/ace_1.4.12/theme-chrome.js +137 -137
- package/build/modules/frontend/build/3rdparty/ace_1.4.12/theme-twilight.js +115 -115
- package/build/modules/frontend/build/3rdparty/ace_1.4.12/worker-javascript.js +15012 -15012
- package/build/modules/frontend/build/3rdparty/qfchart_0.8.7/echarts.min.js +45 -45
- package/build/modules/frontend/build/assets/{Article-O0o3hpW3.js → Article-DqGj0oHP.js} +1 -1
- package/build/modules/frontend/build/assets/{Background-CMWFDg5H.js → Background-Ju3gyOQW.js} +1 -1
- package/build/modules/frontend/build/assets/{IconPhoto-bus4BUds.js → IconPhoto-DJxFMbZN.js} +1 -1
- package/build/modules/frontend/build/assets/{KeyboardArrowLeft-DiwoIhl6.js → KeyboardArrowLeft-D-dH861E.js} +1 -1
- package/build/modules/frontend/build/assets/{PictureAsPdfOutlined-C6GukYFm.js → PictureAsPdfOutlined-CNmNuM9m.js} +1 -1
- package/build/modules/frontend/build/assets/{Refresh-CthzzypR.js → Refresh-DMMj-ez_.js} +1 -1
- package/build/modules/frontend/build/assets/emitters-COYeGAVf.js +1 -0
- package/build/modules/frontend/build/assets/{hasRouteMatch-BUocCfU0.js → hasRouteMatch-CJ8MG-Z5.js} +1 -1
- package/build/modules/frontend/build/assets/{html2canvas-BXjd5VwI.js → html2canvas-ZRtDT2Xj.js} +1 -1
- package/build/modules/frontend/build/assets/{index-B0dOv27L.js → index-B4vT2oz0.js} +1 -1
- package/build/modules/frontend/build/assets/{index-C8ijdPgO.js → index-BZ-2tIjS.js} +1 -1
- package/build/modules/frontend/build/assets/{index-DS8hcHEz.js → index-BfI1t1CW.js} +1 -1
- package/build/modules/frontend/build/assets/{index-Kb9MP8oH.js → index-CAeruUt8.js} +1 -1
- package/build/modules/frontend/build/assets/{index-C65iKnLV.js → index-CAw70V5N.js} +1 -1
- package/build/modules/frontend/build/assets/{index-BfCHul3t.js → index-CWF7zgn2.js} +1 -1
- package/build/modules/frontend/build/assets/{index-BxYTS9U5.js → index-CaNx_Ck6.js} +1 -1
- package/build/modules/frontend/build/assets/{index-xnM4Hc3g.js → index-Chady3cO.js} +1 -1
- package/build/modules/frontend/build/assets/{index-D-QQ3ARh.js → index-DMi0YxbU.js} +1 -1
- package/build/modules/frontend/build/assets/{index--6o4qI0k.js → index-DoJ5J0eu.js} +1 -1
- package/build/modules/frontend/build/assets/{index-xD1NB5cz.js → index-DqU1KnNK.js} +1 -1
- package/build/modules/frontend/build/assets/{index-DfAakW_X.js → index-Ds77hvVx.js} +1 -1
- package/build/modules/frontend/build/assets/{index-BWbOCqgp.js → index-hEQ0fPsL.js} +5 -5
- package/build/modules/frontend/build/assets/{index.es-CeK1bA6Z.js → index.es-C3fgp50D.js} +1 -1
- package/build/modules/frontend/build/assets/{markdownit-BIaVjXBV.js → markdownit-B8WXvttx.js} +1 -1
- package/build/modules/frontend/build/index.html +87 -87
- package/build/modules/frontend/build/manifest.json +23 -23
- package/package.json +118 -118
- package/types.d.ts +2 -1
- package/build/modules/frontend/build/assets/emitters-DwSgdWL_.js +0 -1
package/README.md
CHANGED
|
@@ -1,254 +1,254 @@
|
|
|
1
|
-
<img src="https://github.com/tripolskypetr/backtest-kit/raw/refs/heads/master/assets/decart.svg" height="45px" align="right">
|
|
2
|
-
|
|
3
|
-
# 📊 @backtest-kit/ui
|
|
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.
|
|
6
|
-
|
|
7
|
-

|
|
8
|
-
|
|
9
|
-
[](https://deepwiki.com/tripolskypetr/backtest-kit)
|
|
10
|
-
[](https://npmjs.org/package/@backtest-kit/ui)
|
|
11
|
-
[]()
|
|
12
|
-
|
|
13
|
-
Interactive dashboard for backtest-kit with signal visualization, candle charts, risk analysis, and notification management. Built with React 18, Material-UI, and Lightweight Charts.
|
|
14
|
-
|
|
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
|
-
|
|
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.
|
|
18
|
-
|
|
19
|
-
## ✨ Features
|
|
20
|
-
|
|
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
|
|
29
|
-
|
|
30
|
-
## 📋 What It Does
|
|
31
|
-
|
|
32
|
-
`@backtest-kit/ui` provides both backend API and frontend dashboard:
|
|
33
|
-
|
|
34
|
-
| Component | Description |
|
|
35
|
-
|-----------|-------------|
|
|
36
|
-
| **`serve()`** | Start HTTP server with REST API endpoints |
|
|
37
|
-
| **`getRouter()`** | Get expressjs-compatible router for custom middleware integration |
|
|
38
|
-
|
|
39
|
-
## 🚀 Installation
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npm install @backtest-kit/ui backtest-kit ccxt
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## 📖 Usage
|
|
46
|
-
|
|
47
|
-
### Quick Start - Launch Dashboard
|
|
48
|
-
|
|
49
|
-
```typescript
|
|
50
|
-
import { serve } from '@backtest-kit/ui';
|
|
51
|
-
|
|
52
|
-
// Start the UI server
|
|
53
|
-
serve('0.0.0.0', 60050);
|
|
54
|
-
|
|
55
|
-
// Dashboard available at http://localhost:60050
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Custom Logger Integration
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
import { setLogger } from '@backtest-kit/ui';
|
|
62
|
-
|
|
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
|
-
```
|
|
69
|
-
|
|
70
|
-
## 📐 Dashboard Revenue Math
|
|
71
|
-
|
|
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.
|
|
73
|
-
|
|
74
|
-
### Dollar PnL formula
|
|
75
|
-
|
|
76
|
-
```
|
|
77
|
-
revenue[window] = Σ signal.pnl.pnlCost (for all closed signals in that window)
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
`pnlCost` is computed by the backend (`toProfitLossDto`) as:
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
pnlCost = (pnlPercentage / 100) × pnlEntries
|
|
84
|
-
```
|
|
85
|
-
|
|
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`) |
|
|
91
|
-
|
|
92
|
-
**Example** (1 DCA entry at $100, position closed +5%):
|
|
93
|
-
|
|
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 |
|
|
99
|
-
|
|
100
|
-
### Time windows
|
|
101
|
-
|
|
102
|
-
The anchor point depends on execution mode:
|
|
103
|
-
|
|
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)
|
|
106
|
-
|
|
107
|
-
| Window | Range |
|
|
108
|
-
|--------|-------|
|
|
109
|
-
| Today | `>= startOf(anchorDay)` |
|
|
110
|
-
| Yesterday | `[anchorDay − 1d, anchorDay)` |
|
|
111
|
-
| 7 days | `>= anchorDay − 7d` |
|
|
112
|
-
| 31 days | `>= anchorDay − 31d` |
|
|
113
|
-
|
|
114
|
-
Revenue and signal count are tracked separately for each window and aggregated across all symbols on the Dashboard.
|
|
115
|
-
|
|
116
|
-
## 📐 Position PNL Math
|
|
117
|
-
|
|
118
|
-
### Effective entry price (DCA-weighted)
|
|
119
|
-
|
|
120
|
-
When multiple DCA entries exist, the effective open price is a **cost-weighted harmonic mean**:
|
|
121
|
-
|
|
122
|
-
```
|
|
123
|
-
effectivePrice = Σcost / Σ(cost / price)
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
This is the correct formula for fixed-dollar entries (not simple average), because buying $100 worth at different prices gives different coin quantities.
|
|
127
|
-
|
|
128
|
-
### Partial closes (PP/PL)
|
|
129
|
-
|
|
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.
|
|
131
|
-
|
|
132
|
-
**Cost-basis replay:**
|
|
133
|
-
|
|
134
|
-
```
|
|
135
|
-
for each partial[i]:
|
|
136
|
-
closedDollar += (percent[i] / 100) × costBasisAtClose[i]
|
|
137
|
-
remainingCostBasis = costBasisAtClose[i] × (1 - percent[i] / 100)
|
|
138
|
-
|
|
139
|
-
# DCA entries added AFTER the last partial are appended:
|
|
140
|
-
remainingCostBasis += Σ entry.cost for entries[lastEntryCount..]
|
|
141
|
-
|
|
142
|
-
totalClosedPercent = closedDollar / totalInvested × 100
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
**Effective price through partials** is computed iteratively so that a partial sell does not change the entry price of the remaining coins:
|
|
146
|
-
|
|
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
|
-
```
|
|
157
|
-
|
|
158
|
-
### toProfitLossDto — weighted PNL with slippage & fees
|
|
159
|
-
|
|
160
|
-
**Without partials:**
|
|
161
|
-
|
|
162
|
-
```
|
|
163
|
-
priceOpenSlip = effectivePrice × (1 ± slippage)
|
|
164
|
-
priceCloseSlip = priceClose × (1 ∓ slippage)
|
|
165
|
-
|
|
166
|
-
pnlPercentage = (priceCloseSlip - priceOpenSlip) / priceOpenSlip × 100
|
|
167
|
-
fee = CC_PERCENT_FEE × (1 + priceCloseSlip / priceOpenSlip)
|
|
168
|
-
pnlPercentage -= fee
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
**With partials — dollar-weighted sum:**
|
|
172
|
-
|
|
173
|
-
```
|
|
174
|
-
weight[i] = (percent[i] / 100 × costBasisAtClose[i]) / totalInvested
|
|
175
|
-
|
|
176
|
-
totalWeightedPnl = Σ weight[i] × pnl[i] # each partial at its own effectivePrice
|
|
177
|
-
+ remainingWeight × pnlRemaining # rest closed at final priceClose
|
|
178
|
-
|
|
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
|
|
182
|
-
|
|
183
|
-
pnlPercentage = totalWeightedPnl - fee
|
|
184
|
-
pnlCost = pnlPercentage / 100 × totalInvested
|
|
185
|
-
```
|
|
186
|
-
|
|
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 |
|
|
193
|
-
|
|
194
|
-
## 🖥️ Dashboard Views
|
|
195
|
-
|
|
196
|
-
The frontend provides specialized views for different trading events:
|
|
197
|
-
|
|
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 |
|
|
208
|
-
|
|
209
|
-
Each view includes:
|
|
210
|
-
- 📋 Detailed information form
|
|
211
|
-
- 📈 1m, 15m, 1h candlestick charts
|
|
212
|
-
- 📥 JSON export for all data
|
|
213
|
-
|
|
214
|
-
## 💡 Why Use @backtest-kit/ui?
|
|
215
|
-
|
|
216
|
-
Instead of building custom dashboards:
|
|
217
|
-
|
|
218
|
-
**Without backtest-kit**
|
|
219
|
-
|
|
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
|
-
```
|
|
229
|
-
|
|
230
|
-
**With backtest-kit**
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
// ✅ With @backtest-kit/ui
|
|
234
|
-
import { serve } from '@backtest-kit/ui';
|
|
235
|
-
|
|
236
|
-
serve(); // Full dashboard ready!
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
**Benefits:**
|
|
240
|
-
|
|
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
|
|
247
|
-
|
|
248
|
-
## 🤝 Contribute
|
|
249
|
-
|
|
250
|
-
Fork/PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
|
|
251
|
-
|
|
252
|
-
## 📜 License
|
|
253
|
-
|
|
254
|
-
MIT © [tripolskypetr](https://github.com/tripolskypetr)
|
|
1
|
+
<img src="https://github.com/tripolskypetr/backtest-kit/raw/refs/heads/master/assets/decart.svg" height="45px" align="right">
|
|
2
|
+
|
|
3
|
+
# 📊 @backtest-kit/ui
|
|
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.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
[](https://deepwiki.com/tripolskypetr/backtest-kit)
|
|
10
|
+
[](https://npmjs.org/package/@backtest-kit/ui)
|
|
11
|
+
[]()
|
|
12
|
+
|
|
13
|
+
Interactive dashboard for backtest-kit with signal visualization, candle charts, risk analysis, and notification management. Built with React 18, Material-UI, and Lightweight Charts.
|
|
14
|
+
|
|
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
|
+
|
|
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.
|
|
18
|
+
|
|
19
|
+
## ✨ Features
|
|
20
|
+
|
|
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
|
|
29
|
+
|
|
30
|
+
## 📋 What It Does
|
|
31
|
+
|
|
32
|
+
`@backtest-kit/ui` provides both backend API and frontend dashboard:
|
|
33
|
+
|
|
34
|
+
| Component | Description |
|
|
35
|
+
|-----------|-------------|
|
|
36
|
+
| **`serve()`** | Start HTTP server with REST API endpoints |
|
|
37
|
+
| **`getRouter()`** | Get expressjs-compatible router for custom middleware integration |
|
|
38
|
+
|
|
39
|
+
## 🚀 Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @backtest-kit/ui backtest-kit ccxt
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 📖 Usage
|
|
46
|
+
|
|
47
|
+
### Quick Start - Launch Dashboard
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { serve } from '@backtest-kit/ui';
|
|
51
|
+
|
|
52
|
+
// Start the UI server
|
|
53
|
+
serve('0.0.0.0', 60050);
|
|
54
|
+
|
|
55
|
+
// Dashboard available at http://localhost:60050
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Custom Logger Integration
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { setLogger } from '@backtest-kit/ui';
|
|
62
|
+
|
|
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
|
+
```
|
|
69
|
+
|
|
70
|
+
## 📐 Dashboard Revenue Math
|
|
71
|
+
|
|
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.
|
|
73
|
+
|
|
74
|
+
### Dollar PnL formula
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
revenue[window] = Σ signal.pnl.pnlCost (for all closed signals in that window)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`pnlCost` is computed by the backend (`toProfitLossDto`) as:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
pnlCost = (pnlPercentage / 100) × pnlEntries
|
|
84
|
+
```
|
|
85
|
+
|
|
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`) |
|
|
91
|
+
|
|
92
|
+
**Example** (1 DCA entry at $100, position closed +5%):
|
|
93
|
+
|
|
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 |
|
|
99
|
+
|
|
100
|
+
### Time windows
|
|
101
|
+
|
|
102
|
+
The anchor point depends on execution mode:
|
|
103
|
+
|
|
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)
|
|
106
|
+
|
|
107
|
+
| Window | Range |
|
|
108
|
+
|--------|-------|
|
|
109
|
+
| Today | `>= startOf(anchorDay)` |
|
|
110
|
+
| Yesterday | `[anchorDay − 1d, anchorDay)` |
|
|
111
|
+
| 7 days | `>= anchorDay − 7d` |
|
|
112
|
+
| 31 days | `>= anchorDay − 31d` |
|
|
113
|
+
|
|
114
|
+
Revenue and signal count are tracked separately for each window and aggregated across all symbols on the Dashboard.
|
|
115
|
+
|
|
116
|
+
## 📐 Position PNL Math
|
|
117
|
+
|
|
118
|
+
### Effective entry price (DCA-weighted)
|
|
119
|
+
|
|
120
|
+
When multiple DCA entries exist, the effective open price is a **cost-weighted harmonic mean**:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
effectivePrice = Σcost / Σ(cost / price)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This is the correct formula for fixed-dollar entries (not simple average), because buying $100 worth at different prices gives different coin quantities.
|
|
127
|
+
|
|
128
|
+
### Partial closes (PP/PL)
|
|
129
|
+
|
|
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.
|
|
131
|
+
|
|
132
|
+
**Cost-basis replay:**
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
for each partial[i]:
|
|
136
|
+
closedDollar += (percent[i] / 100) × costBasisAtClose[i]
|
|
137
|
+
remainingCostBasis = costBasisAtClose[i] × (1 - percent[i] / 100)
|
|
138
|
+
|
|
139
|
+
# DCA entries added AFTER the last partial are appended:
|
|
140
|
+
remainingCostBasis += Σ entry.cost for entries[lastEntryCount..]
|
|
141
|
+
|
|
142
|
+
totalClosedPercent = closedDollar / totalInvested × 100
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Effective price through partials** is computed iteratively so that a partial sell does not change the entry price of the remaining coins:
|
|
146
|
+
|
|
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
|
+
```
|
|
157
|
+
|
|
158
|
+
### toProfitLossDto — weighted PNL with slippage & fees
|
|
159
|
+
|
|
160
|
+
**Without partials:**
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
priceOpenSlip = effectivePrice × (1 ± slippage)
|
|
164
|
+
priceCloseSlip = priceClose × (1 ∓ slippage)
|
|
165
|
+
|
|
166
|
+
pnlPercentage = (priceCloseSlip - priceOpenSlip) / priceOpenSlip × 100
|
|
167
|
+
fee = CC_PERCENT_FEE × (1 + priceCloseSlip / priceOpenSlip)
|
|
168
|
+
pnlPercentage -= fee
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**With partials — dollar-weighted sum:**
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
weight[i] = (percent[i] / 100 × costBasisAtClose[i]) / totalInvested
|
|
175
|
+
|
|
176
|
+
totalWeightedPnl = Σ weight[i] × pnl[i] # each partial at its own effectivePrice
|
|
177
|
+
+ remainingWeight × pnlRemaining # rest closed at final priceClose
|
|
178
|
+
|
|
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
|
|
182
|
+
|
|
183
|
+
pnlPercentage = totalWeightedPnl - fee
|
|
184
|
+
pnlCost = pnlPercentage / 100 × totalInvested
|
|
185
|
+
```
|
|
186
|
+
|
|
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 |
|
|
193
|
+
|
|
194
|
+
## 🖥️ Dashboard Views
|
|
195
|
+
|
|
196
|
+
The frontend provides specialized views for different trading events:
|
|
197
|
+
|
|
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 |
|
|
208
|
+
|
|
209
|
+
Each view includes:
|
|
210
|
+
- 📋 Detailed information form
|
|
211
|
+
- 📈 1m, 15m, 1h candlestick charts
|
|
212
|
+
- 📥 JSON export for all data
|
|
213
|
+
|
|
214
|
+
## 💡 Why Use @backtest-kit/ui?
|
|
215
|
+
|
|
216
|
+
Instead of building custom dashboards:
|
|
217
|
+
|
|
218
|
+
**Without backtest-kit**
|
|
219
|
+
|
|
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
|
+
```
|
|
229
|
+
|
|
230
|
+
**With backtest-kit**
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// ✅ With @backtest-kit/ui
|
|
234
|
+
import { serve } from '@backtest-kit/ui';
|
|
235
|
+
|
|
236
|
+
serve(); // Full dashboard ready!
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Benefits:**
|
|
240
|
+
|
|
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
|
|
247
|
+
|
|
248
|
+
## 🤝 Contribute
|
|
249
|
+
|
|
250
|
+
Fork/PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
|
|
251
|
+
|
|
252
|
+
## 📜 License
|
|
253
|
+
|
|
254
|
+
MIT © [tripolskypetr](https://github.com/tripolskypetr)
|
package/build/index.cjs
CHANGED
|
@@ -4955,10 +4955,15 @@ const METHOD_NAME_SERVE = "serve.serve";
|
|
|
4955
4955
|
const METHOD_NAME_GET_ROUTER = "serve.getRouter";
|
|
4956
4956
|
const MAX_CONNECTIONS = 1000;
|
|
4957
4957
|
const SOCKET_TIMEOUT = 60 * 10 * 1000;
|
|
4958
|
-
const serveInternal = functoolsKit.singleshot((host = CC_WWWROOT_HOST, port = CC_WWWROOT_PORT) => {
|
|
4958
|
+
const serveInternal = functoolsKit.singleshot((host = CC_WWWROOT_HOST, port = CC_WWWROOT_PORT, callback) => {
|
|
4959
4959
|
const server = new http.Server(router$1);
|
|
4960
4960
|
server.listen(port, host).addListener("listening", () => {
|
|
4961
4961
|
console.log(`Listening on http://${host}:${port}`);
|
|
4962
|
+
callback && callback();
|
|
4963
|
+
});
|
|
4964
|
+
server.addListener("error", (err) => {
|
|
4965
|
+
console.error("Server error:", err);
|
|
4966
|
+
callback && callback(err);
|
|
4962
4967
|
});
|
|
4963
4968
|
server.maxConnections = MAX_CONNECTIONS;
|
|
4964
4969
|
server.setTimeout(SOCKET_TIMEOUT);
|
|
@@ -4967,13 +4972,13 @@ const serveInternal = functoolsKit.singleshot((host = CC_WWWROOT_HOST, port = CC
|
|
|
4967
4972
|
serveInternal.clear();
|
|
4968
4973
|
};
|
|
4969
4974
|
});
|
|
4970
|
-
function serve(host, port, cwd = process.cwd()) {
|
|
4975
|
+
function serve(host, port, cwd = process.cwd(), callback) {
|
|
4971
4976
|
ioc.loggerService.log(METHOD_NAME_SERVE, {
|
|
4972
4977
|
host,
|
|
4973
4978
|
port,
|
|
4974
4979
|
});
|
|
4975
4980
|
serveSubject.next(cwd);
|
|
4976
|
-
return serveInternal(host, port);
|
|
4981
|
+
return serveInternal(host, port, callback);
|
|
4977
4982
|
}
|
|
4978
4983
|
function getRouter() {
|
|
4979
4984
|
ioc.loggerService.log(METHOD_NAME_GET_ROUTER);
|
package/build/index.mjs
CHANGED
|
@@ -4934,10 +4934,15 @@ const METHOD_NAME_SERVE = "serve.serve";
|
|
|
4934
4934
|
const METHOD_NAME_GET_ROUTER = "serve.getRouter";
|
|
4935
4935
|
const MAX_CONNECTIONS = 1000;
|
|
4936
4936
|
const SOCKET_TIMEOUT = 60 * 10 * 1000;
|
|
4937
|
-
const serveInternal = singleshot((host = CC_WWWROOT_HOST, port = CC_WWWROOT_PORT) => {
|
|
4937
|
+
const serveInternal = singleshot((host = CC_WWWROOT_HOST, port = CC_WWWROOT_PORT, callback) => {
|
|
4938
4938
|
const server = new http.Server(router$1);
|
|
4939
4939
|
server.listen(port, host).addListener("listening", () => {
|
|
4940
4940
|
console.log(`Listening on http://${host}:${port}`);
|
|
4941
|
+
callback && callback();
|
|
4942
|
+
});
|
|
4943
|
+
server.addListener("error", (err) => {
|
|
4944
|
+
console.error("Server error:", err);
|
|
4945
|
+
callback && callback(err);
|
|
4941
4946
|
});
|
|
4942
4947
|
server.maxConnections = MAX_CONNECTIONS;
|
|
4943
4948
|
server.setTimeout(SOCKET_TIMEOUT);
|
|
@@ -4946,13 +4951,13 @@ const serveInternal = singleshot((host = CC_WWWROOT_HOST, port = CC_WWWROOT_PORT
|
|
|
4946
4951
|
serveInternal.clear();
|
|
4947
4952
|
};
|
|
4948
4953
|
});
|
|
4949
|
-
function serve(host, port, cwd = process.cwd()) {
|
|
4954
|
+
function serve(host, port, cwd = process.cwd(), callback) {
|
|
4950
4955
|
ioc.loggerService.log(METHOD_NAME_SERVE, {
|
|
4951
4956
|
host,
|
|
4952
4957
|
port,
|
|
4953
4958
|
});
|
|
4954
4959
|
serveSubject.next(cwd);
|
|
4955
|
-
return serveInternal(host, port);
|
|
4960
|
+
return serveInternal(host, port, callback);
|
|
4956
4961
|
}
|
|
4957
4962
|
function getRouter() {
|
|
4958
4963
|
ioc.loggerService.log(METHOD_NAME_GET_ROUTER);
|