dsh-hyperliquid 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/tools.js CHANGED
@@ -6,7 +6,11 @@
6
6
  * @module lib/tools.js
7
7
  */
8
8
 
9
- import { computeIndicators } from './indicators.js'
9
+ import { closedCandles, computeIndicators } from './indicators.js'
10
+ import { MA_KINDS, MA_SOURCES, maLabel, maLatest, normalizeMaLine, normalizeMaLines } from './moving-average.js'
11
+ import { DRAWING_KINDS } from './chart-drawings.js'
12
+
13
+ const DRAWING_NAMES = { level: '水平线', hray: '水平射线', vline: '垂直线', text: '文本', trend: '趋势线', ray: '射线', extended: '延长线', channel: '平行通道', fib: '斐波那契回撤', rect: '矩形', measure: '价格区间', long: '多头头寸', short: '空头头寸', fibext: '斐波那契扩展', fibchannel: '斐波那契通道', fibtime: '斐波那契时间周期', gannfan: '江恩扇形', pitchfork: '安德鲁叉子线', regression: '回归趋势', arrow: '箭头', callout: '标注气泡', circle: '圆形', triangle: '三角形', brush: '画笔', crossline: '十字线', pricelabel: '价格标签', infoline: '信息线', trendangle: '趋势角度', pricerange: '价格区间', daterange: '日期区间', elliott: '艾略特推动浪', elliottabc: '艾略特调整浪', xabcd: 'XABCD 谐波', abcd: 'ABCD 形态', headshoulders: '头肩形态', gannbox: '江恩箱', gannsquare: '江恩方格', schiff: 'Schiff 叉子线', modschiff: '改良 Schiff 叉子线', icon: '图标' }
10
14
  import { canonicalMarketCoin } from './market-symbol.js'
11
15
 
12
16
  /** Resolve the calling session id from a tool run context. */
