@kamuira/stock-analyzer 1.2.2 → 1.2.4
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/analyze.js +0 -3
- package/index.html +84 -49
- package/package.json +1 -1
package/analyze.js
CHANGED
package/index.html
CHANGED
|
@@ -72,31 +72,42 @@
|
|
|
72
72
|
.result { display: none; }
|
|
73
73
|
.result.active { display: block; }
|
|
74
74
|
|
|
75
|
-
/* 信号卡片 */
|
|
75
|
+
/* 信号卡片 — 中性底色 + 顶部强调条 + 微渐变 */
|
|
76
76
|
.signal-card {
|
|
77
|
-
text-align: center; padding: 30px; border-radius: 12px;
|
|
78
|
-
margin-bottom: 20px; position: relative;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
.signal-card.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.signal-card.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
77
|
+
text-align: center; padding: 28px 30px 26px; border-radius: 12px;
|
|
78
|
+
margin-bottom: 20px; position: relative;
|
|
79
|
+
background: #141e28; border: 1px solid #1f2d3a; border-top: 4px solid #555;
|
|
80
|
+
}
|
|
81
|
+
.signal-card.strong-buy {
|
|
82
|
+
border-top-color: #ff4444;
|
|
83
|
+
background: linear-gradient(180deg, rgba(255,68,68,0.10), rgba(255,68,68,0) 55%), #141e28;
|
|
84
|
+
}
|
|
85
|
+
.signal-card.buy {
|
|
86
|
+
border-top-color: #ff7043;
|
|
87
|
+
background: linear-gradient(180deg, rgba(255,112,67,0.07), rgba(255,112,67,0) 55%), #141e28;
|
|
88
|
+
}
|
|
89
|
+
.signal-card.weak-buy {
|
|
90
|
+
border-top-color: #ff9800;
|
|
91
|
+
background: linear-gradient(180deg, rgba(255,152,0,0.06), rgba(255,152,0,0) 55%), #141e28;
|
|
92
|
+
}
|
|
93
|
+
.signal-card.neutral { border-top-color: #888; }
|
|
94
|
+
.signal-card.sell {
|
|
95
|
+
border-top-color: #26c281;
|
|
96
|
+
background: linear-gradient(180deg, rgba(38,194,129,0.06), rgba(38,194,129,0) 55%), #141e28;
|
|
97
|
+
}
|
|
98
|
+
.signal-card.strong-sell {
|
|
99
|
+
border-top-color: #00cc66;
|
|
100
|
+
background: linear-gradient(180deg, rgba(0,204,102,0.10), rgba(0,204,102,0) 55%), #141e28;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.signal-card .stock-info { margin-bottom: 14px; }
|
|
93
104
|
.signal-card .stock-name { font-size: 22px; font-weight: bold; color: #fff; }
|
|
94
105
|
.signal-card .stock-code { font-size: 13px; color: #888; margin-left: 8px; }
|
|
95
|
-
.signal-card .stock-price { font-size:
|
|
106
|
+
.signal-card .stock-price { font-size: 30px; font-weight: bold; margin: 6px 0 2px; letter-spacing: 0.5px; }
|
|
96
107
|
.signal-card .stock-change { font-size: 14px; }
|
|
97
|
-
.signal-card .signal-text { font-size: 24px; font-weight: bold; margin:
|
|
98
|
-
.signal-card .signal-score { font-size: 13px; color: #
|
|
99
|
-
.signal-card .signal-advice { font-size: 14px; color: #
|
|
108
|
+
.signal-card .signal-text { font-size: 24px; font-weight: bold; margin: 16px 0 4px; letter-spacing: 1px; }
|
|
109
|
+
.signal-card .signal-score { font-size: 13px; color: #8a9aa8; }
|
|
110
|
+
.signal-card .signal-advice { font-size: 14px; color: #c4d0db; margin-top: 10px; }
|
|
100
111
|
|
|
101
112
|
.up { color: #ff4444; }
|
|
102
113
|
.down { color: #00cc66; }
|
|
@@ -218,9 +229,6 @@
|
|
|
218
229
|
<button onclick="quickAnalyze('sh600111')">北方稀土</button>
|
|
219
230
|
<button onclick="quickAnalyze('sh601318')">中国平安</button>
|
|
220
231
|
<button onclick="quickAnalyze('sh601066')">中信建投</button>
|
|
221
|
-
<button onclick="quickAnalyze('2330')">台积电</button>
|
|
222
|
-
<button onclick="quickAnalyze('2317')">鸿海</button>
|
|
223
|
-
<button onclick="quickAnalyze('2454')">联发科</button>
|
|
224
232
|
</div>
|
|
225
233
|
<div class="shortcuts" id="customShortcuts"></div>
|
|
226
234
|
|
|
@@ -333,38 +341,48 @@
|
|
|
333
341
|
// 回测结果卡片
|
|
334
342
|
if (backtestData && !backtestData.error) {
|
|
335
343
|
const bt = backtestData;
|
|
336
|
-
const
|
|
337
|
-
const s5 = bt.summary['5'] || {winRate:0,avgReturn:0};
|
|
338
|
-
const s10 = bt.summary['10'] || {winRate:0,avgReturn:0};
|
|
344
|
+
const long = bt.long || {total:0,winRate:0,avgReturn:0,totalReturn:0,avgWin:0,avgLoss:0,avgHoldDays:0};
|
|
339
345
|
const gradeColor = bt.grade === '优秀' ? '#4caf50' : bt.grade === '良好' ? '#2196f3' : bt.grade === '一般' ? '#ff9800' : '#f44336';
|
|
346
|
+
const winRateColor = long.winRate >= 55 ? '#4caf50' : long.winRate >= 50 ? '#ff9800' : '#f44336';
|
|
347
|
+
const avgReturnColor = long.avgReturn > 1.5 ? '#4caf50' : long.avgReturn > 0 ? '#ff9800' : '#f44336';
|
|
340
348
|
const safeName = (bt.name||'').replace(/['"<>&]/g, '');
|
|
341
349
|
html += `<div style="background:#1a2530;border:1px solid ${gradeColor};margin-bottom:20px;padding:20px;border-radius:10px">
|
|
342
350
|
<h3 style="color:${gradeColor};margin-bottom:12px;font-size:15px">回测验证: ${safeName} (${bt.code}) — 评级: ${bt.grade}</h3>
|
|
343
|
-
<div style="font-size:12px;color:#888;margin-bottom:10px">数据: ${bt.dataRange} |
|
|
344
|
-
|
|
351
|
+
<div style="font-size:12px;color:#888;margin-bottom:10px">数据: ${bt.dataRange || '-'} | 引擎: ${bt.engine || '-'} | 做多交易 ${long.total} 笔 | 平均持仓 ${long.avgHoldDays || 0} 天</div>
|
|
352
|
+
${long.total > 0 ? `<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:12px">
|
|
345
353
|
<div style="text-align:center;padding:8px;background:#141e28;border-radius:6px">
|
|
346
|
-
<div style="font-size:11px;color:#888"
|
|
347
|
-
<div style="font-size:18px;font-weight:bold;color:${
|
|
348
|
-
<div style="font-size:11px;color:#888"
|
|
354
|
+
<div style="font-size:11px;color:#888">做多胜率</div>
|
|
355
|
+
<div style="font-size:18px;font-weight:bold;color:${winRateColor}">${long.winRate}%</div>
|
|
356
|
+
<div style="font-size:11px;color:#888">${long.wins||0}胜 / ${long.losses||0}负</div>
|
|
349
357
|
</div>
|
|
350
358
|
<div style="text-align:center;padding:8px;background:#141e28;border-radius:6px">
|
|
351
|
-
<div style="font-size:11px;color:#888"
|
|
352
|
-
<div style="font-size:18px;font-weight:bold;color:${
|
|
353
|
-
<div style="font-size:11px;color:#888"
|
|
359
|
+
<div style="font-size:11px;color:#888">单笔均收益</div>
|
|
360
|
+
<div style="font-size:18px;font-weight:bold;color:${avgReturnColor}">${long.avgReturn}%</div>
|
|
361
|
+
<div style="font-size:11px;color:#888">累计 ${long.totalReturn}%</div>
|
|
354
362
|
</div>
|
|
355
363
|
<div style="text-align:center;padding:8px;background:#141e28;border-radius:6px">
|
|
356
|
-
<div style="font-size:11px;color:#888"
|
|
357
|
-
<div style="font-size:18px;font-weight:bold;color
|
|
358
|
-
<div style="font-size:11px;color:#888"
|
|
364
|
+
<div style="font-size:11px;color:#888">盈亏比</div>
|
|
365
|
+
<div style="font-size:18px;font-weight:bold;color:#fff">${long.avgWin || 0} : ${Math.abs(long.avgLoss || 0)}</div>
|
|
366
|
+
<div style="font-size:11px;color:#888">均盈 : 均亏</div>
|
|
359
367
|
</div>
|
|
360
|
-
</div
|
|
361
|
-
<button onclick="saveToShortcuts('${bt.code}','${safeName}','${
|
|
368
|
+
</div>` : `<div style="padding:12px;text-align:center;color:#888;font-size:13px;background:#141e28;border-radius:6px;margin-bottom:12px">回测周期内无做多入场信号</div>`}
|
|
369
|
+
<button onclick="saveToShortcuts('${bt.code}','${safeName}','${long.winRate}','${bt.grade}')" style="padding:8px 16px;border:1px solid ${gradeColor};border-radius:6px;background:transparent;color:${gradeColor};cursor:pointer;font-size:13px">+ 加入快捷列表</button>
|
|
362
370
|
<span style="font-size:11px;color:#666;margin-left:10px">加入后可在顶部快速访问</span>
|
|
363
371
|
</div>`;
|
|
364
372
|
}
|
|
365
373
|
|
|
366
|
-
// 信号卡片
|
|
367
|
-
|
|
374
|
+
// 信号卡片 - 按 totalScore 派生 class(后端不返回 signalClass)
|
|
375
|
+
const ts = summary.totalScore;
|
|
376
|
+
const signalClass = summary.signalClass
|
|
377
|
+
|| (ts >= 10 ? 'strong-buy'
|
|
378
|
+
: ts >= 5 ? 'buy'
|
|
379
|
+
: ts >= 1 ? 'weak-buy'
|
|
380
|
+
: ts >= -4 ? 'neutral'
|
|
381
|
+
: ts >= -9 ? 'sell'
|
|
382
|
+
: 'strong-sell');
|
|
383
|
+
const signalIsBullish = ts >= 1;
|
|
384
|
+
const signalIsBearish = ts < -4;
|
|
385
|
+
html += `<div class="signal-card ${signalClass}">
|
|
368
386
|
<div class="stock-info">
|
|
369
387
|
<span class="stock-name">${rt.name}</span>
|
|
370
388
|
<span class="stock-code">${rt.code}</span>
|
|
@@ -374,13 +392,13 @@
|
|
|
374
392
|
<div class="stock-change ${direction}">
|
|
375
393
|
${rt.change > 0 ? '+' : ''}${rt.change.toFixed(2)} (${rt.changePct > 0 ? '+' : ''}${rt.changePct.toFixed(2)}%)
|
|
376
394
|
</div>
|
|
377
|
-
<div class="signal-text ${
|
|
378
|
-
<div class="signal-score">综合评分: ${summary.totalScore}
|
|
395
|
+
<div class="signal-text ${signalIsBullish ? 'up' : signalIsBearish ? 'down' : 'flat'}">${summary.signal}</div>
|
|
396
|
+
<div class="signal-score">综合评分: ${summary.totalScore} 分${rr && rr.positionAdvice ? ' | ' + rr.positionAdvice : ''}</div>
|
|
379
397
|
<div class="signal-advice">${summary.advice}</div>
|
|
380
398
|
</div>`;
|
|
381
399
|
|
|
382
400
|
// 操作建议大卡片(最显眼)
|
|
383
|
-
const posAdvice =
|
|
401
|
+
const posAdvice = (rr && rr.positionAdvice) || '';
|
|
384
402
|
const holdAdvice = summary.totalScore >= 10 ? '建议持仓5-10天,趋势跟踪' : summary.totalScore >= 5 ? '建议持仓3-5天,短线波段' : summary.totalScore >= 1 ? '建议持仓1-3天,快进快出' : '不建议开仓';
|
|
385
403
|
const signalColor = summary.totalScore >= 5 ? '#4caf50' : summary.totalScore >= 1 ? '#ff9800' : summary.totalScore >= -4 ? '#888' : '#f44336';
|
|
386
404
|
|
|
@@ -406,11 +424,11 @@
|
|
|
406
424
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
|
|
407
425
|
<div style="background:#122a1a;border:1px solid #2d5a2d;border-radius:10px;padding:16px">
|
|
408
426
|
<div style="font-size:14px;font-weight:bold;color:#4caf50;margin-bottom:10px">买入操作</div>
|
|
409
|
-
${buyConditions.map(c => `<div style="padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.05);font-size:13px;color:#ccc"
|
|
427
|
+
${buyConditions.map(c => { const m = (typeof c === 'string' ? c : '').match(/^\[([^\]]+)\]\s*(.*)$/); const p = m ? m[1] : ''; const t = m ? m[2] : (c || ''); return `<div style="padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.05);font-size:13px;color:#ccc">${p ? `<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;margin-right:6px;background:#2d4a2d;color:#8bc34a">${p}</span>` : ''}${t}</div>`; }).join('')}
|
|
410
428
|
</div>
|
|
411
429
|
<div style="background:#2a1a1a;border:1px solid #5a2d2d;border-radius:10px;padding:16px">
|
|
412
430
|
<div style="font-size:14px;font-weight:bold;color:#f44336;margin-bottom:10px">卖出/止损</div>
|
|
413
|
-
${sellConditions.map(c => `<div style="padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.05);font-size:13px;color:#ccc"
|
|
431
|
+
${sellConditions.map(c => { const m = (typeof c === 'string' ? c : '').match(/^\[([^\]]+)\]\s*(.*)$/); const p = m ? m[1] : ''; const t = m ? m[2] : (c || ''); return `<div style="padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.05);font-size:13px;color:#ccc">${p ? `<span style="display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;margin-right:6px;background:#4a2d2d;color:#ff8a80">${p}</span>` : ''}${t}</div>`; }).join('')}
|
|
414
432
|
</div>
|
|
415
433
|
</div>
|
|
416
434
|
${rr ? `<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:14px">
|
|
@@ -450,7 +468,15 @@
|
|
|
450
468
|
|
|
451
469
|
// 支撑压力位
|
|
452
470
|
html += `<div class="levels">
|
|
453
|
-
${sr.map(s =>
|
|
471
|
+
${(sr || []).map(s => {
|
|
472
|
+
if (typeof s === 'string') {
|
|
473
|
+
const idx = s.lastIndexOf(': ');
|
|
474
|
+
const label = idx > -1 ? s.slice(0, idx) : s;
|
|
475
|
+
const value = idx > -1 ? s.slice(idx + 2) : '';
|
|
476
|
+
return `<div class="level-item"><div class="level-label">${label}</div><div class="level-value">${value}</div></div>`;
|
|
477
|
+
}
|
|
478
|
+
return `<div class="level-item"><div class="level-label">${s.label || s.description || ''}</div><div class="level-value">${s.value || ''}</div>${s.reliability ? `<div style="font-size:11px;color:#888;margin-top:2px">触及${s.touchCount}次 反弹${s.bounceCount}次 可靠性:${s.reliability}</div>` : ''}</div>`;
|
|
479
|
+
}).join('')}
|
|
454
480
|
</div>`;
|
|
455
481
|
|
|
456
482
|
// 各指标详情
|
|
@@ -484,7 +510,16 @@
|
|
|
484
510
|
${scoreText}
|
|
485
511
|
</div>
|
|
486
512
|
<div class="section-body">
|
|
487
|
-
${d.signals.map(s =>
|
|
513
|
+
${d.signals.map(s => {
|
|
514
|
+
const m = (typeof s === 'string' ? s : '').match(/^\[([^\]]+)\]\s*(.*)$/);
|
|
515
|
+
if (!m) return `<div class="signal-item">${s}</div>`;
|
|
516
|
+
const tag = m[1], rest = m[2];
|
|
517
|
+
const bullish = /多|涨|强|底/.test(tag);
|
|
518
|
+
const bearish = /空|跌|弱|顶/.test(tag);
|
|
519
|
+
const bg = bullish ? '#1a3a1a' : bearish ? '#3a1a1a' : '#1a2a3a';
|
|
520
|
+
const fg = bullish ? '#8bc34a' : bearish ? '#ff8a80' : '#7fa9d4';
|
|
521
|
+
return `<div class="signal-item"><span style="display:inline-block;padding:1px 7px;border-radius:4px;font-size:11px;margin-right:6px;background:${bg};color:${fg}">${tag}</span>${rest}</div>`;
|
|
522
|
+
}).join('')}
|
|
488
523
|
</div>
|
|
489
524
|
</div>`;
|
|
490
525
|
}
|