tradelab 0.1.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.
@@ -0,0 +1,213 @@
1
+ :root {
2
+ color-scheme: dark;
3
+ --bg: #08111e;
4
+ --bg-accent: #10223d;
5
+ --panel: rgba(12, 22, 39, 0.9);
6
+ --panel-strong: rgba(17, 31, 54, 0.96);
7
+ --text: #eef4ff;
8
+ --muted: #9fb0c9;
9
+ --line: rgba(159, 176, 201, 0.16);
10
+ --accent: #64e0c1;
11
+ --accent-strong: #9bffdf;
12
+ --positive: #4ade80;
13
+ --negative: #fb7185;
14
+ --shadow: 0 24px 60px rgba(0, 0, 0, 0.28);
15
+ }
16
+
17
+ * {
18
+ box-sizing: border-box;
19
+ }
20
+
21
+ html,
22
+ body {
23
+ margin: 0;
24
+ min-height: 100%;
25
+ }
26
+
27
+ body {
28
+ font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
29
+ color: var(--text);
30
+ background:
31
+ radial-gradient(circle at top, rgba(100, 224, 193, 0.08), transparent 30rem),
32
+ linear-gradient(180deg, #050a12 0%, var(--bg) 100%);
33
+ }
34
+
35
+ .report-shell {
36
+ max-width: 1240px;
37
+ margin: 0 auto;
38
+ padding: 32px 24px 48px;
39
+ }
40
+
41
+ .eyebrow {
42
+ margin: 0 0 10px;
43
+ font-size: 0.76rem;
44
+ letter-spacing: 0.14em;
45
+ text-transform: uppercase;
46
+ color: var(--accent-strong);
47
+ }
48
+
49
+ .hero {
50
+ display: flex;
51
+ align-items: flex-start;
52
+ justify-content: space-between;
53
+ gap: 20px;
54
+ margin-bottom: 24px;
55
+ flex-wrap: wrap;
56
+ }
57
+
58
+ .hero h1 {
59
+ margin: 0 0 8px;
60
+ font-size: clamp(2rem, 3vw, 3rem);
61
+ line-height: 1;
62
+ letter-spacing: -0.03em;
63
+ }
64
+
65
+ .hero-subtitle {
66
+ margin: 0;
67
+ color: var(--muted);
68
+ font-size: 0.96rem;
69
+ }
70
+
71
+ .hero-pill {
72
+ white-space: nowrap;
73
+ border: 1px solid rgba(100, 224, 193, 0.24);
74
+ color: var(--accent-strong);
75
+ background: rgba(100, 224, 193, 0.08);
76
+ border-radius: 999px;
77
+ padding: 10px 14px;
78
+ font-size: 0.9rem;
79
+ }
80
+
81
+ .metric-grid,
82
+ .panel-grid {
83
+ display: grid;
84
+ gap: 16px;
85
+ }
86
+
87
+ .metric-grid {
88
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
89
+ margin-bottom: 20px;
90
+ }
91
+
92
+ .panel-grid {
93
+ grid-template-columns: repeat(12, minmax(0, 1fr));
94
+ }
95
+
96
+ .metric-card,
97
+ .panel {
98
+ background: linear-gradient(180deg, var(--panel-strong), var(--panel));
99
+ border: 1px solid var(--line);
100
+ border-radius: 18px;
101
+ box-shadow: var(--shadow);
102
+ }
103
+
104
+ .metric-card {
105
+ padding: 16px;
106
+ }
107
+
108
+ .metric-card__label {
109
+ color: var(--muted);
110
+ font-size: 0.78rem;
111
+ text-transform: uppercase;
112
+ letter-spacing: 0.1em;
113
+ }
114
+
115
+ .metric-card__value {
116
+ margin-top: 8px;
117
+ font-size: 1.55rem;
118
+ font-weight: 600;
119
+ font-variant-numeric: tabular-nums;
120
+ }
121
+
122
+ .metric-card__note {
123
+ margin-top: 6px;
124
+ color: var(--muted);
125
+ font-size: 0.84rem;
126
+ }
127
+
128
+ .panel {
129
+ padding: 18px;
130
+ grid-column: span 4;
131
+ }
132
+
133
+ .panel--wide {
134
+ grid-column: span 8;
135
+ }
136
+
137
+ .panel-heading {
138
+ margin-bottom: 14px;
139
+ }
140
+
141
+ .panel-heading h2 {
142
+ margin: 0 0 6px;
143
+ font-size: 1rem;
144
+ }
145
+
146
+ .panel-heading p {
147
+ margin: 0;
148
+ color: var(--muted);
149
+ font-size: 0.86rem;
150
+ }
151
+
152
+ .chart {
153
+ height: 320px;
154
+ }
155
+
156
+ .data-table {
157
+ width: 100%;
158
+ border-collapse: collapse;
159
+ font-size: 0.92rem;
160
+ }
161
+
162
+ .data-table th,
163
+ .data-table td {
164
+ padding: 10px 12px;
165
+ text-align: left;
166
+ vertical-align: top;
167
+ border-bottom: 1px solid rgba(159, 176, 201, 0.12);
168
+ }
169
+
170
+ .data-table tbody th,
171
+ .data-table thead th {
172
+ color: var(--muted);
173
+ font-weight: 600;
174
+ }
175
+
176
+ .data-table tbody td,
177
+ .data-table thead th,
178
+ .data-table tbody th {
179
+ font-variant-numeric: tabular-nums;
180
+ }
181
+
182
+ .data-table tr:last-child th,
183
+ .data-table tr:last-child td {
184
+ border-bottom: none;
185
+ }
186
+
187
+ .table-wrap {
188
+ overflow-x: auto;
189
+ }
190
+
191
+ .table-empty {
192
+ color: var(--muted);
193
+ text-align: center;
194
+ }
195
+
196
+ .is-hidden {
197
+ display: none;
198
+ }
199
+
200
+ @media (max-width: 960px) {
201
+ .panel,
202
+ .panel--wide {
203
+ grid-column: span 12;
204
+ }
205
+
206
+ .report-shell {
207
+ padding: 20px 16px 36px;
208
+ }
209
+
210
+ .chart {
211
+ height: 280px;
212
+ }
213
+ }
@@ -0,0 +1,106 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{TITLE}} backtest</title>
7
+ <style>{{CSS}}</style>
8
+ </head>
9
+ <body>
10
+ <main class="report-shell">
11
+ <header class="hero">
12
+ <div>
13
+ <p class="eyebrow">Trading Engine Report</p>
14
+ <h1>{{TITLE}} backtest</h1>
15
+ <p class="hero-subtitle">{{HERO_SUBTITLE}}</p>
16
+ </div>
17
+ <div class="hero-pill">{{HERO_PILL}}</div>
18
+ </header>
19
+
20
+ <section class="metric-grid">
21
+ {{METRIC_CARDS}}
22
+ </section>
23
+
24
+ <section class="panel-grid">
25
+ <article class="panel panel--wide">
26
+ <div class="panel-heading">
27
+ <h2>Equity Curve</h2>
28
+ <p>Realized equity through the run.</p>
29
+ </div>
30
+ <div id="equity-chart" class="chart"></div>
31
+ </article>
32
+
33
+ <article class="panel">
34
+ <div class="panel-heading">
35
+ <h2>Summary</h2>
36
+ <p>Headline stats for completed positions.</p>
37
+ </div>
38
+ <table class="data-table">
39
+ <tbody>{{SUMMARY_ROWS}}</tbody>
40
+ </table>
41
+ </article>
42
+
43
+ <article class="panel">
44
+ <div class="panel-heading">
45
+ <h2>Drawdown</h2>
46
+ <p>Peak-to-trough decline on realized equity.</p>
47
+ </div>
48
+ <div id="drawdown-chart" class="chart"></div>
49
+ </article>
50
+
51
+ <article class="panel">
52
+ <div class="panel-heading">
53
+ <h2>Daily PnL</h2>
54
+ <p>Per-day realized performance.</p>
55
+ </div>
56
+ <div id="daily-chart" class="chart"></div>
57
+ </article>
58
+
59
+ <article class="panel panel--wide {{REPLAY_VISIBILITY}}">
60
+ <div class="panel-heading">
61
+ <h2>Executions</h2>
62
+ <p>Replay frames reduced to a clean price-and-fills view.</p>
63
+ </div>
64
+ <div id="replay-chart" class="chart"></div>
65
+ </article>
66
+
67
+ <article class="panel">
68
+ <div class="panel-heading">
69
+ <h2>Breakdown</h2>
70
+ <p>Long/short split and distribution snapshots.</p>
71
+ </div>
72
+ <table class="data-table">
73
+ <tbody>{{BREAKDOWN_ROWS}}</tbody>
74
+ </table>
75
+ </article>
76
+
77
+ <article class="panel panel--wide">
78
+ <div class="panel-heading">
79
+ <h2>Recent Positions</h2>
80
+ <p>Last 25 completed positions.</p>
81
+ </div>
82
+ <div class="table-wrap">
83
+ <table class="data-table data-table--positions">
84
+ <thead>
85
+ <tr>
86
+ <th>Opened</th>
87
+ <th>Side</th>
88
+ <th>Entry</th>
89
+ <th>Exit</th>
90
+ <th>Reason</th>
91
+ <th>PnL</th>
92
+ <th>MFE / MAE</th>
93
+ </tr>
94
+ </thead>
95
+ <tbody>{{POSITION_ROWS}}</tbody>
96
+ </table>
97
+ </div>
98
+ </article>
99
+ </section>
100
+ </main>
101
+
102
+ <script id="report-data" type="application/json">{{REPORT_DATA_JSON}}</script>
103
+ <script src="{{PLOTLY_CDN_URL}}"></script>
104
+ <script>{{REPORT_JS}}</script>
105
+ </body>
106
+ </html>
@@ -0,0 +1,120 @@
1
+ (function renderBacktestReport() {
2
+ const raw = document.getElementById("report-data");
3
+ if (!raw) return;
4
+
5
+ let data;
6
+ try {
7
+ data = JSON.parse(raw.textContent);
8
+ } catch {
9
+ return;
10
+ }
11
+
12
+ function plot(targetId, traces, layout = {}) {
13
+ const el = document.getElementById(targetId);
14
+ if (!el || typeof Plotly === "undefined") return;
15
+
16
+ Plotly.newPlot(
17
+ el,
18
+ traces,
19
+ {
20
+ margin: { t: 16, r: 18, b: 42, l: 52 },
21
+ paper_bgcolor: "rgba(0,0,0,0)",
22
+ plot_bgcolor: "rgba(0,0,0,0)",
23
+ font: { color: "#eef4ff" },
24
+ xaxis: { gridcolor: "rgba(159,176,201,0.12)" },
25
+ yaxis: { gridcolor: "rgba(159,176,201,0.12)" },
26
+ legend: { orientation: "h" },
27
+ ...layout,
28
+ },
29
+ {
30
+ responsive: true,
31
+ displayModeBar: false,
32
+ displaylogo: false,
33
+ }
34
+ );
35
+ }
36
+
37
+ plot("equity-chart", [
38
+ {
39
+ x: data.eqSeries.map((point) => point.t),
40
+ y: data.eqSeries.map((point) => point.equity),
41
+ type: "scatter",
42
+ mode: "lines",
43
+ name: "Equity",
44
+ line: { color: "#64e0c1", width: 2.5 },
45
+ },
46
+ ], {
47
+ yaxis: { title: "Equity", gridcolor: "rgba(159,176,201,0.12)" },
48
+ });
49
+
50
+ plot("drawdown-chart", [
51
+ {
52
+ x: data.drawdown.map((point) => point.t),
53
+ y: data.drawdown.map((point) => point.value),
54
+ type: "scatter",
55
+ mode: "lines",
56
+ name: "Drawdown",
57
+ line: { color: "#fb7185", width: 2.2 },
58
+ fill: "tozeroy",
59
+ fillcolor: "rgba(251,113,133,0.12)",
60
+ },
61
+ ], {
62
+ yaxis: {
63
+ title: "Drawdown",
64
+ tickformat: ",.0%",
65
+ gridcolor: "rgba(159,176,201,0.12)",
66
+ },
67
+ });
68
+
69
+ if (Array.isArray(data.dailyPnl) && data.dailyPnl.length) {
70
+ plot("daily-chart", [
71
+ {
72
+ x: data.dailyPnl.map((point) => point.date),
73
+ y: data.dailyPnl.map((point) => point.pnl),
74
+ type: "bar",
75
+ name: "Daily PnL",
76
+ marker: {
77
+ color: data.dailyPnl.map((point) =>
78
+ point.pnl >= 0 ? "#4ade80" : "#fb7185"
79
+ ),
80
+ },
81
+ },
82
+ ], {
83
+ yaxis: { title: "PnL", gridcolor: "rgba(159,176,201,0.12)" },
84
+ });
85
+ }
86
+
87
+ if (data.hasReplay && Array.isArray(data.replay.frames)) {
88
+ const entries = data.replay.events.filter((event) => event.type === "entry");
89
+ const exits = data.replay.events.filter((event) => event.type !== "entry");
90
+
91
+ plot("replay-chart", [
92
+ {
93
+ x: data.replay.frames.map((frame) => frame.t),
94
+ y: data.replay.frames.map((frame) => frame.price),
95
+ type: "scatter",
96
+ mode: "lines",
97
+ name: "Price",
98
+ line: { color: "#93c5fd", width: 2 },
99
+ },
100
+ {
101
+ x: entries.map((event) => event.t),
102
+ y: entries.map((event) => event.price),
103
+ type: "scatter",
104
+ mode: "markers",
105
+ name: "Entries",
106
+ marker: { color: "#4ade80", size: 9, symbol: "triangle-up" },
107
+ },
108
+ {
109
+ x: exits.map((event) => event.t),
110
+ y: exits.map((event) => event.price),
111
+ type: "scatter",
112
+ mode: "markers",
113
+ name: "Exits",
114
+ marker: { color: "#fb7185", size: 9, symbol: "triangle-down" },
115
+ },
116
+ ], {
117
+ yaxis: { title: "Price", gridcolor: "rgba(159,176,201,0.12)" },
118
+ });
119
+ }
120
+ })();