@@ -23,7 +27,19 @@ function fmt(n, d = 2) {
23
27
 
24
28
  function shortSide(isBuy) { return isBuy ? 'BUY/LONG' : 'SELL/SHORT' }
25
29
 
30
+ /**
31
+ * DSH requires tool output to be lossless JSON: no undefined fields, no NaN / Infinity.
32
+ * Every hyper_* result is normalized here so a stray field can never fail a tool call.
33
+ */
34
+ export function losslessJson(value) {
35
+ return JSON.parse(JSON.stringify(value, (_key, item) => typeof item === 'number' && !Number.isFinite(item) ? null : item))
36
+ }
37
+
26
38
  export function buildToolDefinitions(remote) {
39
+ return buildTools(remote).map((tool) => ({ ...tool, async execute(...args) { return losslessJson(await tool.execute(...args)) } }))
40
+ }
41
+
42
+ function buildTools(remote) {
27
43
  return [
28
44
  {
29
45
  name: 'hyper_context',
@@ -106,6 +122,7 @@ export function buildToolDefinitions(remote) {
106
122
  type: 'object',
107
123
  properties: {
108
124
  action: { type: 'string', enum: ['overview', 'catalog', 'book', 'candles', 'indicators', 'trades'], description: '行情查询类型' },
125
+ ma: { type: 'array', description: 'action=indicators 可选:额外计算的均线,最多 8 条;省略时返回当前会话图表上已叠加的均线', items: { type: 'object', properties: { kind: { type: 'string', enum: MA_KINDS }, period: { type: 'number' }, source: { type: 'string', enum: MA_SOURCES }, smoothing: { type: 'object', properties: { kind: { type: 'string', enum: MA_KINDS }, period: { type: 'number' } } } }, required: ['kind', 'period'] } },
109
126
  coin: { type: 'string', description: '官方合约名,以 action=catalog 返回的 name 为准,如 BTC 或 xyz:XYZ100;overview 时可省略' },
110
127
  kind: { type: 'string', enum: ['perp', 'hip3', 'spot', 'outcome'], description: 'catalog 市场类型过滤' },
111
128
  category: { type: 'string', description: 'catalog 官方分类过滤,如 crypto、stocks、commodities、indices、fx、rates、preipo;tradfi 聚合这些传统金融标签' },
@@ -118,7 +135,7 @@ export function buildToolDefinitions(remote) {
118
135
  },
119
136
  output: { schema: { type: 'object', additionalProperties: true }, render: (_a, v) => [{ type: 'text', text: v.text }] },
120
137
  timeoutMs: 15_000,
121
- async execute(args) {
138
+ async execute(args, exec) {
122
139
  const action = args?.action ?? 'overview'
123
140
  if (action === 'overview') {
124
141
  const { universe } = await remote.markets()
@@ -174,9 +191,14 @@ export function buildToolDefinitions(remote) {
174
191
  `RSI14=${fmt(indicators.rsi14, 1)} MACD=${fmt(indicators.macd.line, 4)} signal=${fmt(indicators.macd.signal, 4)} hist=${fmt(indicators.macd.histogram, 4)}`,
175
192
  `布林带下/中/上=${fmt(indicators.bollinger20.lower)}/${fmt(indicators.bollinger20.middle)}/${fmt(indicators.bollinger20.upper)} ATR14=${fmt(indicators.atr14)} 量比20=${fmt(indicators.volumeRatio20, 2)}`,
176
193
  `邻近支撑=${fmt(indicators.support.price)}(${new Date(indicators.support.time).toISOString()}) 阻力=${fmt(indicators.resistance.price)}(${new Date(indicators.resistance.time).toISOString()})`,
177
- '指标是历史 K 线的派生值;需要结合最新盘口、账户风险和数据时效验证,不能单独当作下单信号。',
178
- ].join('\n')
179
- return { text, indicators, interval, coin }
194
+ ]
195
+ let session = null
196
+ try { session = sessionIdOf(exec) } catch { /* not a trading session */ }
197
+ const requested = Array.isArray(args?.ma) ? normalizeMaLines(args.ma) : (session && remote.store.getSession?.(session) ? remote.store.getSessionWorkspace(session).maLines ?? [] : [])
198
+ const ma = requested.length ? maLatest(closedCandles(candles, { interval }).rows, requested) : []
199
+ if (ma.length) text.push('均线(最新已收盘):' + ma.map((row) => `${row.label}=${row.value == null ? '数据不足' : fmt(row.value)}${row.line.smoothing ? `→平滑${fmt(row.smoothed)}` : ''}`).join(' '))
200
+ text.push('指标是历史 K 线的派生值;需要结合最新盘口、账户风险和数据时效验证,不能单独当作下单信号。')
201
+ return { text: text.join('\n'), indicators, ma, interval, coin }
180
202
  }
181
203
  if (action === 'trades') {
182
204
  const snap = await remote.snapshot({ coin })
@@ -188,13 +210,26 @@ export function buildToolDefinitions(remote) {
188
210
  },
189
211
  {
190
212
  name: 'hyper_chart',
191
- description: '在当前 DSH 交易会话的行情图上画或管理供人查看的水平支撑/阻力线与趋势线。先用 hyper_market candles/indicators 验证价格和时间;标注仅用于解释,不是自动订单或交易信号。',
213
+ description: '在当前 DSH 交易会话的行情图上画或管理供人查看的水平支撑/阻力线、趋势线与均线。kind=ma 在图上叠加一条按 K 线实时计算的均线:maType 支持 sma/ema/smma(平滑均线,即 RMA/Wilder)/wma,period 1-500,source 默认 close,可选 smoothingType+smoothingPeriod 再对均线做一次平滑(同 TradingView 均线指标的 Smoothing);均线按会话保存,随周期切换重算,最多 8 条,add 会返回最新已收盘值。画线工具:level 水平线(price)、trend 趋势线(fromTime/fromPrice/toTime/toPrice),其余用 points=[{time,price}]:hray 水平射线/vline 垂直线 1 点,ray 射线/extended 延长线/fib 斐波那契回撤(第 1 点=1、第 2 点=0)/rect 矩形/measure 价格区间 2 点,channel 平行通道 3 点(第 3 点定通道宽度),text 文本 1 点(label 为文本,最多 200 字),long/short 多空头寸 3 点 [入场, 止损, 止盈](第 2 点时间为右边界);fibext 斐波那契扩展 3 点(A→B 波段,自 C 投射)、fibchannel 斐波那契通道 3 点、fibtime 斐波那契时间周期 2 点、gannfan 江恩扇形 2 点(第 2 点定 1×1)、pitchfork 安德鲁叉子线 3 点、regression 回归趋势 2 点(时间区间)、arrow 箭头 2 点、callout 标注气泡 2 点(锚点+文本框位置,label 为文本)、circle 圆形 2 点(圆心+边缘)、triangle 三角形 3 点、brush 画笔 2-500 点;crossline 十字线、pricelabel 价格标签 1 点;infoline 信息线、trendangle 趋势角度、pricerange 价格区间、daterange 日期区间 2 点;elliott 推动浪 6 点(0-5)、elliottabc 调整浪 4 点(0-A-B-C)、xabcd 谐波 5 点、abcd 4 点、headshoulders 头肩 7 点(左底-左肩-颈1-头-颈2-右肩-右底)、gannbox/gannsquare 2 点对角、schiff/modschiff 叉子线 3 点、icon 图标 1 点(label 为 flag/star/up/down/warning/check/cross/fire/target/bell,或贴图库 emoji:📈/🚀/🐂/🐻/⚠️ 等)。update 按 id 修改 points/price/label 或样式 color/lineWidth(1-4)/lineStyle(solid/dashed/dotted)/locked。画线按会话和标的保存,在该标的所有周期显示。先用 hyper_market candles/indicators 验证价格和时间;标注仅用于解释,不是自动订单或交易信号。',
192
214
  parameters: {
193
215
  type: 'object', properties: {
194
- action: { type: 'string', enum: ['add', 'list', 'remove'] },
216
+ action: { type: 'string', enum: ['add', 'list', 'update', 'remove'] },
195
217
  coin: { type: 'string', description: '默认会话绑定币对' },
196
218
  interval: { type: 'string', description: '默认当前交易台周期' },
197
- kind: { type: 'string', enum: ['level', 'trend'], description: 'add 必填:水平线或趋势线' },
219
+ kind: { type: 'string', enum: [...DRAWING_KINDS, 'ma'], description: 'add 必填:画线类型或 ma 均线' },
220
+ points: { type: 'array', description: 'hray/vline/ray/extended/channel/fib/rect/measure 的锚点,按点击顺序', items: { type: 'object', properties: { time: { type: 'number', description: 'K 线时间 epoch ms' }, price: { type: 'number' } }, required: ['time', 'price'] } },
221
+ maType: { type: 'string', enum: MA_KINDS, description: 'kind=ma:均线类型;smma 即平滑均线(RMA)' },
222
+ period: { type: 'number', description: 'kind=ma:周期,1-500 的整数' },
223
+ source: { type: 'string', enum: MA_SOURCES, description: 'kind=ma:价格源,默认 close' },
224
+ smoothingType: { type: 'string', enum: MA_KINDS, description: 'kind=ma 可选:对均线再做一次平滑的类型' },
225
+ smoothingPeriod: { type: 'number', description: 'kind=ma 可选:平滑周期' },
226
+ color: { type: 'string', description: '可选 #RRGGBB 颜色(画线或均线)' },
227
+ lineWidth: { type: 'number', description: '画线线宽 1-4' },
228
+ lineStyle: { type: 'string', enum: ['solid', 'dashed', 'dotted'], description: '画线线型' },
229
+ locked: { type: 'boolean', description: 'update:锁定后用户不能误拖动' },
230
+ hidden: { type: 'boolean', description: 'update:在图上隐藏(保留在画线列表中)' },
231
+ shared: { type: 'boolean', description: '同步到该币种的所有交易会话' },
232
+ alert: { type: 'boolean', description: '价格穿越提醒(level/hray/trend/ray/extended),触发一次后自动关闭' },
198
233
  role: { type: 'string', enum: ['support', 'resistance', 'neutral'], description: '视觉语义' },
199
234
  label: { type: 'string', description: '图上展示的短标签,1-60 字' },
200
235
  price: { type: 'number', description: '水平线价格' },
@@ -202,7 +237,7 @@ export function buildToolDefinitions(remote) {
202
237
  fromPrice: { type: 'number', description: '趋势线起点价格' },
203
238
  toTime: { type: 'number', description: '趋势线终点 K 线时间,epoch ms' },
204
239
  toPrice: { type: 'number', description: '趋势线终点价格' },
205
- id: { type: 'string', description: 'remove 时使用 list 返回的标注 ID' },
240
+ id: { type: 'string', description: 'remove 时使用 list 返回的标注或均线 ID' },
206
241
  }, required: ['action'], additionalProperties: false,
207
242
  },
208
243
  output: { schema: { type: 'object', additionalProperties: true }, render: (_a, v) => [{ type: 'text', text: v.text }] },
@@ -211,13 +246,39 @@ export function buildToolDefinitions(remote) {
211
246
  remote.requireTradingSession(sessionId)
212
247
  const coin = canonicalMarketCoin(args?.coin ?? remote.store.getSession(sessionId)?.coin ?? remote.desk.coin)
213
248
  const interval = args?.interval ?? remote.store.getSessionWorkspace?.(sessionId)?.interval ?? remote.desk.interval
249
+ const maLines = () => remote.store.getSessionWorkspace?.(sessionId)?.maLines ?? []
250
+ const maValues = async (lines) => {
251
+ if (!lines.length) return []
252
+ const { candles } = await remote.candles({ coin, interval })
253
+ return maLatest(closedCandles(candles, { interval }).rows, lines)
254
+ }
255
+ const maText = (row) => `${row.id} ${row.label} = ${row.value == null ? '数据不足' : fmt(row.value)}${row.line.smoothing ? `,平滑 = ${row.smoothed == null ? '数据不足' : fmt(row.smoothed)}` : ''}`
214
256
  if (args?.action === 'list') {
215
- const result = await remote.chartMarks({ sessionId, coin, interval })
216
- return { text: result.marks.length ? result.marks.map((mark) => `${mark.id} ${mark.kind} ${mark.role} ${mark.label}`).join('\n') : '当前图表没有标注', ...result }
257
+ const result = await remote.chartMarks({ sessionId, coin, allIntervals: true })
258
+ const ma = await maValues(maLines())
259
+ const lines = [...result.marks.map((mark) => `${mark.id} ${mark.interval} ${mark.kind} ${mark.role} ${mark.label}`), ...ma.map((row) => `${maText(row)}(${coin} ${interval} 最新已收盘)`)]
260
+ return { text: lines.length ? lines.join('\n') : '当前标的没有标注或均线', ...result, maLines: ma }
261
+ }
262
+ if (args?.action === 'add' && args?.kind === 'ma') {
263
+ const line = normalizeMaLine({ kind: args.maType, period: args.period, source: args.source, color: args.color,
264
+ smoothing: args.smoothingType != null || args.smoothingPeriod != null ? { kind: args.smoothingType, period: args.smoothingPeriod } : null })
265
+ const next = remote.setSessionMaLines(sessionId, [...maLines().filter((row) => row.id !== line.id), line])
266
+ const [row] = await maValues([next.find((item) => item.id === line.id)])
267
+ return { text: `已在 ${coin} 图表叠加均线 ${maText(row)}(${interval} 最新已收盘),切换周期会自动重算。`, line: row.line, value: row.value, smoothed: row.smoothed }
268
+ }
269
+ if (args?.action === 'remove' && maLines().some((row) => row.id === args?.id)) {
270
+ const removed = maLines().find((row) => row.id === args.id)
271
+ remote.setSessionMaLines(sessionId, maLines().filter((row) => row.id !== args.id))
272
+ return { text: `已移除均线:${maLabel(removed)}(${removed.id})`, line: removed }
273
+ }
274
+ if (args?.action === 'update') {
275
+ const patch = Object.fromEntries(['points', 'price', 'fromTime', 'fromPrice', 'toTime', 'toPrice', 'label', 'color', 'lineWidth', 'lineStyle', 'locked', 'hidden', 'shared', 'alert'].filter((key) => args?.[key] != null).map((key) => [key, args[key]]))
276
+ const mark = remote.editChartMark(args?.id, patch, sessionId)
277
+ return { text: `已更新${DRAWING_NAMES[mark.kind] ?? mark.kind}:${mark.label}(${mark.id})`, mark }
217
278
  }
218
279
  if (args?.action === 'add') {
219
280
  const mark = remote.addChartMark({ ...args, coin, interval }, sessionId)
220
- return { text: `已在 ${coin} ${interval} 图表添加 ${mark.kind === 'level' ? '水平线' : '趋势线'}:${mark.label}(${mark.id})`, mark }
281
+ return { text: `已在 ${coin} ${interval} 图表添加${DRAWING_NAMES[mark.kind] ?? mark.kind}:${mark.label}(${mark.id})`, mark }
221
282
  }
222
283
  if (args?.action === 'remove') {
223
284
  const mark = remote.removeChartMark(args?.id, sessionId)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-hyperliquid",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Hyperliquid trading desk for the DeepSeek Harness (dsh) - market workspace, native agent conversation and guarded live / paper trading over the Hyperliquid API.",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",
@@ -37,7 +37,8 @@
37
37
  "build": "node scripts/build-client.mjs",
38
38
  "build:watch": "node scripts/build-client.mjs --watch",
39
39
  "typecheck": "tsc --noEmit -p tsconfig.client.json",
40
- "test": "node test/signing.test.mjs && node test/signing-web3.test.mjs && node test/exchange-nonce.test.mjs && node test/store.test.mjs && node test/trading-guard.test.mjs && node test/paper-risk.test.mjs && node test/network-switch.test.mjs && node test/account-data.test.mjs && node test/market-data.test.mjs && node test/market-symbol.test.mjs && node test/indicators.test.mjs && node test/chart-marks.test.mjs && node test/history.test.mjs && node test/skill.test.mjs && node test/markers.test.mjs && node test/client-integration.mjs"
40
+ "test": "node test/signing.test.mjs && node test/signing-web3.test.mjs && node test/exchange-nonce.test.mjs && node test/store.test.mjs && node test/trading-guard.test.mjs && node test/risk-settings.test.mjs && node test/paper-risk.test.mjs && node test/network-switch.test.mjs && node test/account-data.test.mjs && node test/market-data.test.mjs && node test/market-symbol.test.mjs && node test/indicators.test.mjs && node test/moving-average.test.mjs && node test/strategy-theories.test.mjs && node test/chart-marks.test.mjs && node test/chart-drawings.test.mjs && node test/click-guard.test.mjs && node test/drawing-sync-alerts.test.mjs && node test/tool-output.test.mjs && node test/chart-reference-chip.test.mjs && node test/dsh-extensions.test.mjs && node test/history.test.mjs && node test/skill.test.mjs && node test/markers.test.mjs && node test/client-integration.mjs",
41
+ "smoke:drawing": "bash scripts/drawing-smoke/run.sh"
41
42
  },
42
43
  "dependencies": {
43
44
  "@deepseek-ai/schemastery": "^3.18.4",
@@ -73,6 +74,7 @@
73
74
  }
74
75
  },
75
76
  "devDependencies": {
77
+ "@deepseek-ai/cordis": "~4.0.4",
76
78
  "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
77
79
  "@types/react": "^19.3.0",
78
80
  "@types/react-dom": "^19.3.0",
@@ -19,7 +19,17 @@ description: 在 DSH 交易会话中分析 Hyperliquid 实时行情、账户与
19
19
 
20
20
  1. 用 `hyper_market action=indicators` 读取对应币种和周期的 SMA20、EMA20/50、RSI14、MACD、布林带、ATR14、20 根量比以及最近摆动高低点推导的支撑/阻力。工具只用交易所 `T` 收盘时间已过去的 K 线计算,并返回 `excludedOpen`;图表上的实时末根 K 线仍可能形成中。先检查 `ready`、已收盘 K 线数量和 `observedAt`(最新收盘时间),并按周期判断数据是否过期;旧数据或少于 50 根已收盘 K 线不可作为入场依据。需要跨周期验证时分别查询当前周期与更高周期,不把两者混作同一信号。
21
21
  2. 将趋势、动量、波动和量能分开判断:例如价格相对 EMA20/50、MACD 与 RSI 是否同向,价格是否接近支撑/阻力,量比是否支持突破,ATR 所示正常波动是否足以触及止损。指标相互矛盾、价格已经远离合理入场区、盘口过薄或止损所需风险超过限额时,选择观望。支撑/阻力是历史区间,不是保证成交或反弹的价格。
22
- 3. 给人解释图表时可用 `hyper_chart action=add` 画水平支撑/阻力线或两点趋势线,标签写明依据和周期;画线前用实际 K 线核实价格与时间(趋势线使用 epoch 毫秒)。每条线只属于当前 DSH 交易会话、币种和周期,可用 `list/remove` 管理。图表标注不触发交易,也不替代 `hyper_decide` 的实时预检。
22
+ 3. 给人解释图表时可用 `hyper_chart action=add` 画水平支撑/阻力线、两点趋势线,或用 points 画射线、平行通道、斐波那契回撤(第 1 点为波段起点)、矩形区间与价格区间测量、文本备注,或用 long/short 标出入场、止损与止盈(points 为 [入场, 止损, 止盈]);波段结构可用 fibext(第 3 点为回撤点)、pitchfork、regression(按收盘价拟合 ±2σ)、gannfan / gannbox 表达,浪型与形态用 elliott(0-5)、elliottabc、xabcd、abcd、headshoulders 标出,关键位用 callout / pricelabel / icon 标注;已有画线用 `action=update` 按 id 调整,不必删除重画;用户要求盯某个价位或趋势线时,可对 level/hray/trend/ray/extended 设置 `alert=true`(穿越时提醒,触发一次),需要在同币种所有会话可见时设置 `shared=true`,标签写明依据和周期;画线前用实际 K 线核实价格与时间(趋势线使用 epoch 毫秒)。每条线只属于当前 DSH 交易会话、币种和周期,可用 `list/remove` 管理。图表标注不触发交易,也不替代 `hyper_decide` 的实时预检。需要平滑均线(SMMA/RMA)或任意周期的 SMA/EMA/WMA 时,用 `hyper_chart action=add kind=ma maType=smma period=…` 叠加到图上(可加 smoothingType/smoothingPeriod 二次平滑),用 `hyper_market action=indicators` 读取其最新已收盘值;不要再建议用户去外部图表工具。
23
+
24
+ ## 策略研究框架
25
+
26
+ 交易台的“策略框架”只用已收盘 K 线展示可核对的观察条件;它没有证明当前币种、周期或执行方式能够盈利。若用户询问策略,先确认其交易周期与风险预算,区分**理论依据、当下满足的条件、反证、执行成本**,再决定观望或继续分析。不得把单个指标称为买卖信号。尤其是布林带碰到上/下轨本身不是反转信号,轨外收盘也可能延续趋势([Bollinger 原始规则](https://www.bollingerbands.com/bollinger-band-rules))。
27
+
28
+ - **趋势跟随**:观察已收盘价相对 EMA20/50 的位置、均线斜率,并用更高周期确认方向。时间序列动量有跨传统期货资产的研究证据([Moskowitz、Ooi、Pedersen,2012](https://doi.org/10.1016/j.jfineco.2011.11.003)),不能直接外推为 5 分钟加密永续合约的预期收益。说明回撤入场条件、趋势失效点和追价风险。
29
+ - **区间突破**:以前 20 根已收盘 K 线高低点和量比做候选条件;必须检查收盘是否站稳、盘口滑点与假突破。20 根和 1.5 倍量比只是产品默认观察参数,尚未在当前市场回测优化;不要把它们描述成已验证阈值。
30
+ - **波动带回归**:只在相对平缓的趋势中,观察前根收盘越轨、随后收回轨内以及 RSI 的配合;出现强单边趋势时优先观望。布林带的 20 根、2 倍标准差为常见默认参数,需按市场和任务重新验证。
31
+
32
+ 任何框架都要结合 `hyper_market indicators` 的最新已收盘时间、ATR、成交量、盘口、资金费与账户暴露。波动升高时应审查仓位规模;波动管理的研究来自股票因子等资产,并不保证该合约收益([Moreira、Muir,2017](https://www.nber.org/system/files/working_papers/w22208/w22208.pdf))。若用历史 K 线比较方案,要计入手续费、滑点和资金费,并区分样本内、样本外与实际交易;模拟表现有显著局限([CFTC 说明](https://www.cftc.gov/LearnAndProtect/AdvisoriesAndArticles/fraudadv_tradingsystem.html))。
23
33
 
24
34
  ## 决策与执行
25
35