@arcaneorion/dsh-teaching-board 0.4.1 → 0.5.1
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 +35 -4
- package/cordis.patch.yml +6 -2
- package/package.json +5 -3
- package/skills/stage-panel/SKILL.md +107 -0
- package/skills/stage-panel/references/diagram-selection.md +80 -0
- package/skills/stage-panel/references/html-patterns.md +93 -0
- package/skills/stage-panel/references/visual-style.md +43 -0
- package/skills/stage-panel/submode/code/SKILL.md +17 -0
- package/skills/stage-panel/submode/code/examples/go-hello-channel.json +36 -0
- package/skills/stage-panel/submode/code/examples/go-http-api-basics.json +58 -0
- package/skills/stage-panel/submode/code/examples/py-agent-loop.json +79 -0
- package/skills/stage-panel/submode/code/examples/py-ai-parameters-intro.json +65 -0
- package/skills/stage-panel/submode/code/examples/py-fizzbuzz-intro.json +74 -0
- package/skills/stage-panel/submode/code/examples/py-training-loop.json +114 -0
- package/skills/stage-panel/submode/code/examples/rs-move-basics.json +118 -0
- package/skills/stage-panel/submode/code/references/host-toolchains.md +61 -0
- package/skills/stage-panel/submode/code/schemas/lesson.schema.json +225 -0
- package/skills/stage-panel/submode/code/templates/lab.html +561 -0
- package/skills/stage-panel/submode/game-study/SKILL.md +16 -0
- package/skills/stage-panel/submode/game-study/examples/hft-microstructure.json +60 -0
- package/skills/stage-panel/submode/game-study/examples/securities-law.json +57 -0
- package/skills/stage-panel/submode/game-study/schemas/quest.schema.json +95 -0
- package/skills/stage-panel/submode/game-study/templates/quiz.html +516 -0
- package/skills/stage-panel/submode/learn/SKILL.md +16 -0
- package/skills/stage-panel/submode/learn/examples/epsilon-delta.json +140 -0
- package/skills/stage-panel/submode/learn/examples/template-showcase.json +147 -0
- package/skills/stage-panel/submode/learn/schemas/lesson.schema.json +184 -0
- package/skills/stage-panel/submode/learn/templates/lesson.html +782 -0
- package/skills/stage-panel/submode/ml/SKILL.md +9 -0
- package/skills/stage-panel/submode/ml/examples/gd.json +7 -0
- package/skills/stage-panel/submode/ml/templates/gd.html +217 -0
- package/skills/stage-panel/submode/quant/SKILL.md +9 -0
- package/skills/stage-panel/submode/quant/references/export_backtest_data.py +192 -0
- package/skills/stage-panel/submode/quant/templates/dashboard.html +381 -0
- package/src/client.js +177 -15
- package/src/index.js +11 -0
- package/src/skills.js +88 -0
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Code 展示</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: dark;
|
|
10
|
+
--bg: #0d1117;
|
|
11
|
+
--panel: #161b22;
|
|
12
|
+
--line: #30363d;
|
|
13
|
+
--text: #e6edf3;
|
|
14
|
+
--muted: #8b949e;
|
|
15
|
+
--accent: #3fb950;
|
|
16
|
+
--blue: #58a6ff;
|
|
17
|
+
--warn: #d29922;
|
|
18
|
+
--err: #f85149;
|
|
19
|
+
--kw: #ff7b72;
|
|
20
|
+
--str: #a5d6ff;
|
|
21
|
+
--com: #8b949e;
|
|
22
|
+
--num: #79c0ff;
|
|
23
|
+
--type: #ffa657;
|
|
24
|
+
--fn: #d2a8ff;
|
|
25
|
+
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
26
|
+
--sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
27
|
+
}
|
|
28
|
+
* { box-sizing: border-box; }
|
|
29
|
+
html, body {
|
|
30
|
+
margin: 0;
|
|
31
|
+
padding: 0;
|
|
32
|
+
background: var(--bg);
|
|
33
|
+
color: var(--text);
|
|
34
|
+
font-family: var(--sans);
|
|
35
|
+
}
|
|
36
|
+
button, textarea, pre { font: inherit; }
|
|
37
|
+
button { cursor: pointer; }
|
|
38
|
+
|
|
39
|
+
.wrap {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
gap: 12px;
|
|
43
|
+
max-width: 1080px;
|
|
44
|
+
margin: 0 auto;
|
|
45
|
+
padding: 16px;
|
|
46
|
+
}
|
|
47
|
+
.bar {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-wrap: wrap;
|
|
50
|
+
align-items: flex-start;
|
|
51
|
+
justify-content: space-between;
|
|
52
|
+
gap: 10px;
|
|
53
|
+
}
|
|
54
|
+
.meta { min-width: 0; }
|
|
55
|
+
.lang {
|
|
56
|
+
display: inline-block;
|
|
57
|
+
font-family: var(--mono);
|
|
58
|
+
font-size: 11px;
|
|
59
|
+
text-transform: uppercase;
|
|
60
|
+
letter-spacing: .1em;
|
|
61
|
+
color: var(--accent);
|
|
62
|
+
border: 1px solid #238636;
|
|
63
|
+
border-radius: 999px;
|
|
64
|
+
padding: 1px 8px;
|
|
65
|
+
margin-bottom: 6px;
|
|
66
|
+
}
|
|
67
|
+
h1 {
|
|
68
|
+
margin: 0;
|
|
69
|
+
font-size: 20px;
|
|
70
|
+
line-height: 1.25;
|
|
71
|
+
}
|
|
72
|
+
.sub { margin: 4px 0 0; font-size: 13px; color: var(--muted); }
|
|
73
|
+
.btns { display: flex; gap: 8px; align-items: center; }
|
|
74
|
+
.btn {
|
|
75
|
+
border: 1px solid var(--line);
|
|
76
|
+
background: #21262d;
|
|
77
|
+
color: var(--text);
|
|
78
|
+
border-radius: 6px;
|
|
79
|
+
padding: 6px 12px;
|
|
80
|
+
font-size: 13px;
|
|
81
|
+
line-height: 1.2;
|
|
82
|
+
}
|
|
83
|
+
.btn.primary-soft {
|
|
84
|
+
background: #1f6feb33;
|
|
85
|
+
border-color: #1f6feb;
|
|
86
|
+
color: var(--blue);
|
|
87
|
+
}
|
|
88
|
+
.btn:hover { filter: brightness(1.12); }
|
|
89
|
+
.btn:disabled { opacity: .55; cursor: not-allowed; }
|
|
90
|
+
|
|
91
|
+
.tutor-row {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: 10px;
|
|
95
|
+
flex-wrap: wrap;
|
|
96
|
+
}
|
|
97
|
+
.tutor-note {
|
|
98
|
+
flex: 1 1 240px;
|
|
99
|
+
background: #0f141a;
|
|
100
|
+
border: 1px solid var(--line);
|
|
101
|
+
border-radius: 6px;
|
|
102
|
+
color: var(--text);
|
|
103
|
+
padding: 8px 10px;
|
|
104
|
+
font-size: 13px;
|
|
105
|
+
}
|
|
106
|
+
.tutor-note:focus { border-color: var(--blue); outline: none; }
|
|
107
|
+
.tutor-hint { font-size: 11px; color: var(--muted); }
|
|
108
|
+
|
|
109
|
+
.layout {
|
|
110
|
+
display: grid;
|
|
111
|
+
grid-template-columns: minmax(0, 1.5fr) minmax(240px, 1fr);
|
|
112
|
+
gap: 12px;
|
|
113
|
+
align-items: start;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.code-card {
|
|
117
|
+
border: 1px solid var(--line);
|
|
118
|
+
border-radius: 8px;
|
|
119
|
+
background: var(--panel);
|
|
120
|
+
overflow: hidden;
|
|
121
|
+
}
|
|
122
|
+
.filebar {
|
|
123
|
+
display: flex;
|
|
124
|
+
justify-content: space-between;
|
|
125
|
+
gap: 8px;
|
|
126
|
+
padding: 8px 12px;
|
|
127
|
+
border-bottom: 1px solid var(--line);
|
|
128
|
+
font-family: var(--mono);
|
|
129
|
+
font-size: 12px;
|
|
130
|
+
color: var(--muted);
|
|
131
|
+
background: #0f141a;
|
|
132
|
+
}
|
|
133
|
+
.filebar b { color: var(--text); font-weight: 600; }
|
|
134
|
+
.code-stack {
|
|
135
|
+
position: relative;
|
|
136
|
+
background: #0b0f14;
|
|
137
|
+
max-height: 70vh;
|
|
138
|
+
overflow: auto;
|
|
139
|
+
margin: 0;
|
|
140
|
+
padding: 12px 14px;
|
|
141
|
+
white-space: pre;
|
|
142
|
+
tab-size: 4;
|
|
143
|
+
font-family: var(--mono);
|
|
144
|
+
font-size: 14px;
|
|
145
|
+
line-height: 1.55;
|
|
146
|
+
color: #dbe7f0;
|
|
147
|
+
}
|
|
148
|
+
.code-stack:empty::before { content: '(无代码)'; color: var(--muted); font-style: italic; }
|
|
149
|
+
|
|
150
|
+
.tok-kw { color: var(--kw); }
|
|
151
|
+
.tok-str { color: var(--str); }
|
|
152
|
+
.tok-com { color: var(--com); font-style: italic; }
|
|
153
|
+
.tok-num { color: var(--num); }
|
|
154
|
+
.tok-type { color: var(--type); }
|
|
155
|
+
.tok-fn { color: var(--fn); }
|
|
156
|
+
.tok-op { color: #ffA198; }
|
|
157
|
+
|
|
158
|
+
.side-card { padding: 12px; display: grid; gap: 12px; }
|
|
159
|
+
.block h2 {
|
|
160
|
+
margin: 0 0 6px;
|
|
161
|
+
font-size: 11px;
|
|
162
|
+
letter-spacing: .08em;
|
|
163
|
+
text-transform: uppercase;
|
|
164
|
+
color: var(--muted);
|
|
165
|
+
}
|
|
166
|
+
.block p, .block li {
|
|
167
|
+
margin: 0;
|
|
168
|
+
font-size: 13px;
|
|
169
|
+
line-height: 1.45;
|
|
170
|
+
color: #c9d1d9;
|
|
171
|
+
}
|
|
172
|
+
.block ul { margin: 0; padding-left: 1.1em; display: grid; gap: 6px; }
|
|
173
|
+
.ann { display: grid; gap: 6px; }
|
|
174
|
+
.ann div {
|
|
175
|
+
border-left: 3px solid var(--blue);
|
|
176
|
+
background: #0f141a;
|
|
177
|
+
padding: 6px 8px;
|
|
178
|
+
font-size: 12px;
|
|
179
|
+
}
|
|
180
|
+
.ann .error { border-left-color: var(--err); }
|
|
181
|
+
.ann .move, .ann .warn { border-left-color: var(--warn); }
|
|
182
|
+
.pipe {
|
|
183
|
+
font-family: var(--mono);
|
|
184
|
+
font-size: 11px;
|
|
185
|
+
color: var(--muted);
|
|
186
|
+
}
|
|
187
|
+
.hidden { display: none !important; }
|
|
188
|
+
|
|
189
|
+
@media (max-width: 860px) {
|
|
190
|
+
.layout { grid-template-columns: 1fr; }
|
|
191
|
+
}
|
|
192
|
+
</style>
|
|
193
|
+
</head>
|
|
194
|
+
<body>
|
|
195
|
+
<div class="wrap">
|
|
196
|
+
<div class="bar">
|
|
197
|
+
<div class="meta">
|
|
198
|
+
<div class="lang" id="lang">python</div>
|
|
199
|
+
<h1 id="title">Code 展示</h1>
|
|
200
|
+
<p class="sub" id="subtitle"></p>
|
|
201
|
+
</div>
|
|
202
|
+
<div class="btns">
|
|
203
|
+
<button class="btn primary-soft" id="btn-send" type="button">发给导师讲解</button>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<div class="tutor-row">
|
|
208
|
+
<input id="tutor-note" class="tutor-note" type="text" maxlength="500" placeholder="发给导师时的可选问题(可不填,直接点按钮)" />
|
|
209
|
+
<span class="tutor-hint" id="tutor-hint">作为讲解素材呈现代码;点按钮附当前代码发给 AI 导师。</span>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<div class="layout">
|
|
213
|
+
<div class="code-card">
|
|
214
|
+
<div class="filebar">
|
|
215
|
+
<span><b id="filename">main.py</b></span>
|
|
216
|
+
<span>展示 · 讲解参考</span>
|
|
217
|
+
</div>
|
|
218
|
+
<pre class="code-stack" id="code"></pre>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<aside class="side-card">
|
|
222
|
+
<div class="block" id="summary-block">
|
|
223
|
+
<h2>本课</h2>
|
|
224
|
+
<p id="summary"></p>
|
|
225
|
+
</div>
|
|
226
|
+
<div class="block" id="objectives-block">
|
|
227
|
+
<h2>目标</h2>
|
|
228
|
+
<ul id="objectives"></ul>
|
|
229
|
+
</div>
|
|
230
|
+
<div class="block" id="ann-block">
|
|
231
|
+
<h2>行注</h2>
|
|
232
|
+
<div class="ann" id="annotations"></div>
|
|
233
|
+
</div>
|
|
234
|
+
<div class="block" id="exercise-block">
|
|
235
|
+
<h2>练习</h2>
|
|
236
|
+
<ul id="exercises"></ul>
|
|
237
|
+
</div>
|
|
238
|
+
<div class="block" id="hint-block">
|
|
239
|
+
<h2>提示</h2>
|
|
240
|
+
<ul id="hints"></ul>
|
|
241
|
+
</div>
|
|
242
|
+
<div class="pipe" id="pipe">就绪</div>
|
|
243
|
+
</aside>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
|
|
247
|
+
<script>
|
|
248
|
+
const LESSON = __LOCALWEB_LESSON_DATA__;
|
|
249
|
+
const INPUT_ID = "__LOCALWEB_INPUT_ID__";
|
|
250
|
+
const PANEL_ID = "__LOCALWEB_PANEL_ID__";
|
|
251
|
+
const MENTOR = __LOCALWEB_MENTOR__;
|
|
252
|
+
|
|
253
|
+
const $ = (id) => document.getElementById(id);
|
|
254
|
+
const els = {
|
|
255
|
+
lang: $('lang'),
|
|
256
|
+
title: $('title'),
|
|
257
|
+
subtitle: $('subtitle'),
|
|
258
|
+
summary: $('summary'),
|
|
259
|
+
objectives: $('objectives'),
|
|
260
|
+
annotations: $('annotations'),
|
|
261
|
+
exercises: $('exercises'),
|
|
262
|
+
hints: $('hints'),
|
|
263
|
+
filename: $('filename'),
|
|
264
|
+
code: $('code'),
|
|
265
|
+
pipe: $('pipe'),
|
|
266
|
+
btnSend: $('btn-send'),
|
|
267
|
+
tutorNote: $('tutor-note'),
|
|
268
|
+
tutorHint: $('tutor-hint'),
|
|
269
|
+
summaryBlock: $('summary-block'),
|
|
270
|
+
objectivesBlock: $('objectives-block'),
|
|
271
|
+
annBlock: $('ann-block'),
|
|
272
|
+
exerciseBlock: $('exercise-block'),
|
|
273
|
+
hintBlock: $('hint-block'),
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const lang = String((LESSON && LESSON.lang) || 'python').toLowerCase();
|
|
277
|
+
|
|
278
|
+
const KEYWORDS = {
|
|
279
|
+
python: new Set(['False','None','True','and','as','assert','async','await','break','class','continue','def','del','elif','else','except','finally','for','from','global','if','import','in','is','lambda','nonlocal','not','or','pass','raise','return','try','while','with','yield']),
|
|
280
|
+
rust: new Set(['as','async','await','break','const','continue','crate','dyn','else','enum','extern','false','fn','for','if','impl','in','let','loop','match','mod','move','mut','pub','ref','return','self','Self','static','struct','super','trait','true','type','unsafe','use','where','while']),
|
|
281
|
+
go: new Set(['break','case','chan','const','continue','default','defer','else','fallthrough','for','func','go','goto','if','import','interface','map','package','range','return','select','struct','switch','type','var']),
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const BUILTINS = {
|
|
285
|
+
python: new Set(['print','len','range','str','int','float','list','dict','set','tuple','open','type','isinstance','enumerate','zip','map','filter','sum','min','max','abs','sorted','reversed','any','all']),
|
|
286
|
+
rust: new Set(['println','print','format','vec','String','Vec','Option','Result','Some','None','Ok','Err','Box','Rc','Arc']),
|
|
287
|
+
go: new Set(['fmt','make','len','cap','append','copy','delete','panic','recover','new','close','true','false','nil','string','int','int64','float64','bool','error','byte','rune']),
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
function defaultFilename(l) {
|
|
291
|
+
if (l === 'rust') return 'main.rs';
|
|
292
|
+
if (l === 'go') return 'main.go';
|
|
293
|
+
return 'main.py';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function primarySnippet() {
|
|
297
|
+
const snippets = Array.isArray(LESSON.snippets) ? LESSON.snippets.filter(Boolean) : [];
|
|
298
|
+
const runId = LESSON.run && LESSON.run.snippet_id;
|
|
299
|
+
const same = snippets.filter((s) => String(s.lang || lang).toLowerCase() === lang);
|
|
300
|
+
if (runId) {
|
|
301
|
+
const hit = same.find((s) => s.id === runId) || snippets.find((s) => s.id === runId);
|
|
302
|
+
if (hit) return hit;
|
|
303
|
+
}
|
|
304
|
+
return same.find((s) => (s.role || 'primary') === 'primary')
|
|
305
|
+
|| same[0]
|
|
306
|
+
|| {
|
|
307
|
+
id: 'main',
|
|
308
|
+
lang,
|
|
309
|
+
filename: (LESSON.starter && LESSON.starter.filename) || defaultFilename(lang),
|
|
310
|
+
code: (LESSON.starter && LESSON.starter.code) || '',
|
|
311
|
+
annotations: [],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const snippet = primarySnippet();
|
|
316
|
+
const displayCode = String(snippet.code || (LESSON.starter && LESSON.starter.code) || '');
|
|
317
|
+
|
|
318
|
+
let sendBusy = false;
|
|
319
|
+
|
|
320
|
+
function esc(v) {
|
|
321
|
+
return String(v ?? '').replace(/[&<>"']/g, (ch) => ({
|
|
322
|
+
'&':'&','<':'<','>':'>','"':'"',"'":'''
|
|
323
|
+
}[ch]));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function setPipe(msg) { els.pipe.textContent = msg; }
|
|
327
|
+
|
|
328
|
+
function tokenize(code, language) {
|
|
329
|
+
const kw = KEYWORDS[language] || KEYWORDS.python;
|
|
330
|
+
const builtins = BUILTINS[language] || new Set();
|
|
331
|
+
const lineComment = language === 'python' ? '#' : '//';
|
|
332
|
+
let i = 0;
|
|
333
|
+
let out = '';
|
|
334
|
+
const n = code.length;
|
|
335
|
+
|
|
336
|
+
function takeWhile(pred) {
|
|
337
|
+
const start = i;
|
|
338
|
+
while (i < n && pred(code[i])) i += 1;
|
|
339
|
+
return code.slice(start, i);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
while (i < n) {
|
|
343
|
+
const ch = code[i];
|
|
344
|
+
const next = code[i + 1] || '';
|
|
345
|
+
|
|
346
|
+
if (language === 'python' && ch === '#') {
|
|
347
|
+
const text = takeWhile((c) => c !== '\n');
|
|
348
|
+
out += `<span class="tok-com">${esc(text)}</span>`;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if ((language === 'rust' || language === 'go') && ch === '/' && next === '/') {
|
|
352
|
+
const text = takeWhile((c) => c !== '\n');
|
|
353
|
+
out += `<span class="tok-com">${esc(text)}</span>`;
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if ((language === 'rust' || language === 'go') && ch === '/' && next === '*') {
|
|
357
|
+
let text = '/*';
|
|
358
|
+
i += 2;
|
|
359
|
+
while (i < n && !(code[i] === '*' && code[i + 1] === '/')) {
|
|
360
|
+
text += code[i];
|
|
361
|
+
i += 1;
|
|
362
|
+
}
|
|
363
|
+
if (i < n) { text += '*/'; i += 2; }
|
|
364
|
+
out += `<span class="tok-com">${esc(text)}</span>`;
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (ch === '"' || ch === "'" || (language === 'rust' && ch === 'r' && (next === '"' || next === '#'))) {
|
|
369
|
+
if (language === 'rust' && ch === 'r') {
|
|
370
|
+
let j = i + 1;
|
|
371
|
+
let hashes = '';
|
|
372
|
+
while (code[j] === '#') { hashes += '#'; j += 1; }
|
|
373
|
+
if (code[j] === '"') {
|
|
374
|
+
let text = code.slice(i, j + 1);
|
|
375
|
+
i = j + 1;
|
|
376
|
+
const end = '"' + hashes;
|
|
377
|
+
while (i < n && code.slice(i, i + end.length) !== end) {
|
|
378
|
+
text += code[i];
|
|
379
|
+
i += 1;
|
|
380
|
+
}
|
|
381
|
+
text += code.slice(i, i + end.length);
|
|
382
|
+
i += end.length;
|
|
383
|
+
out += `<span class="tok-str">${esc(text)}</span>`;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const quote = ch;
|
|
388
|
+
let text = quote;
|
|
389
|
+
i += 1;
|
|
390
|
+
while (i < n) {
|
|
391
|
+
const c = code[i];
|
|
392
|
+
text += c;
|
|
393
|
+
i += 1;
|
|
394
|
+
if (c === '\\' && i < n) { text += code[i]; i += 1; continue; }
|
|
395
|
+
if (c === quote) break;
|
|
396
|
+
if (c === '\n' && language === 'python' && quote !== "'''" ) break;
|
|
397
|
+
}
|
|
398
|
+
out += `<span class="tok-str">${esc(text)}</span>`;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (language === 'go' && ch === '`') {
|
|
403
|
+
let text = '`';
|
|
404
|
+
i += 1;
|
|
405
|
+
while (i < n && code[i] !== '`') { text += code[i]; i += 1; }
|
|
406
|
+
if (i < n) { text += '`'; i += 1; }
|
|
407
|
+
out += `<span class="tok-str">${esc(text)}</span>`;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (/[0-9]/.test(ch) || (ch === '.' && /[0-9]/.test(next))) {
|
|
412
|
+
const text = takeWhile((c) => /[0-9_.xXoObBa-fA-F]/.test(c));
|
|
413
|
+
out += `<span class="tok-num">${esc(text)}</span>`;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (/[A-Za-z_]/.test(ch)) {
|
|
418
|
+
const text = takeWhile((c) => /[A-Za-z0-9_]/.test(c));
|
|
419
|
+
let k = i;
|
|
420
|
+
while (k < n && /\s/.test(code[k])) k += 1;
|
|
421
|
+
const isCall = code[k] === '(';
|
|
422
|
+
if (kw.has(text)) out += `<span class="tok-kw">${esc(text)}</span>`;
|
|
423
|
+
else if (builtins.has(text)) out += `<span class="tok-fn">${esc(text)}</span>`;
|
|
424
|
+
else if (/^[A-Z]/.test(text) && language !== 'python') out += `<span class="tok-type">${esc(text)}</span>`;
|
|
425
|
+
else if (isCall) out += `<span class="tok-fn">${esc(text)}</span>`;
|
|
426
|
+
else out += esc(text);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if ('=+-*/%<>!&|^~?:.'.includes(ch)) {
|
|
431
|
+
const text = takeWhile((c) => '=+-*/%<>!&|^~?:.'.includes(c));
|
|
432
|
+
out += `<span class="tok-op">${esc(text)}</span>`;
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
out += esc(ch);
|
|
437
|
+
i += 1;
|
|
438
|
+
}
|
|
439
|
+
return out || ' ';
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function renderMeta() {
|
|
443
|
+
els.lang.textContent = lang;
|
|
444
|
+
els.title.textContent = (LESSON && LESSON.title) || 'Code 展示';
|
|
445
|
+
els.subtitle.textContent = (LESSON && (LESSON.subtitle || LESSON.stage)) || '';
|
|
446
|
+
const summary = (LESSON && LESSON.summary) || '';
|
|
447
|
+
els.summary.textContent = summary;
|
|
448
|
+
els.summaryBlock.classList.toggle('hidden', !summary);
|
|
449
|
+
|
|
450
|
+
const objectives = Array.isArray(LESSON.objectives) ? LESSON.objectives : [];
|
|
451
|
+
if (objectives.length) {
|
|
452
|
+
els.objectives.innerHTML = objectives.map((o) => `<li>${esc(o)}</li>`).join('');
|
|
453
|
+
} else {
|
|
454
|
+
els.objectivesBlock.classList.add('hidden');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const anns = Array.isArray(snippet.annotations) ? snippet.annotations : [];
|
|
458
|
+
if (anns.length) {
|
|
459
|
+
els.annotations.innerHTML = anns.map((a) =>
|
|
460
|
+
`<div class="${esc(a.kind || 'info')}"><strong>L${esc(a.line)}</strong> ${esc(a.text || '')}</div>`
|
|
461
|
+
).join('');
|
|
462
|
+
} else {
|
|
463
|
+
els.annBlock.classList.add('hidden');
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const exercises = Array.isArray(LESSON.exercises) ? LESSON.exercises : [];
|
|
467
|
+
if (exercises.length) {
|
|
468
|
+
els.exercises.innerHTML = exercises.map((ex) =>
|
|
469
|
+
`<li><strong>${esc(ex.type || 'task')}</strong> — ${esc(ex.prompt || '')}</li>`
|
|
470
|
+
).join('');
|
|
471
|
+
} else {
|
|
472
|
+
els.exerciseBlock.classList.add('hidden');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const hints = Array.isArray(LESSON.hints) ? LESSON.hints : [];
|
|
476
|
+
if (hints.length) {
|
|
477
|
+
els.hints.innerHTML = hints.map((h) => `<li>${esc(h)}</li>`).join('');
|
|
478
|
+
} else {
|
|
479
|
+
els.hintBlock.classList.add('hidden');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
els.filename.textContent = snippet.filename || defaultFilename(lang);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function buildTutorPackage(note) {
|
|
486
|
+
const lines = [
|
|
487
|
+
'## Code 展示 · 发给导师',
|
|
488
|
+
'',
|
|
489
|
+
`- lesson: ${(LESSON && (LESSON.id || LESSON.title)) || ''}`,
|
|
490
|
+
`- lang: ${lang}`,
|
|
491
|
+
`- file: ${els.filename.textContent || defaultFilename(lang)}`,
|
|
492
|
+
`- action: ask_tutor`,
|
|
493
|
+
];
|
|
494
|
+
if (note) {
|
|
495
|
+
lines.push('', '### 用户说明', '', note);
|
|
496
|
+
}
|
|
497
|
+
lines.push('', '### 展示代码', '', '```' + lang, displayCode.replace(/\n$/, ''), '```');
|
|
498
|
+
lines.push('', '请结合代码讲解:思路、关键点、可能的问题或下一步。');
|
|
499
|
+
return lines.join('\n');
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function sendToTutor() {
|
|
503
|
+
if (sendBusy) return;
|
|
504
|
+
if (!MENTOR || !INPUT_ID) {
|
|
505
|
+
setPipe('缺少导师通道:请用 code 命令(默认 wait)重新打开本课');
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const note = String((els.tutorNote && els.tutorNote.value) || '').trim();
|
|
509
|
+
const text = buildTutorPackage(note);
|
|
510
|
+
sendBusy = true;
|
|
511
|
+
els.btnSend.disabled = true;
|
|
512
|
+
els.btnSend.textContent = '发送中…';
|
|
513
|
+
setPipe('正在发给导师…');
|
|
514
|
+
window.parent.postMessage({
|
|
515
|
+
localweb: true,
|
|
516
|
+
type: 'panel_input',
|
|
517
|
+
input_id: INPUT_ID,
|
|
518
|
+
panel_id: PANEL_ID,
|
|
519
|
+
label: 'ask_tutor',
|
|
520
|
+
text,
|
|
521
|
+
meta: {
|
|
522
|
+
localweb_code: true,
|
|
523
|
+
action: 'ask_tutor',
|
|
524
|
+
lang,
|
|
525
|
+
lesson_id: (LESSON && LESSON.id) || '',
|
|
526
|
+
source: displayCode,
|
|
527
|
+
note,
|
|
528
|
+
},
|
|
529
|
+
}, '*');
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function onTutorSendResult(ok, message) {
|
|
533
|
+
sendBusy = false;
|
|
534
|
+
els.btnSend.disabled = false;
|
|
535
|
+
els.btnSend.textContent = '发给导师讲解';
|
|
536
|
+
if (ok) {
|
|
537
|
+
setPipe(message || '已发送到导师通道');
|
|
538
|
+
if (els.tutorHint) els.tutorHint.textContent = '已发送 · 等待导师读取';
|
|
539
|
+
} else {
|
|
540
|
+
setPipe(message || '发送失败');
|
|
541
|
+
if (els.tutorHint) els.tutorHint.textContent = '发送失败,可再点一次';
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
window.addEventListener('message', (event) => {
|
|
546
|
+
const data = event.data;
|
|
547
|
+
if (!data || typeof data !== 'object' || data.localweb !== true) return;
|
|
548
|
+
if (data.type === 'panel_input_result') {
|
|
549
|
+
onTutorSendResult(Boolean(data.ok), data.message || (data.ok ? '已发送' : '发送失败'));
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Init: render code + meta. Pure display; no edits, no host run.
|
|
554
|
+
els.code.innerHTML = tokenize(displayCode.endsWith('\n') ? displayCode : displayCode + '\n', lang);
|
|
555
|
+
try { renderMeta(); } catch (err) { console.error(err); setPipe('meta render error, code still shown'); }
|
|
556
|
+
setPipe(MENTOR ? '展示 · 可发给导师讲解' : '展示模式');
|
|
557
|
+
|
|
558
|
+
els.btnSend.addEventListener('click', sendToTutor);
|
|
559
|
+
</script>
|
|
560
|
+
</body>
|
|
561
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# game-study —— 自适应学习(面板交互模式·DSH 版)
|
|
2
|
+
|
|
3
|
+
题目结构遵循 `schemas/quest.schema.json`(逐题主动回忆:题干/选项/答案/知识点/错因);
|
|
4
|
+
题库示例:`examples/hft-microstructure.json`、`examples/securities-law.json`;
|
|
5
|
+
答题模板 `templates/quiz.html`。
|
|
6
|
+
|
|
7
|
+
## 投递(DSH 原生)
|
|
8
|
+
|
|
9
|
+
1. 逐题渲染 → `stage_panel`;答题选项用 `stage_choice`(回答 = 用户消息,无 wait);
|
|
10
|
+
2. 一轮完成后用面板给掌握度小结,并 `stage_choice` 出「下一轮 / 复习错题 / 结束」。
|
|
11
|
+
|
|
12
|
+
## 说明(诚实标注)
|
|
13
|
+
|
|
14
|
+
原实现的**跨会话掌握度 / 错题本 / FSRS 调度持久化**属于独立服务层(`.localweb/study/`),
|
|
15
|
+
本模式暂不引入私有持久化——会话本身是真相。跨会话进度是后续可议的 feature(会话工具输出累积或独立存储服务),
|
|
16
|
+
本轮先承接题目内容、答题模板与出题规范。
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 2,
|
|
3
|
+
"id": "hft-microstructure",
|
|
4
|
+
"title": "高频交易微观结构",
|
|
5
|
+
"subtitle": "以限价订单簿为中心,漫步订单流/延迟/做市/风险四个展开面",
|
|
6
|
+
"domain": "HFT 微观结构",
|
|
7
|
+
"summary": "自适应学习:以限价订单簿为中心,逐题主动回忆。答错进错题本、掌握度累积、费曼题回传 CLI 判定。HP 不再是娱乐涂层,掌握度才是真正的训练预算。",
|
|
8
|
+
"cells": [
|
|
9
|
+
{"id":"lob","label":"限价订单簿","brief":"实时记录所有未成交限价单的数据结构——每个价位的买卖挂单量。微观结构的底座。","example":"Level-2:买一 100.00 挂 3000 股,卖一 100.02 挂 1500 股——簿的不对称就是信号。","difficulty":2,"tags":["core"]},
|
|
10
|
+
{"id":"orderflow","label":"订单流","brief":"按时间序贯到达的订单事件(新增/撤单/成交)序列。把市场看作由订单流驱动的时间动态系统。","example":"一笔 5000 股市价买单吃穿簿三层卖单才成交完——一次订单流事件对簿的冲击。","difficulty":3,"tags":["flow"],"prereq":["lob"]},
|
|
11
|
+
{"id":"latency","label":"延迟","brief":"从行情到达、到决策、到下单的端到端时间。微秒级 HFT 里,延迟直接等于机会。","example":"新行情后你下单差 50 微秒——这 50μs 决定你能否吃到那档价差。","difficulty":3,"tags":["latency"],"prereq":["lob"]},
|
|
12
|
+
{"id":"mm","label":"做市","brief":"同时在买价卖价双边挂限价单赚 bid-ask 价差,撮合时主动管理库存。HFT 占比最大的策略。","example":"bid=99.99 挂买、ask=100.01 挂卖各 100 股——两边成交赚 2 分×100=2 美元,不赌方向。","difficulty":3,"tags":["models"],"prereq":["lob"]},
|
|
13
|
+
{"id":"spread","label":"买卖价差","brief":"最优卖价(ask)与最优买价(bid)之差,即时流动性的价格。越窄越流动。","example":"bid=100.00、ask=100.02,价差 0.02——市价单一买一卖付 2 分钱流动性税。","difficulty":2,"tags":["book"],"prereq":["lob"]},
|
|
14
|
+
{"id":"depth","label":"订单簿深度","brief":"不同价位累积挂单量,衡量簿能吸收多大冲击而不剧烈移价。流动性的纵深。","example":"买盘 100.00 挂 3000 股、99.99 挂 8000 股——深度告诉你 5000 股市价单会把价砸到哪。","difficulty":3,"tags":["book"],"prereq":["lob"]},
|
|
15
|
+
{"id":"midprice","label":"中间价","brief":"(bid+ask)/2,订单簿瞬时公允估值。无交易成本的价格参考,多数策略的计价基准。","example":"bid=100.00、ask=100.02 → mid=100.01——做市盈亏常以 mid 为公允计价。","difficulty":2,"tags":["book"],"prereq":["spread"]},
|
|
16
|
+
{"id":"imbalance","label":"买卖单失衡","brief":"簿上买盘量与卖盘量的不对称。短期价格漂移最强预测因子之一:买压更强则中间价倾向上移。","example":"买盘 10000 股 vs 卖盘 4000 股,失衡比 +0.43——历史上正失衡后 1 秒收益偏正。","difficulty":4,"tags":["flow"],"prereq":["orderflow","depth"]},
|
|
17
|
+
{"id":"passive","label":"被动单与主动单","brief":"被动限价单挂簿等待成交(提供流动性),主动市价单吃簿立即成交(消费流动性)。谁被动谁主动决定谁赚价差谁付价差。","example":"你挂 100.00 买单等成交是被动单;对手市价卖单砸进来吃掉你的挂单是主动单——你赚了价差。","difficulty":3,"tags":["flow"],"prereq":["orderflow"]},
|
|
18
|
+
{"id":"colo","label":"同地部署","brief":"把交易服务器物理放进交易所机房,把网络往返延迟压到最低。HFT 降延迟最直接的硬件手段。","example":"从芝加哥搬到纳斯达克 NJ 机房,往返 15ms→50μs,提速约 300 倍。","difficulty":3,"tags":["latency"],"prereq":["latency"]},
|
|
19
|
+
{"id":"latarb","label":"延迟套利","brief":"利用速度优势,在慢市场未更新报价前于快市场已更新价差中撮合。常被指为抢跑。","example":"ES 期货在 CME 已出新价,ETF 还挂旧价——抢在 ETF 更新前买入,几乎无风险赚价差。","difficulty":4,"tags":["latency"],"prereq":["colo"]},
|
|
20
|
+
{"id":"inventory","label":"库存风险","brief":"净头寸的方向性敞口。做市商一边成交多一边没成交就积累库存,被价格反向变动打亏。","example":"买单成交卖单没成交,净持多 100 股——价格跌 1 元库存亏 100 元,吃掉全天价差收入。","difficulty":3,"tags":["models"],"prereq":["mm"]},
|
|
21
|
+
{"id":"adverse","label":"逆相选择","brief":"做市商致命风险:挂单被知情交易者(有信息优势方)吃掉——成交在不利价格,跟着对手方向亏。","example":"知情者知道要发利好,猛砸市价买单吃光你的卖单——你卖在低价,眼看价格飞走。","difficulty":4,"tags":["models"],"prereq":["mm"]}
|
|
22
|
+
],
|
|
23
|
+
"relations": [
|
|
24
|
+
{"from":"orderflow","to":"lob","label":"更新","kind":"implements"},
|
|
25
|
+
{"from":"lob","to":"spread","label":"涌现为","kind":"emerges"},
|
|
26
|
+
{"from":"lob","to":"depth","label":"具现","kind":"implements"},
|
|
27
|
+
{"from":"spread","to":"midprice","label":"计算","kind":"depends"},
|
|
28
|
+
{"from":"orderflow","to":"imbalance","label":"涌现为","kind":"emerges"},
|
|
29
|
+
{"from":"orderflow","to":"passive","label":"分类","kind":"unfolds"},
|
|
30
|
+
{"from":"passive","to":"imbalance","label":"累积","kind":"depends"},
|
|
31
|
+
{"from":"mm","to":"lob","label":"挂单于","kind":"implements"},
|
|
32
|
+
{"from":"mm","to":"spread","label":"赚取","kind":"implements"},
|
|
33
|
+
{"from":"mm","to":"inventory","label":"承担","kind":"constrains"},
|
|
34
|
+
{"from":"mm","to":"adverse","label":"面临","kind":"constrains"},
|
|
35
|
+
{"from":"latency","to":"colo","label":"通过","kind":"implements"},
|
|
36
|
+
{"from":"colo","to":"latarb","label":"武器化","kind":"implements"},
|
|
37
|
+
{"from":"latarb","to":"adverse","label":"加剧","kind":"emerges"},
|
|
38
|
+
{"from":"imbalance","to":"midprice","label":"推动","kind":"emerges"},
|
|
39
|
+
{"from":"inventory","to":"spread","label":"加宽","kind":"constrains"}
|
|
40
|
+
],
|
|
41
|
+
"questions": [
|
|
42
|
+
{"id":"q1","cell":"lob","type":"choice","prompt":"限价订单簿(LOB)记录的是什么?","options":[{"label":"所有已成交的历史交易","correct":false},{"label":"所有未成交的限价挂单(按价位排列的买卖量)","correct":true},{"label":"交易所收取的手续费表","correct":false}],"answer":"所有未成交的限价挂单(按价位排列的买卖量)","explain":"LOB 是实时挂单簿,记录的是待成交的供需,不是历史成交。它是微观结构的底座。","difficulty":1},
|
|
43
|
+
{"id":"q2","cell":"spread","type":"choice","prompt":"买卖价差(bid-ask spread)衡量什么?","options":[{"label":"即时流动性的价格,越窄越流动","correct":true},{"label":"一天内最大涨跌幅","correct":false},{"label":"交易所的成交量","correct":false}],"answer":"即时流动性的价格,越窄越流动","explain":"价差=最优卖价-最优买价,是立即买入再卖出的流动性成本。窄价差=高流动性。","difficulty":1},
|
|
44
|
+
{"id":"q3","cell":"mm","type":"choice","prompt":"做市商的主要盈利来源是什么?","options":[{"label":"准确预测涨跌方向做单边交易","correct":false},{"label":"双边挂单赚 bid-ask 价差,不赌方向","correct":true},{"label":"收取其他交易者的佣金","correct":false}],"answer":"双边挂单赚 bid-ask 价差,不赌方向","explain":"做市靠在买卖双边挂限价单,撮合成交赚价差,靠成交量而非方向盈利。","difficulty":2},
|
|
45
|
+
{"id":"q4","cell":"adverse","type":"choice","prompt":"做市商最致命的风险「逆相选择」指的是?","options":[{"label":"交易所系统故障导致撤单","correct":false},{"label":"挂单被知情交易者吃掉,成交在不利价格","correct":true},{"label":"自己的网络延迟过高","correct":false}],"answer":"挂单被知情交易者吃掉,成交在不利价格","explain":"知情者有信息优势,专挑做市商挂单吃,做市商被动成交在错误方向,是结构性亏钱。","difficulty":3},
|
|
46
|
+
{"id":"q5","cell":["lob","spread"],"type":"connect","prompt":"「限价订单簿」与「买卖价差」是什么关系?","options":[{"label":"展开为","is":"unfolds","correct":false},{"label":"涌现为(簿的状态涌现出价差)","is":"emerges","correct":true},{"label":"互不相关","is":"","correct":false}],"answer":"emerges","explain":"价差是订单簿最优买卖档直接涌现的结果,簿的不对称就是价差信号。","difficulty":2},
|
|
47
|
+
{"id":"q6","cell":["mm","adverse"],"type":"connect","prompt":"「做市」与「逆相选择」是什么关系?","options":[{"label":"约束/面临(做市天然面临逆相风险)","is":"constrains","correct":true},{"label":"实现","is":"implements","correct":false},{"label":"同型","is":"mirrors","correct":false}],"answer":"constrains","explain":"做市必须在市场挂单等待,这天然暴露给知情者——逆相选择是做市的结构性约束。","difficulty":3},
|
|
48
|
+
{"id":"q7","cell":["latency","colo"],"type":"connect","prompt":"「延迟」与「同地部署」是什么关系?","options":[{"label":"实现(同地部署是实现低延迟的手段)","is":"implements","correct":true},{"label":"约束","is":"constrains","correct":false},{"label":"互不相关","is":"","correct":false}],"answer":"implements","explain":"同地部署把服务器搬进交易所机房,是实现端到端低延迟最直接的硬件手段。","difficulty":2},
|
|
49
|
+
{"id":"q8","cell":"orderflow","type":"multi","prompt":"订单流(order flow)包含哪些事件?(多选,全对才得分)","options":[{"label":"新增限价单","correct":true},{"label":"撤单","correct":true},{"label":"成交","correct":true},{"label":"收盘结算价","correct":false}],"answer":"新增限价单 / 撤单 / 成交","explain":"订单流是逐笔到达的订单事件序列:新挂、撤单、成交。结算价不属于逐笔订单事件。","difficulty":2},
|
|
50
|
+
{"id":"q9","cell":"imbalance","type":"choice","prompt":"买卖单失衡(imbalance)对短期中间价的预测信号是?","options":[{"label":"失衡越大,短期中间价越可能反向漂移","correct":false},{"label":"买压强于卖压,中间价倾向上移","correct":true},{"label":"失衡与价格无关","correct":false}],"answer":"买压强于卖压,中间价倾向上移","explain":"失衡是微观结构最强的短期方向因子:买盘堆积=买压,推动中间价上移。","difficulty":4},
|
|
51
|
+
{"id":"q10","cell":["lob","spread","midprice"],"type":"feynman","prompt":"用自己的话解释:限价订单簿是如何同时「产生」买卖价差和中间价的?它们之间是什么关系?","answer":"LOB 最优买价 bid 与最优卖价 ask 直接构成价差(ask-bid);中间价=(bid+ask)/2 是无成本公允估值。价差是流动性成本,中间价是估值锚。","feynman_points":["bid 和 ask 都来自簿的最优档","价差=ask-bid(流动性成本)","中间价=(bid+ask)/2(公允估值)","价差越窄流动性越高,中间价越可信"],"explain":"考查对簿-价差-中间价三位一体的结构理解。","difficulty":4}
|
|
52
|
+
],
|
|
53
|
+
"session": {"length": 8, "shuffle": true, "review_mode": false},
|
|
54
|
+
"next": [
|
|
55
|
+
{"id":"orderflow-deep","label":"深入订单流预测","description":"失衡、撤单流、微观价格漂移的动量信号。"},
|
|
56
|
+
{"id":"mm-deep","label":"深入做市机制","description":"库存动态管理、价差随风险调宽、与知情者博弈。"},
|
|
57
|
+
{"id":"latency-deep","label":"深入延迟竞赛","description":"FPGA、内核旁路、同地部署与延迟套利边界。"},
|
|
58
|
+
{"id":"book-deep","label":"深入订单簿动力学","description":"簿重建、隐藏冰山订单、不对称性作为信号。"}
|
|
59
|
+
]
|
|
60
|
+
}
|