koishi-plugin-monetary-bourse 2.0.0 → 2.0.2

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.
@@ -21,7 +21,7 @@
21
21
  body {
22
22
  margin: 0;
23
23
  padding: 24px;
24
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
24
+ font-family: 'Roboto Mono', 'Trebuchet MS', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
25
25
  background-color: var(--bg-color);
26
26
  width: 480px;
27
27
  box-sizing: border-box;
@@ -128,6 +128,7 @@
128
128
  display: flex;
129
129
  align-items: center;
130
130
  gap: 8px;
131
+ letter-spacing: -0.2px;
131
132
  }
132
133
  .stock-amount {
133
134
  font-size: 13px;
@@ -136,6 +137,7 @@
136
137
  color: var(--text-primary);
137
138
  padding: 4px 10px;
138
139
  border-radius: 6px;
140
+ font-family: 'Roboto Mono', 'Trebuchet MS', monospace;
139
141
  }
140
142
 
141
143
  .stock-body { padding: 24px; }
@@ -151,7 +153,8 @@
151
153
  font-size: 18px;
152
154
  font-weight: 600;
153
155
  color: var(--text-primary);
154
- font-family: 'Roboto Mono', monospace;
156
+ font-family: 'Roboto Mono', 'Trebuchet MS', monospace;
157
+ letter-spacing: -0.3px;
155
158
  }
156
159
  .stat-value.highlight { color: var(--accent-color); }
157
160
 
@@ -253,6 +256,13 @@
253
256
  // 从 JSON 标签中读取数据
254
257
  const DATA = JSON.parse(document.getElementById('data-source').textContent);
255
258
 
259
+ // 统一字体:数字和金额统一使用与其他页面一致的等宽字体
260
+ const monoTargets = document.querySelectorAll('.stat-value, .profit-value, .pending-amount, .pending-price, .pending-cost, .pending-type, .stock-amount')
261
+ monoTargets.forEach(el => {
262
+ el.style.fontFamily = "'Roboto Mono','Trebuchet MS','Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif"
263
+ el.style.letterSpacing = '-0.2px'
264
+ })
265
+
256
266
  // 渲染用户信息
257
267
  document.getElementById('avatar').textContent = DATA.username.charAt(0).toUpperCase();
258
268
  document.getElementById('username').textContent = DATA.username;
@@ -23,7 +23,7 @@
23
23
  body {
24
24
  padding: 32px;
25
25
  /* 字体栈优化:优先使用现代系统字体 */
26
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif;
26
+ font-family: 'Roboto Mono', 'Trebuchet MS', 'Inter', -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif;
27
27
  background-color: var(--bg-color);
28
28
 
29
29
  /* 强制 4:3 比例 (1024x768) */
@@ -105,7 +105,7 @@
105
105
  }
106
106
 
107
107
  .current-price {
108
- font-family: 'Trebuchet MS', monospace;
108
+ font-family: 'Roboto Mono', 'Trebuchet MS', monospace;
109
109
  font-size: 64px;
110
110
  font-weight: normal;
111
111
  color: var(--main-color);
@@ -1,28 +1,35 @@
1
+ <!DOCTYPE html>
1
2
  <html>
2
3
  <head>
4
+ <meta charset="utf-8">
3
5
  <style>
4
6
  :root {
5
7
  --bg-color: #0c0f15;
6
- --card-bg: #151921;
7
- --item-bg: #1e232e;
8
- --text-primary: #e1e3e6;
9
- --text-secondary: #8b919e;
10
- --border-color: #232730;
8
+ --card-bg: #161b22;
9
+ --item-bg: #1f242e;
10
+ --text-primary: #f0f3f5;
11
+ --text-secondary: #8b949e;
12
+ --border-color: #30363d;
11
13
 
12
14
  /* 语义色 */
13
- --up-color: #f87171; /* 涨/盈/买 */
14
- --down-color: #4ade80; /* 跌/亏/卖 */
15
- --up-bg: rgba(248, 113, 113, 0.15);
16
- --down-bg: rgba(74, 222, 128, 0.15);
15
+ --buy-color: #f87171; /* 涨/买 */
16
+ --sell-color: #4ade80; /* 跌/卖 */
17
+ --buy-bg: rgba(248, 113, 113, 0.15);
18
+ --sell-bg: rgba(74, 222, 128, 0.15);
17
19
 
18
- --accent-color: #6366f1;
20
+ --pending-color: #eab308; /* 黄色 */
21
+ --pending-bg: rgba(234, 179, 8, 0.15);
22
+
23
+ --accent-color: #58a6ff;
19
24
  }
20
25
 
21
26
  * { margin: 0; padding: 0; box-sizing: border-box; }
22
27
 
23
28
  body {
24
- padding: 32px;
25
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
29
+ /* 修改 3: 增大页面外间距 (32px -> 64px) */
30
+ padding: 64px;
31
+ /* 还原回原本的字体组合 */
32
+ font-family: 'Roboto Mono', 'Trebuchet MS', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
26
33
  background-color: var(--bg-color);
27
34
  width: 960px;
28
35
  height: 720px;
@@ -36,104 +43,143 @@
36
43
  background-color: var(--card-bg);
37
44
  width: 100%;
38
45
  height: 100%;
39
- padding: 32px 40px;
40
- border-radius: 24px;
41
- box-shadow: 0 24px 48px rgba(0,0,0,0.5), inset 0 1px 1px rgba(255,255,255,0.05);
42
- border: 1px solid var(--border-color);
46
+ padding: 32px 40px; /* 内部左右间距也稍微增加一点 */
47
+ border-radius: 16px;
48
+ box-shadow: 0 32px 64px rgba(0,0,0,0.6), 0 0 0 1px var(--border-color);
43
49
  display: flex;
44
50
  flex-direction: column;
45
51
  position: relative;
52
+ overflow: hidden;
46
53
  }
47
54
 
48
- /* Header */
49
- .header {
55
+ /* --- 顶部区域:股票信息 + 价格 --- */
56
+ .top-section {
50
57
  display: flex;
51
58
  justify-content: space-between;
52
- align-items: center;
59
+ align-items: center;
53
60
  margin-bottom: 24px;
54
- flex-shrink: 0;
55
61
  }
56
62
 
57
- .stock-info {
63
+ .stock-group {
58
64
  display: flex;
59
65
  align-items: center;
60
66
  gap: 16px;
61
67
  }
62
68
 
63
69
  .stock-icon {
64
- width: 48px;
65
- height: 48px;
66
- border-radius: 12px;
67
- background: linear-gradient(135deg, #6366f1, #8b5cf6);
70
+ width: 64px; /* 稍微加大 */
71
+ height: 64px;
72
+ border-radius: 14px;
73
+ background: linear-gradient(135deg, #2b313a, #1f242e);
74
+ border: 1px solid var(--border-color);
68
75
  display: flex;
69
76
  align-items: center;
70
77
  justify-content: center;
71
- font-size: 24px;
72
- box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3);
73
- color: white;
78
+ font-size: 32px;
79
+ color: var(--text-primary);
74
80
  }
75
81
 
76
- .stock-text {
82
+ .stock-meta {
77
83
  display: flex;
78
84
  flex-direction: column;
79
- gap: 4px;
85
+ justify-content: center;
86
+ gap: 8px;
80
87
  }
81
88
 
82
89
  .stock-name {
83
- font-size: 28px;
90
+ font-size: 28px; /* 加大 */
84
91
  font-weight: 700;
85
- color: var(--text-primary);
86
- line-height: 1.2;
92
+ line-height: 1.1;
87
93
  }
88
94
 
89
- .trade-badge {
95
+ /* 徽标容器 */
96
+ .badges-container {
97
+ display: flex;
98
+ gap: 8px;
99
+ }
100
+
101
+ .badge {
90
102
  display: inline-flex;
91
103
  align-items: center;
92
- gap: 6px;
93
- padding: 4px 10px;
104
+ padding: 6px 12px; /* 修改 1: 加大内边距 */
94
105
  border-radius: 6px;
95
- font-size: 12px;
106
+ font-size: 14px; /* 修改 1: 增大字号 (13px -> 14px) */
96
107
  font-weight: 700;
97
108
  text-transform: uppercase;
98
109
  letter-spacing: 0.5px;
99
- width: fit-content;
110
+ line-height: 1;
100
111
  }
101
- .trade-badge.buy { background: var(--up-bg); color: var(--up-color); }
102
- .trade-badge.sell { background: var(--down-bg); color: var(--down-color); }
103
112
 
104
- .price-info {
113
+ .badge.buy { background: var(--buy-bg); color: var(--buy-color); border: 1px solid rgba(248, 113, 113, 0.2); }
114
+ .badge.sell { background: var(--sell-bg); color: var(--sell-color); border: 1px solid rgba(74, 222, 128, 0.2); }
115
+
116
+ .badge.pending {
117
+ background: var(--pending-bg);
118
+ color: var(--pending-color);
119
+ border: 1px solid rgba(234, 179, 8, 0.2);
120
+ }
121
+ .badge.pending::before {
122
+ content: '';
123
+ display: inline-block;
124
+ width: 6px;
125
+ height: 6px;
126
+ border-radius: 50%;
127
+ background-color: currentColor;
128
+ margin-right: 6px;
129
+ animation: pulse 2s infinite;
130
+ }
131
+ @keyframes pulse {
132
+ 0% { opacity: 1; }
133
+ 50% { opacity: 0.4; }
134
+ 100% { opacity: 1; }
135
+ }
136
+
137
+ .price-group {
105
138
  text-align: right;
106
139
  }
107
140
 
108
- .trade-price {
109
- font-family: 'Roboto Mono', monospace;
110
- font-size: 48px;
111
- font-weight: 600;
141
+ .main-price {
142
+ font-size: 56px; /* 加大 */
143
+ font-weight: 700;
144
+ letter-spacing: -2px;
112
145
  line-height: 1;
113
- letter-spacing: -1px;
146
+ margin-bottom: 8px;
147
+ /* 修改 2: 强制使用数字字体 */
148
+ font-family: 'Roboto Mono', monospace;
114
149
  }
115
- .trade-price.buy { color: var(--up-color); }
116
- .trade-price.sell { color: var(--down-color); }
150
+ .main-price.buy { color: var(--buy-color); }
151
+ .main-price.sell { color: var(--sell-color); }
117
152
 
118
- .trade-detail {
119
- margin-top: 8px;
120
- font-size: 14px;
153
+ .sub-detail {
154
+ font-size: 16px; /* 修改 1: 增大字号 (15px -> 16px) */
121
155
  color: var(--text-secondary);
122
- font-weight: 500;
156
+ display: flex;
157
+ align-items: center;
158
+ justify-content: flex-end;
159
+ gap: 16px;
160
+ }
161
+ .sub-detail span {
162
+ display: inline-flex;
163
+ align-items: center;
164
+ gap: 6px;
165
+ }
166
+ .val-highlight {
167
+ color: var(--text-primary);
168
+ font-weight: 600;
169
+ /* 修改 2: 强制使用数字字体 */
170
+ font-family: 'Roboto Mono', monospace;
123
171
  }
124
- .highlight-val { color: var(--text-primary); font-weight: 600; }
125
172
 
126
- /* Chart Area */
127
- .chart-container {
173
+ /* --- 图表区域 --- */
174
+ .chart-wrapper {
128
175
  flex: 1;
129
176
  width: 100%;
130
177
  min-height: 0;
131
- margin: 8px 0 24px 0;
132
- border-radius: 16px;
178
+ margin-bottom: 32px; /* 增加底部间距 */
179
+ border-radius: 12px;
133
180
  background: rgba(0,0,0,0.2);
134
181
  border: 1px solid var(--border-color);
135
182
  position: relative;
136
- overflow: hidden;
137
183
  }
138
184
 
139
185
  canvas {
@@ -142,372 +188,373 @@
142
188
  height: 100%;
143
189
  }
144
190
 
145
- /* Footer Info Grid */
146
- .footer {
191
+ /* --- 底部数据卡片 --- */
192
+ .stats-grid {
147
193
  display: grid;
148
- grid-template-columns: repeat(3, 1fr);
194
+ grid-template-columns: 1fr 1fr 1fr;
149
195
  gap: 20px;
150
- flex-shrink: 0;
196
+ height: 120px; /* 增加高度以容纳更大的字体 */
151
197
  }
152
198
 
153
- .stat-card {
199
+ .stat-item {
154
200
  background: var(--item-bg);
155
- border-radius: 16px;
156
- padding: 20px;
157
201
  border: 1px solid var(--border-color);
202
+ border-radius: 12px;
203
+ padding: 20px 28px;
158
204
  display: flex;
159
205
  flex-direction: column;
160
- gap: 8px;
161
- }
162
-
163
- .stat-card.primary {
164
- background: rgba(99, 102, 241, 0.05);
165
- border-color: rgba(99, 102, 241, 0.3);
166
- }
167
-
168
- .stat-card.profit {
169
- background: rgba(248, 113, 113, 0.05);
170
- border-color: rgba(248, 113, 113, 0.3);
171
- }
172
-
173
- .stat-card.loss {
174
- background: rgba(74, 222, 128, 0.05);
175
- border-color: rgba(74, 222, 128, 0.3);
206
+ justify-content: center;
207
+ position: relative;
176
208
  }
177
209
 
210
+ .stat-item.highlight { background: rgba(88, 166, 255, 0.05); border-color: rgba(88, 166, 255, 0.2); }
211
+ .stat-item.profit { background: rgba(248, 113, 113, 0.05); border-color: rgba(248, 113, 113, 0.2); }
212
+ .stat-item.loss { background: rgba(74, 222, 128, 0.05); border-color: rgba(74, 222, 128, 0.2); }
213
+
178
214
  .stat-label {
179
- font-size: 12px;
180
- font-weight: 600;
181
- color: var(--text-secondary);
215
+ font-size: 14px; /* 修改 1: 增大字号 (13px -> 14px) */
182
216
  text-transform: uppercase;
217
+ color: var(--text-secondary);
183
218
  letter-spacing: 0.5px;
219
+ margin-bottom: 10px;
220
+ font-weight: 600;
184
221
  }
185
222
 
186
223
  .stat-value {
187
- font-size: 24px;
224
+ font-size: 28px; /* 加大 */
188
225
  font-weight: 700;
189
226
  color: var(--text-primary);
227
+ /* 修改 2: 强制使用数字字体 */
190
228
  font-family: 'Roboto Mono', monospace;
191
229
  }
192
230
 
193
- .stat-value.profit { color: var(--up-color); }
194
- .stat-value.loss { color: var(--down-color); }
231
+ .stat-value.profit { color: var(--buy-color); }
232
+ .stat-value.loss { color: var(--sell-color); }
195
233
 
196
234
  .stat-sub {
197
- font-size: 12px;
235
+ font-size: 14px; /* 修改 1: 增大字号 */
198
236
  color: var(--text-secondary);
199
237
  opacity: 0.8;
238
+ margin-top: 6px;
239
+ /* 百分比也属于数字,用一下 Roboto Mono 更好看 */
240
+ font-family: 'Roboto Mono', sans-serif;
200
241
  }
201
242
 
202
- .update-time {
243
+ /* 底部时间戳 */
244
+ .footer-meta {
203
245
  position: absolute;
204
- bottom: 12px;
205
- right: 20px;
206
- font-size: 11px;
246
+ bottom: 0px;
247
+ right: 0px;
248
+ display: flex;
249
+ gap: 16px;
250
+ align-items: center;
251
+ }
252
+
253
+ .time-tag {
254
+ font-size: 12px;
207
255
  color: var(--text-secondary);
208
- opacity: 0.4;
256
+ padding: 6px 8px;
257
+ border-radius: 6px;
258
+ font-family: 'Roboto Mono', sans-serif; /* 包含时间数字 */
209
259
  }
260
+
210
261
  </style>
211
262
  </head>
212
263
  <body>
213
264
  <div class="card">
214
- <div class="header">
215
- <div class="stock-info">
265
+ <!-- 顶部信息栏 -->
266
+ <div class="top-section">
267
+ <div class="stock-group">
216
268
  <div class="stock-icon">⚡</div>
217
- <div class="stock-text">
218
- <div class="stock-name" id="stock-name"></div>
219
- <div class="trade-badge" id="trade-badge">
220
- <span class="icon"></span>
221
- <span class="text"></span>
222
- </div>
269
+ <div class="stock-meta">
270
+ <div class="stock-name" id="stock-name">--</div>
271
+ <!-- 这里会动态插入徽标 -->
272
+ <div class="badges-container" id="badges-container"></div>
223
273
  </div>
224
274
  </div>
225
- <div class="price-info">
226
- <div class="trade-price" id="trade-price"></div>
227
- <div class="trade-detail">
228
- 成交 <span class="highlight-val" id="trade-amount"></span> 股 · 总额 <span class="highlight-val" id="trade-total"></span>
275
+
276
+ <div class="price-group">
277
+ <div class="main-price" id="trade-price">--</div>
278
+ <div class="sub-detail">
279
+ <span>数量 <b class="val-highlight" id="trade-amount">--</b></span>
280
+ <span style="opacity: 0.2">|</span>
281
+ <span>总额 <b class="val-highlight" id="trade-total">--</b></span>
229
282
  </div>
230
283
  </div>
231
284
  </div>
232
285
 
233
- <div class="chart-container" id="chart-container">
286
+ <!-- 图表区域 -->
287
+ <div class="chart-wrapper" id="chart-container">
234
288
  <canvas id="chart"></canvas>
235
289
  </div>
236
290
 
237
- <div class="footer" id="footer"></div>
238
- <div class="update-time" id="update-time"></div>
291
+ <!-- 底部数据 -->
292
+ <div class="stats-grid" id="stats-grid">
293
+ <!-- JS填充 -->
294
+ </div>
295
+
296
+ <!-- 仅用于显示时间,不干扰布局 -->
297
+ <div class="footer-meta">
298
+ <div class="time-tag" id="trade-time">--</div>
299
+ </div>
239
300
  </div>
240
301
 
302
+ <!-- 数据源 -->
241
303
  <script type="application/json" id="data-source">
242
304
  {{DATA}}
243
305
  </script>
306
+
244
307
  <script>
245
- const DATA = JSON.parse(document.getElementById('data-source').textContent);
308
+ // 模拟数据用于测试
309
+ /*
310
+ const TEST_DATA = {
311
+ tradeType: 'buy',
312
+ stockName: 'NVDA · NVIDIA Corp',
313
+ status: 'pending',
314
+ pendingMinutes: 5.5,
315
+ pendingEndTime: '14:35',
316
+ tradePrice: 485.20,
317
+ amount: 10,
318
+ totalCost: 4852.00,
319
+ tradeTime: '2023-10-27 10:30:05',
320
+ newHolding: 150,
321
+ currency: 'USD',
322
+ prices: [480, 482, 481, 483, 485, 484, 486, 485.2, 487, 488, 486],
323
+ timestamps: [1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 11000],
324
+ tradeIndex: 7
325
+ };
326
+ */
327
+
328
+ let DATA;
329
+ try {
330
+ DATA = JSON.parse(document.getElementById('data-source').textContent);
331
+ } catch (e) {
332
+ DATA = typeof TEST_DATA !== 'undefined' ? TEST_DATA : {};
333
+ }
334
+
246
335
  const isBuy = DATA.tradeType === 'buy';
336
+ const status = DATA.status || 'settled';
337
+ const isPending = status === 'pending';
247
338
 
248
- // 颜色定义
249
339
  const colors = {
250
- up: '#f87171',
251
- down: '#4ade80',
252
- text: '#e1e3e6',
253
- grid: '#2a2e39',
254
- label: '#64748b'
340
+ buy: '#f87171',
341
+ sell: '#4ade80',
342
+ pending: '#eab308',
343
+ grid: '#30363d',
344
+ text: '#f0f3f5',
345
+ textDim: '#8b949e'
255
346
  };
256
347
 
257
- // 主色调:买入用红,卖出用绿(或者根据盈亏)
258
- // 这里统一:买入=红(Up/Action),卖出=绿(Down/Action)
259
- const mainColor = isBuy ? colors.up : colors.down;
260
-
261
- // --- 渲染 DOM ---
262
- document.getElementById('stock-name').textContent = DATA.stockName;
263
-
264
- const badge = document.getElementById('trade-badge');
265
- badge.classList.add(DATA.tradeType);
266
- badge.querySelector('.icon').textContent = isBuy ? '📈' : '📉';
267
- badge.querySelector('.text').textContent = isBuy ? '买入成功 BUY' : '卖出成功 SELL';
268
-
269
- const priceEl = document.getElementById('trade-price');
270
- priceEl.textContent = DATA.tradePrice.toFixed(2);
271
- priceEl.classList.add(DATA.tradeType);
348
+ const mainColor = isBuy ? colors.buy : colors.sell;
272
349
 
350
+ // --- 1. 渲染顶部基本信息 ---
351
+ document.getElementById('stock-name').textContent = DATA.stockName || 'Unknown Stock';
352
+ document.getElementById('trade-price').textContent = DATA.tradePrice.toFixed(2);
353
+ document.getElementById('trade-price').className = `main-price ${DATA.tradeType}`;
273
354
  document.getElementById('trade-amount').textContent = DATA.amount;
274
355
  document.getElementById('trade-total').textContent = DATA.totalCost.toFixed(2);
275
- document.getElementById('update-time').textContent = `交易时间 ${DATA.tradeTime}`;
356
+ document.getElementById('trade-time').textContent = (isPending ? '下单时间 ' : '交易时间 ') + DATA.tradeTime;
357
+
358
+ // --- 2. 徽标渲染 ---
359
+ const badgesContainer = document.getElementById('badges-container');
360
+ badgesContainer.innerHTML = '';
361
+
362
+ // (A) 交易类型徽标
363
+ const typeBadge = document.createElement('div');
364
+ typeBadge.className = `badge ${DATA.tradeType}`;
365
+ typeBadge.innerHTML = isBuy ? 'BUY / 买入' : 'SELL / 卖出';
366
+ badgesContainer.appendChild(typeBadge);
367
+
368
+ // (B) 状态徽标
369
+ if (isPending) {
370
+ const pendingBadge = document.createElement('div');
371
+ pendingBadge.className = 'badge pending';
372
+ const pendingText = DATA.pendingEndTime
373
+ ? `PENDING 预计 ${DATA.pendingEndTime} 完成`
374
+ : 'PENDING 挂单处理中';
375
+ pendingBadge.innerHTML = pendingText;
376
+ badgesContainer.appendChild(pendingBadge);
377
+ }
276
378
 
277
- const footer = document.getElementById('footer');
379
+ // --- 3. 底部数据卡片渲染 ---
380
+ const statsGrid = document.getElementById('stats-grid');
278
381
  if (isBuy) {
279
- footer.innerHTML = `
280
- <div class="stat-card primary">
281
- <div class="stat-label">持仓数量 Holdings</div>
382
+ statsGrid.innerHTML = `
383
+ <div class="stat-item highlight">
384
+ <div class="stat-label">Current Holdings 持仓</div>
282
385
  <div class="stat-value">${DATA.newHolding}</div>
283
386
  <div class="stat-sub">股</div>
284
387
  </div>
285
- <div class="stat-card">
286
- <div class="stat-label">成交均价 Avg Price</div>
388
+ <div class="stat-item">
389
+ <div class="stat-label">Avg Price 均价</div>
287
390
  <div class="stat-value">${DATA.tradePrice.toFixed(2)}</div>
288
- <div class="stat-sub">每股</div>
391
+ <div class="stat-sub">${DATA.currency}</div>
289
392
  </div>
290
- <div class="stat-card">
291
- <div class="stat-label">当前市值 Market Value</div>
393
+ <div class="stat-item">
394
+ <div class="stat-label">Total Value 市值</div>
292
395
  <div class="stat-value">${(DATA.newHolding * DATA.tradePrice).toFixed(2)}</div>
293
396
  <div class="stat-sub">${DATA.currency}</div>
294
397
  </div>
295
398
  `;
296
399
  } else {
297
- const hasProfit = DATA.profit !== null;
298
- const isProfit = hasProfit && DATA.profit >= 0;
299
- const profitClass = hasProfit ? (isProfit ? 'profit' : 'loss') : '';
400
+ const profit = DATA.profit;
401
+ const isProfit = profit >= 0;
402
+ const profitClass = isProfit ? 'profit' : 'loss';
300
403
  const profitSign = isProfit ? '+' : '';
301
- const profitColorClass = isProfit ? 'profit' : 'loss';
302
404
 
303
- footer.innerHTML = `
304
- <div class="stat-card primary">
305
- <div class="stat-label">卖出收益 Revenue</div>
405
+ statsGrid.innerHTML = `
406
+ <div class="stat-item highlight">
407
+ <div class="stat-label">Revenue 卖出金额</div>
306
408
  <div class="stat-value">${DATA.totalCost.toFixed(2)}</div>
307
409
  <div class="stat-sub">${DATA.currency}</div>
308
410
  </div>
309
- <div class="stat-card">
310
- <div class="stat-label">买入成本 Cost Basis</div>
311
- <div class="stat-value">${hasProfit ? DATA.buyCost.toFixed(2) : '--'}</div>
312
- <div class="stat-sub">${hasProfit ? '@ ' + DATA.avgBuyPrice.toFixed(2) : '无记录'}</div>
411
+ <div class="stat-item">
412
+ <div class="stat-label">Cost Basis 买入成本</div>
413
+ <div class="stat-value">${DATA.avgBuyPrice ? DATA.avgBuyPrice.toFixed(2) : '--'}</div>
414
+ <div class="stat-sub">每股</div>
313
415
  </div>
314
- <div class="stat-card ${profitClass}">
315
- <div class="stat-label">本次盈亏 P/L</div>
316
- <div class="stat-value ${profitColorClass}">${hasProfit ? profitSign + DATA.profit.toFixed(2) : '--'}</div>
317
- <div class="stat-sub">${hasProfit ? profitSign + DATA.profitPercent.toFixed(2) + '%' : ''}</div>
416
+ <div class="stat-item ${profitClass}">
417
+ <div class="stat-label">P/L 盈亏</div>
418
+ <div class="stat-value ${profitClass}">${profit !== null ? profitSign + profit.toFixed(2) : '--'}</div>
419
+ <div class="stat-sub">${profit !== null ? profitSign + DATA.profitPercent.toFixed(2) + '%' : ''}</div>
318
420
  </div>
319
421
  `;
320
422
  }
321
423
 
322
- // --- 绘制图表 ---
424
+
425
+ // --- 4. 图表绘制 ---
323
426
  const container = document.getElementById('chart-container');
324
427
  const canvas = document.getElementById('chart');
325
-
326
- // 动态设置 Canvas 分辨率以匹配容器大小 (x2 for Retina)
327
428
  const rect = container.getBoundingClientRect();
328
- const dpr = 2; // 强制高分屏渲染
429
+ const dpr = window.devicePixelRatio || 2;
430
+
329
431
  canvas.width = rect.width * dpr;
330
432
  canvas.height = rect.height * dpr;
331
433
 
332
434
  const ctx = canvas.getContext('2d');
333
- ctx.scale(dpr, dpr); // 缩放上下文
435
+ ctx.scale(dpr, dpr);
334
436
 
335
437
  const W = rect.width;
336
438
  const H = rect.height;
337
- const padding = { top: 40, bottom: 40, left: 10, right: 80 }; // 右侧留给价格标签
338
-
339
- const prices = DATA.prices;
340
- const timestamps = DATA.timestamps;
341
- const tradeIndex = DATA.tradeIndex;
342
-
343
- // 计算范围
344
- const maxPrice = Math.max(...prices);
345
- const minPrice = Math.min(...prices);
346
- const range = maxPrice - minPrice || 1;
347
- // 留白
348
- const yMin = minPrice - range * 0.2;
349
- const yMax = maxPrice + range * 0.2;
350
- const yRange = yMax - yMin;
351
-
352
- const tStart = timestamps[0];
353
- const tEnd = timestamps[timestamps.length - 1];
354
- const tRange = tEnd - tStart || 1;
439
+ const pad = { top: 30, bottom: 30, left: 10, right: 60 };
355
440
 
356
- const getX = t => padding.left + ((t - tStart) / tRange) * (W - padding.left - padding.right);
357
- const getY = p => H - padding.bottom - ((p - yMin) / yRange) * (H - padding.top - padding.bottom);
441
+ if (DATA.prices && DATA.prices.length > 0) {
442
+ const prices = DATA.prices;
443
+ const minP = Math.min(...prices);
444
+ const maxP = Math.max(...prices);
445
+ const rangeP = maxP - minP || 1;
446
+
447
+ const yMin = minP - rangeP * 0.2;
448
+ const yMax = maxP + rangeP * 0.2;
449
+ const yRange = yMax - yMin;
450
+
451
+ const stepX = (W - pad.left - pad.right) / (prices.length - 1);
452
+
453
+ const getX = i => pad.left + i * stepX;
454
+ const getY = p => H - pad.bottom - ((p - yMin) / yRange) * (H - pad.top - pad.bottom);
455
+
456
+ // (A) 绘制网格线
457
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.06)';
458
+ ctx.lineWidth = 1;
459
+ const gridLines = 4;
460
+ for (let i = 0; i <= gridLines; i++) {
461
+ const y = H - pad.bottom - (i / gridLines) * (H - pad.top - pad.bottom);
462
+ ctx.beginPath();
463
+ ctx.moveTo(pad.left, y);
464
+ ctx.lineTo(W - pad.right, y);
465
+ ctx.stroke();
466
+
467
+ // 价格标签 (字体也同步调整为 Roboto Mono 且加大)
468
+ const val = yMin + (i / gridLines) * yRange;
469
+ ctx.fillStyle = colors.textDim;
470
+ // 修改 1 & 2: 增大字号到 14px 并使用 Roboto Mono
471
+ ctx.font = '500 14px "Roboto Mono", monospace';
472
+ ctx.textAlign = 'left';
473
+ ctx.fillText(val.toFixed(2), W - pad.right + 8, y + 4);
474
+ }
358
475
 
359
- // 1. 绘制网格
360
- ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
361
- ctx.lineWidth = 1;
362
-
363
- // 横线
364
- const gridCount = 5;
365
- for (let i = 0; i <= gridCount; i++) {
366
- const y = H - padding.bottom - (i / gridCount) * (H - padding.top - padding.bottom);
476
+ // (B) 绘制面积图
477
+ const points = prices.map((p, i) => ({ x: getX(i), y: getY(p) }));
478
+
367
479
  ctx.beginPath();
368
- ctx.moveTo(padding.left, y);
369
- ctx.lineTo(W - padding.right, y);
370
- ctx.stroke();
480
+ ctx.moveTo(points[0].x, H - pad.bottom);
481
+ points.forEach(p => ctx.lineTo(p.x, p.y));
482
+ ctx.lineTo(points[points.length-1].x, H - pad.bottom);
483
+ ctx.closePath();
371
484
 
372
- // Y轴标签
373
- const val = yMin + (i / gridCount) * yRange;
374
- ctx.fillStyle = colors.label;
375
- ctx.font = '500 11px "Roboto Mono", monospace';
376
- ctx.textAlign = 'left';
377
- ctx.textBaseline = 'middle';
378
- ctx.fillText(val.toFixed(2), W - padding.right + 10, y);
379
- }
485
+ const gradient = ctx.createLinearGradient(0, pad.top, 0, H - pad.bottom);
486
+ gradient.addColorStop(0, isBuy ? 'rgba(248, 113, 113, 0.25)' : 'rgba(74, 222, 128, 0.25)');
487
+ gradient.addColorStop(1, 'rgba(0,0,0,0)');
488
+ ctx.fillStyle = gradient;
489
+ ctx.fill();
380
490
 
381
- // 2. 路径点
382
- const points = prices.map((p, i) => ({
383
- x: getX(timestamps[i]),
384
- y: getY(p)
385
- }));
386
-
387
- // 3. 渐变区域
388
- const gradient = ctx.createLinearGradient(0, padding.top, 0, H - padding.bottom);
389
- gradient.addColorStop(0, isBuy ? 'rgba(248, 113, 113, 0.2)' : 'rgba(74, 222, 128, 0.2)');
390
- gradient.addColorStop(1, 'rgba(0,0,0,0)');
391
-
392
- ctx.beginPath();
393
- ctx.moveTo(points[0].x, H - padding.bottom);
394
- if (points.length > 1) {
395
- ctx.lineTo(points[0].x, points[0].y);
396
- for (let i = 0; i < points.length - 1; i++) {
397
- const p0 = points[i];
398
- const p1 = points[i + 1];
399
- const midX = (p0.x + p1.x) / 2;
400
- const midY = (p0.y + p1.y) / 2;
401
- ctx.quadraticCurveTo(p0.x, p0.y, midX, midY);
402
- }
403
- ctx.lineTo(points[points.length - 1].x, points[points.length - 1].y);
404
- }
405
- ctx.lineTo(points[points.length - 1].x, H - padding.bottom);
406
- ctx.closePath();
407
- ctx.fillStyle = gradient;
408
- ctx.fill();
409
-
410
- // 4. 曲线
411
- ctx.beginPath();
412
- ctx.lineWidth = 3;
413
- ctx.strokeStyle = mainColor;
414
- ctx.lineCap = 'round';
415
- ctx.lineJoin = 'round';
416
- if (points.length > 1) {
491
+ // (C) 绘制折线
492
+ ctx.beginPath();
417
493
  ctx.moveTo(points[0].x, points[0].y);
418
494
  for (let i = 0; i < points.length - 1; i++) {
419
- const p0 = points[i];
420
- const p1 = points[i + 1];
421
- const midX = (p0.x + p1.x) / 2;
422
- const midY = (p0.y + p1.y) / 2;
423
- ctx.quadraticCurveTo(p0.x, p0.y, midX, midY);
495
+ const xc = (points[i].x + points[i + 1].x) / 2;
496
+ const yc = (points[i].y + points[i + 1].y) / 2;
497
+ ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc);
424
498
  }
425
- ctx.lineTo(points[points.length - 1].x, points[points.length - 1].y);
426
- }
427
- ctx.stroke();
428
-
429
- // 5. 交易点标记
430
- const tradePoint = points[tradeIndex];
431
-
432
- // 垂直虚线
433
- ctx.beginPath();
434
- ctx.setLineDash([4, 4]);
435
- ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
436
- ctx.lineWidth = 1;
437
- ctx.moveTo(tradePoint.x, padding.top);
438
- ctx.lineTo(tradePoint.x, H - padding.bottom);
439
- ctx.stroke();
440
- ctx.setLineDash([]);
441
-
442
- // 扩散光圈
443
- const glow = ctx.createRadialGradient(tradePoint.x, tradePoint.y, 0, tradePoint.x, tradePoint.y, 20);
444
- glow.addColorStop(0, isBuy ? 'rgba(248, 113, 113, 0.6)' : 'rgba(74, 222, 128, 0.6)');
445
- glow.addColorStop(1, 'rgba(0,0,0,0)');
446
- ctx.fillStyle = glow;
447
- ctx.beginPath();
448
- ctx.arc(tradePoint.x, tradePoint.y, 20, 0, Math.PI * 2);
449
- ctx.fill();
450
-
451
- // 实心点
452
- ctx.fillStyle = '#fff';
453
- ctx.beginPath();
454
- ctx.arc(tradePoint.x, tradePoint.y, 5, 0, Math.PI * 2);
455
- ctx.fill();
456
-
457
- // 交易价格标签
458
- const labelText = DATA.tradePrice.toFixed(2);
459
- ctx.font = 'bold 14px "Roboto Mono", monospace';
460
- const labelW = ctx.measureText(labelText).width + 16;
461
- const labelH = 24;
462
- const labelX = tradePoint.x - labelW / 2;
463
- const labelY = tradePoint.y - 32;
464
-
465
- ctx.fillStyle = mainColor;
466
- ctx.beginPath();
467
- ctx.roundRect(labelX, labelY, labelW, labelH, 6);
468
- ctx.fill();
469
-
470
- // 小三角
471
- ctx.beginPath();
472
- ctx.moveTo(tradePoint.x - 6, labelY + labelH);
473
- ctx.lineTo(tradePoint.x + 6, labelY + labelH);
474
- ctx.lineTo(tradePoint.x, labelY + labelH + 6);
475
- ctx.fill();
476
-
477
- ctx.fillStyle = '#fff';
478
- ctx.textAlign = 'center';
479
- ctx.textBaseline = 'middle';
480
- ctx.fillText(labelText, tradePoint.x, labelY + labelH/2 + 1);
481
-
482
- // 6. 卖出时的买入成本线
483
- if (!isBuy && DATA.avgBuyPrice) {
484
- const buyY = getY(DATA.avgBuyPrice);
499
+ ctx.lineTo(points[points.length-1].x, points[points.length-1].y);
485
500
 
501
+ ctx.strokeStyle = mainColor;
502
+ ctx.lineWidth = 2.5;
503
+ ctx.lineCap = 'round';
504
+ ctx.stroke();
505
+
506
+ // (D) 绘制交易点
507
+ const tIdx = DATA.tradeIndex !== undefined ? DATA.tradeIndex : points.length - 1;
508
+ const tp = points[tIdx];
509
+
486
510
  ctx.beginPath();
487
- ctx.setLineDash([6, 4]);
488
- ctx.strokeStyle = 'rgba(99, 102, 241, 0.5)'; // Indigo
489
- ctx.lineWidth = 1.5;
490
- ctx.moveTo(padding.left, buyY);
491
- ctx.lineTo(W - padding.right, buyY);
511
+ ctx.setLineDash([4, 4]);
512
+ ctx.strokeStyle = 'rgba(255,255,255,0.4)';
513
+ ctx.lineWidth = 1;
514
+ ctx.moveTo(tp.x, pad.top);
515
+ ctx.lineTo(tp.x, H - pad.bottom);
492
516
  ctx.stroke();
493
517
  ctx.setLineDash([]);
494
-
495
- // 标签
496
- const buyLabel = "COST " + DATA.avgBuyPrice.toFixed(2);
497
- ctx.font = 'bold 11px "Roboto Mono", monospace';
498
- const bLabelW = ctx.measureText(buyLabel).width + 12;
499
-
500
- ctx.fillStyle = '#6366f1';
518
+
519
+ const glow = ctx.createRadialGradient(tp.x, tp.y, 0, tp.x, tp.y, 16);
520
+ glow.addColorStop(0, isBuy ? 'rgba(248, 113, 113, 0.6)' : 'rgba(74, 222, 128, 0.6)');
521
+ glow.addColorStop(1, 'rgba(0,0,0,0)');
522
+ ctx.fillStyle = glow;
501
523
  ctx.beginPath();
502
- ctx.roundRect(W - padding.right + 8, buyY - 10, bLabelW, 20, 4);
524
+ ctx.arc(tp.x, tp.y, 16, 0, Math.PI * 2);
503
525
  ctx.fill();
504
-
526
+
505
527
  ctx.fillStyle = '#fff';
506
- ctx.textAlign = 'left';
507
- ctx.textBaseline = 'middle';
508
- ctx.fillText(buyLabel, W - padding.right + 14, buyY);
528
+ ctx.beginPath();
529
+ ctx.arc(tp.x, tp.y, 4, 0, Math.PI * 2);
530
+ ctx.fill();
531
+
532
+ // 价格标签 Tag (同步字体和大小)
533
+ const tagText = DATA.tradePrice.toFixed(2);
534
+ // 修改 1 & 2: 增大字号到 14px 并使用 Roboto Mono
535
+ ctx.font = 'bold 14px "Roboto Mono", monospace';
536
+ const tm = ctx.measureText(tagText);
537
+ const tagW = tm.width + 16;
538
+ const tagH = 26; // 稍微增加高度适应大字号
539
+ const tagX = tp.x - tagW / 2;
540
+ const tagY = tp.y - 36;
541
+
542
+ ctx.fillStyle = mainColor;
543
+ ctx.beginPath();
544
+ ctx.roundRect(tagX, tagY, tagW, tagH, 4);
545
+ ctx.fill();
546
+
547
+ ctx.beginPath();
548
+ ctx.moveTo(tp.x - 4, tagY + tagH);
549
+ ctx.lineTo(tp.x + 4, tagY + tagH);
550
+ ctx.lineTo(tp.x, tagY + tagH + 4);
551
+ ctx.fill();
552
+
553
+ ctx.fillStyle = '#1e232e';
554
+ ctx.textAlign = 'center';
555
+ ctx.fillText(tagText, tp.x, tagY + 17); // 微调垂直对齐
509
556
  }
510
557
 
511
558
  </script>
512
559
  </body>
513
- </html>
560
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-monetary-bourse",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "main": "lib/index.js",
5
5
  "typings": "lib/index.d.ts",
6
6
  "files": [
package/readme.md CHANGED
@@ -1,12 +1,12 @@
1
- # koishi-plugin-monetary-bourse · v2.0.0
1
+ # koishi-plugin-monetary-bourse
2
2
 
3
- [![npm](https://img.shields.io/npm/v/koishi-plugin-monetary-bourse?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-monetary-bourse)
3
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-monetary-bourse?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-monetary-bourse) [![GitHub stars](https://img.shields.io/github/stars/BYWled/koishi-plugin-monetary-bourse?style=flat-square&logo=github)](https://github.com/BYWled/koishi-plugin-monetary-bourse) [![Gitee](https://img.shields.io/badge/Gitee-Project-c71d23?style=flat-square&logo=gitee&logoColor=white)](https://gitee.com/BYWled/koishi-plugin-monetary-bourse)
4
4
 
5
5
  为 Koishi 提供基于 `monetary` 通用货币系统的股票交易所功能。
6
6
 
7
7
  本插件模拟了一个具备自动宏观调控、25种经典K线形态、智能概率博弈和可视化交割单的深度拟真股票市场。用户可以使用机器人通用的货币(如信用点)进行股票买卖、炒股理财。
8
8
 
9
- > 版本:**2.0.0**
9
+ > 版本:**2.0.2**
10
10
 
11
11
  ## ✨ 特性
12
12
 
@@ -141,50 +141,58 @@ A: 股价采用 **"智能期望模型"** 驱动,更贴近真实博弈:
141
141
 
142
142
  ## 更新记录
143
143
 
144
- - **2.0.0**
145
- - **perf/alg**: 提升随机噪声强度,实时曲线更具呼吸感,减少单调走势。
146
- - **ui/layout**: 重构交易详情页布局,分离股票信息与价格展示区域,底部改为 Grid 布局,并将页面外间距增至 64px 以提升视觉呼吸感。
147
- - **feat(ui/display)**: 优化状态徽标逻辑,支持「交易方向」与「挂单中」状态同时并列显示,并为 Pending 状态增加呼吸动画。
148
- - **ui/typography**: 显著增大详情页关键数值与标签字号,回滚并强制使用 Roboto Mono 等宽字体,确保金融数据对齐与专业感。
149
- - **fix(ui/trade)**: 交易结果回单在存在冻结时间时也会即时弹出;修正时间戳位置,将其强制固定于页面右下角。
150
-
151
- - **Alpha.10**
152
- - **feat(core)**: 引入 **25 种** 全新 K 线形态库;新增 `selectPatternByExpectation` 智能调度算法,按价格偏离度动态调整形态概率以实现“估值修复”。
153
- - **feat(ui)**: 新增交易交割单(成交结算图),并将 HTML 模板模板化重构,支持高分辨率渲染与更友好的样式复用。
154
- - **perf**: 优化持仓成本计算、挂单排队体验与兼容旧数据的处理逻辑。
144
+ ### 2.0.2
145
+
146
+ - **doc**: 更新同步更新Gitee和Github链接至`README`
147
+
148
+ ### 2.0.1
149
+ - **fix(build)**: 将静态 HTML 打包进 `lib`,修复构建未更新静态页面的问题
150
+
151
+ ### 2.0.0
152
+ - **perf/alg**: 提升随机噪声强度,实时曲线更具呼吸感,减少单调走势。
153
+ - **ui/layout**: 重构交易详情页布局,分离股票信息与价格展示区域,底部改为 Grid 布局,并将页面外间距增至 64px 以提升视觉呼吸感。
154
+ - **feat(ui/display)**: 优化状态徽标逻辑,支持「交易方向」与「挂单中」状态同时并列显示,并为 Pending 状态增加呼吸动画。
155
+ - **ui/typography**: 显著增大详情页关键数值与标签字号,回滚并强制使用 Roboto Mono 等宽字体,确保金融数据对齐与专业感。
156
+ - **fix(ui/trade)**: 交易结果回单在存在冻结时间时也会即时弹出;修正时间戳位置,将其强制固定于页面右下角。
157
+
158
+ ### Alpha.10
159
+ - **feat(core)**: 引入 **25 种** 全新 K 线形态库;新增 `selectPatternByExpectation` 智能调度算法,按价格偏离度动态调整形态概率以实现“估值修复”。
160
+ - **feat(ui)**: 新增交易交割单(成交结算图),并将 HTML 模板模板化重构,支持高分辨率渲染与更友好的样式复用。
161
+ - **perf**: 优化持仓成本计算、挂单排队体验与兼容旧数据的处理逻辑。
162
+
163
+ ### Alpha.9
164
+ - **fix(build)**: 将静态 HTML 打包进 `lib`,修复构建遗漏导致的插件无法加载问题。
155
165
 
156
- - **Alpha.9**
157
- - **fix(build)**: 将静态 HTML 打包进 `lib`,修复构建遗漏导致的插件无法加载问题。
166
+ ### Alpha.8
167
+ - **feat(ui)**: 提取并模板化走势图(`src/templates/stock-chart.html`),全面视觉优化(深色玻璃拟态、坐标轴与字体改进、增强可读性)。
168
+ - **feat(queue)**: 将挂单改为用户串行排队,防止拆单规避冻结时长(T+0 防绕过)。
169
+ - **perf/alg**: 提升基础波动率与形态影响力,改进周波浪与均值回归;新增 `enableDebug` 调试开关以便开发验证。
158
170
 
159
- - **Alpha.8**
160
- - **feat(ui)**: 提取并模板化走势图(`src/templates/stock-chart.html`),全面视觉优化(深色玻璃拟态、坐标轴与字体改进、增强可读性)。
161
- - **feat(queue)**: 将挂单改为用户串行排队,防止拆单规避冻结时长(T+0 防绕过)。
162
- - **perf/alg**: 提升基础波动率与形态影响力,改进周波浪与均值回归;新增 `enableDebug` 调试开关以便开发验证。
171
+ ### Alpha.7
172
+ - **feat(model)**: 引入几何布朗运动 + 均值回归,添加正态分布噪声与软着陆限幅,改善日内波动特性以提高拟真度。
163
173
 
164
- - **Alpha.7**
165
- - **feat(model)**: 引入几何布朗运动 + 均值回归,添加正态分布噪声与软着陆限幅,改善日内波动特性以提高拟真度。
174
+ ### Alpha.6
175
+ - **refactor**: 采用百分比波动合成模型,解决长期振幅失控并提升稳定性。
176
+ - **fix**: 修正手动宏观调控在部分场景下重置基准价的问题。
166
177
 
167
- - **Alpha.6**
168
- - **refactor**: 采用百分比波动合成模型,解决长期振幅失控并提升稳定性。
169
- - **fix**: 修正手动宏观调控在部分场景下重置基准价的问题。
178
+ ### Alpha.5
179
+ - **refactor**: 重写走势引擎为绝对价格合成(基准价 + 趋势 + 日内波动 + 周波浪 + 噪声),修复长期运行抖动并强化 ±50% 涨跌幅限幅。
170
180
 
171
- - **Alpha.5**
172
- - **refactor**: 重写走势引擎为绝对价格合成(基准价 + 趋势 + 日内波动 + 周波浪 + 噪声),修复长期运行抖动并强化 ±50% 涨跌幅限幅。
181
+ ### 1.1.1 (Alpha.4)
182
+ - **feat**: 引入硬性 ±50% 涨跌幅上限并随机刷新宏观目标以增强稳定性和随机性。
183
+ - **fix**: 避免在更新负载中修改主键;移除过时的手动控制配置;新增调试命令以便验证逻辑。
173
184
 
174
- - **1.1.1 (Alpha.4)**
175
- - **feat**: 引入硬性 ±50% 涨跌幅上限并随机刷新宏观目标以增强稳定性和随机性。
176
- - **fix**: 避免在更新负载中修改主键;移除过时的手动控制配置;新增调试命令以便验证逻辑。
185
+ ### 1.1.0 (Alpha.3)
186
+ - **feat**: 新增持仓盈亏与成本追踪(`totalCost`)及持仓卡片渲染(Puppeteer),提升展示与交互体验。
187
+ - **fix**: 统一金额与价格为两位小数以保证精度与兼容性;当 `maxFreezeTime = 0` 时即时完成交易处理。
177
188
 
178
- - **1.1.0 (Alpha.3)**
179
- - **feat**: 新增持仓盈亏与成本追踪(`totalCost`)及持仓卡片渲染(Puppeteer),提升展示与交互体验。
180
- - **fix**: 统一金额与价格为两位小数以保证精度与兼容性;当 `maxFreezeTime = 0` 时即时完成交易处理。
189
+ ### 1.0.0
190
+ - **fix**: 修复因更新负载包含主键而导致的 `TypeError: cannot modify primary key`,提高数据库兼容性。
181
191
 
182
- - **1.0.0**
183
- - **fix**: 修复因更新负载包含主键而导致的 `TypeError: cannot modify primary key`,提高数据库兼容性。
192
+ ### Alpha.2
193
+ - **feat**: 扩展日内走势剧本并叠加周级波浪,改进 Puppeteer 渲染以减少横轴标签重叠与视觉冲突。
194
+ - **note**: 历史版本曾存在主键相关问题,后续版本已逐步修复。
184
195
 
185
- - **Alpha.2**
186
- - **feat**: 扩展日内走势剧本并叠加周级波浪,改进 Puppeteer 渲染以减少横轴标签重叠与视觉冲突。
187
- - **note**: 历史版本曾存在主键相关问题,后续版本已逐步修复。
196
+ ### Alpha.1
197
+ - **feat**: 初始发布:实现买卖、挂单、资金冻结、历史行情存储及 `stock` 系列基础指令,支持与 `monetary` / `monetary-bank` 的基础联动。
188
198
 
189
- - **Alpha.1**
190
- - **feat**: 初始发布:实现买卖、挂单、资金冻结、历史行情存储及 `stock` 系列基础指令,支持与 `monetary` / `monetary-bank` 的基础联动。