kenlet-v2
一个把 LLM 从交易层剥离出去的交易引擎,可以承载任意策略;同时作为 DeepSeek Harness 插件接入, 在 Harness 里做可视化的策略设计、回测、分析与迭代。
- 引擎不依赖 LLM。
core/engine.cjs不知道 LLM 的存在。策略只输出"想要的仓位", 下单、仓位计算、成交价、手续费、滑点、资金费、止损止盈、回撤熔断与账本全部由引擎负责。 - 策略可替换。 实现
onBar(bar, context)即可接入;也可以使用本地解释执行的声明式规则 JSON。 - LLM 只做建议。 LLM 生成的规则是声明式 JSON,经本地白名单校验后由解释器执行, 绝不执行生成代码;LLM 产出的参数与权重全部经过硬边界、置信度门槛与幻觉过滤。
1 与 kenlet-v1 的关系
kenlet-v1 是一个三层自适应加密资产交易系统(Layer 1 引擎 / Layer 2 组合 / Layer 3 LLM)。 v2 的目标不同:把已被 v1 验证过的工程结论固化成一个与 LLM 解耦的交易层, 只保留"交易层 + 受约束的 LLM 建议"这两件事,并把 v1 中经验证的风控机制原样吸收:
| v1 已验证的机制 | v2 落地位置 | 说明 |
|---|---|---|
| 统一执行引擎(回测/实盘同一决策路径) | TradingEngine.onBar |
唯一逐 bar 路径,回测与未来实盘适配器共用 |
| 风险本位仓位 `q = E·r/ | p_e−p_sl | ` |
| ATR 止损/止盈 | 策略 intent + 引擎执行 | 引擎强制止损在亏损侧、止盈在盈利侧 |
| 真实成本模型(价差/滑点/手续费/资金费/最小单/步进/tick/部分成交) | ExecutionModel |
资金费逐 bar 结算,部分成交只作用于入场 |
| 回撤熔断 | RiskManager.check |
触发后不再开新仓,并给出停止原因 |
波动率目标 κ = clamp(σ_target/σ̂, floor, cap) |
RiskManager + 引擎逐 bar 刷新 |
高波动期自动收缩风险预算 |
| 市场状态判定(bull/bear/ranging/choppy) | detectRegime |
MA 排列 + Wilder ADX + ATR 分位,只读当前及历史 bar |
| Walk-Forward 样本外验证 | walkForward |
滚动训练/测试窗口,训练集选参、测试集评估、OOS 收益链式拼接 |
| LLM 硬护栏 / 置信度门槛 / 指令校验 / 容错降级 / 决策台账 | validateAdvisorDecision + 工作台台账 |
未知参数键、幻觉标的与未知指令一律过滤并记录 |
| LLM 影子组合与增量 alpha | ShadowPortfolio |
LLM 权重只记账不下单,与实际组合对比得出 α |
| 集成投票(多次采样) | voteAdvisorDecisions |
action 多数决(平票降级 hold)、数值取中位数、置信度取均值 |
v2 没有搬过来的:多标的组合调度、相关性风控 σ_p=√(wᵀΣw)、外部指数(QQQ/TWI)注入、
实盘/纸交易下单通道。参见 §9 局限。
1.1 v2 的模块全景
┌────────────────────────── deepseek-harness (DSH) ──────────────────────────┐
│ 侧栏「交易研究模式」→ kenlet Workbench(React SVG 工作台) │
│ │
│ ┌── core(纯交易层,LLM-free,浏览器/Node 通用)─────────────────────┐ │
│ │ samples → indicators → registry ─┐ │ │
│ │ ├→ engine(逐 bar 回测/风控/成本)│ │
│ │ portfolio(σ_p/κ 相关性风控)────┤ │ │
│ │ chart-model(数据→几何图元)─────┘ │ │
│ └──────────────────────────┬───────────────────────────────────────┘ │
│ │ strategy.onBar(bar, ctx) → intent │
│ ┌── core/llm-lab(可选 LLM 层,全部过护栏)─────────────────────────┐ │
│ │ 设计 → 回测 → 分析 → 修改 闭环 · 硬边界 · 置信度门槛 · 台账 │ │
│ └────────────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────────────┘
"纯粹的 LLM 剥离的交易层":删掉 llm-lab.cjs 与 UI 的 LLM 面板,
其余一切照常工作——回测、风控、样本外验证、可视化都不需要 LLM。
"底部可以装任何策略":引擎只认识 onBar(bar, ctx) → intent;
builtin(5 个内置)/ dsl(声明式 JSON)/ code(沙箱化用户 JS)/
external(宿主注入工厂)四种来源经 registry 统一装填。
2 安装为 DeepSeek Harness 插件
插件已在仓库里构建完成:dist/ 就是一个自包含的插件包
(package.json / cordis.patch.yml / index.js / client.js / core/engine.cjs / icon.svg)。
2.1 安装到 profile
dsh plugin 会把包管理命令透传给当前 profile:
# 从本地路径安装(推荐:源码即插件)
dsh plugin --profile web add file:/absolute/path/to/kenlet-v2
# 或者直接指定 dist/
dsh plugin --profile web add file:/absolute/path/to/kenlet-v2/dist
安装会把依赖写入 profile 的 package.json,并在 dsh.profile.bundles 末尾追加该 bundle
(profile 目录默认为 $DSH_HOME/profiles/<name>)。也可以在 Web 侧边栏的 Plugins 页面里
选择该 bundle;或在 profile 的 cordis.patch.yml 里手写一行:
- insert:
- id: kenlet-v2
name: '@local/kenlet-v2'
config: {}
2.2 生效方式
- profile 声明了
patchReload: live时,cordis.patch.yml的变更会即时重组,无需重启; - 新增 bundle(
dsh.profile.bundles)需要重启对应 profile 的 app 才能进入组合: 关闭当前dsh web进程后重新执行dsh web即可。
客户端插件的自动热更新只有在 DSH 源码目录同时运行 pnpm run dev:web 时才生效;
其他情况下改动 client.js 后需要刷新页面。
2.3 本机安装现状(已执行)
本仓库已按"本地链接"方式装进 web profile:
C:\Users\Hileb\.dsh\profiles\web\node_modules\@local\kenlet-v2
→ 目录联接(junction)到 E:\Projects\Magixyne\kenlet-v2
C:\Users\Hileb\.dsh\profiles\web\package.json
dependencies: { "@local/kenlet-v2": "link:E:/Projects/Magixyne/kenlet-v2" }
dsh.profile.bundles: [ ..., "@local/kenlet-v2" ]
已验证:
dsh --profile web --dump-config输出中包含# == @local/kenlet-v2层与- id: kenlet-v2 / name: '@local/kenlet-v2'行,且没有任何skipping profile bundle或解析报错;npm run verify:profile(12 项)确认 profile 的解析锚点能定位到本包、index.js可被导入且apply()是无副作用的 no-op、client.js与cordis.patch.yml都在包内、manifest 的dsh.bundle.patch/dsh.client/exports['./client']与 DSH 契约一致。
未验证的部分:Web GUI 由浏览器持有一次性启动令牌(请求头缺令牌时返回 401),
因此没有在真实页面里点开工作台做视觉确认。已做的是把 client.js 按
window.__ModuleLoader__.load 契约真实执行、物化、注册并用微型 React 渲染整棵树
(见 npm run verify,38 项)。
卸载:
Remove-Item -Recurse -Force "$env:USERPROFILE\.dsh\profiles\web\node_modules\@local\kenlet-v2"
# 然后把 package.json 里的 dependencies 与 dsh.profile.bundles 恢复为原值
# (本仓库执行前的备份在 %TEMP%\kenlet-profile-package.json.bak)
2.4 在界面里打开
Kenlet 以独立的交易研究模式出现在 Harness 侧栏,与会话和插件页面并列。 选择“交易研究模式”后会直接打开完整工作台;切换到其他侧栏项目即可离开该模式。
当前 Harness 为面板提供了侧栏项与独立主面板的扩展接口;新会话区域的
conversation.hero.agentPreset 是内置的单控件插槽,不提供第三方追加模式选项的机制。
因此 Kenlet 使用可扩展的一级侧栏工作区承载模式界面,不替换内置预设控件。
3 工作台能做什么
| 区块 | 能力 |
|---|---|
| 行情 | 内置样本数据集(BTC/ETH/SOL 日线 + BTC 4h,确定性 PRNG 生成,离线可用,明确标注「内置样本 · 非真实行情」);可一键载入 Binance 公共 K 线(无需密钥,失败自动回退样本)。标注当前市场状态(多头/空头/震荡/高波动) |
| 策略装填层 | 从策略注册表选择:ma_crossover(v1 Layer-1 移植)/ buy_hold(基准)/ rsi_reversal / bollinger_revert / donchian_breakout。参数表单由 paramSchema 动态生成,换策略即换表单 |
| 图表 | core/chart-model 驱动的 SVG:蜡烛 + 成交量 + 策略声明的指标叠加线(null 缺口切段)+ regime 底色带(bull 绿/bear 红/ranging 蓝/choppy 不着色)+ 每笔交易入场三角与出场 × 标记(按盈亏着色)+ 持仓区间 SL/TP 虚线导轨 + 净值/回撤曲线;支持窗口缩放平移与线性/对数切换 |
| 指标 | 净收益、最大回撤、交易数/胜率、Profit Factor、Calmar、净 PnL、成本合计、成本拖累 |
| 风控 | 单笔风险、ATR 止损止盈、回撤熔断、手续费、滑点、资金费、成交比例;组合相关性风控卡(平均相关 / 最大两两相关 / HHI / σ_p / κ,单标的或样本不足时 κ=1 不干预) |
| LLM 炼丹闭环 | 设计 → 回测 → 分析 → 修改 四阶段循环:LLM 产出策略 spec → 本地 validateCandidate 校验(拒绝可见)→ 回测 → buildAnalyzePrompt 诊断(过拟合/参数敏感/趋势依赖)→ buildRevisePrompt 迭代;全程 Ledger 台账,迭代预算上限,walk-forward 样本外复核防止炼出过拟合 |
| 滚动样本外验证 | 滚动 train/test 窗口,训练集选参、测试集评估,输出逐窗口劣化与 OOS 总收益 |
| LLM 参数审阅 | 把成本后绩效、样本内外切片与最近交易交给 LLM,只接受白名单参数建议;支持 1–5 次集成投票 |
| 影子组合 | LLM 给出的 weights 只做记账,按真实价格逐 bar 标记,与实际策略收益对比得到增量 alpha |
| 研究台账 | 设计、应用、参数审阅、样本外验证记录,保存在浏览器 localStorage |
LLM 永远不能绕过护栏:硬边界 clamp、置信度门槛、未知键/幻觉标的过滤、 集成投票、容错降级 hold、决策台账——六种机制全部沿用 kenlet-v1 已验证的实现。
所有设置(参数、已保存的声明式策略、LLM 接口配置、台账)都保存在本机浏览器, 不会上传到任何服务。
4 策略接口
任何对象只要实现 onBar 就能被引擎驱动:
const strategy = {
/**
* @param {{time:number, open:number, high:number, low:number, close:number, volume:number}} bar
* @param {{index:number, equity:number, position:number, entryPrice:number,
* stopPrice:number|null, riskHalted:boolean, volScale:number,
* history:Array<{equity:number}>}} context
* @returns {{target:number, reason?:string, stopDistance?:number,
* stopPrice?:number, takeProfit?:number, confidence?:number}}
*/
onBar(bar, context) {
// 只表达意图:target = 1 做多、-1 做空、0 空仓(也可给 0–1 的小数表达部分仓位)
return { target: 1, stopDistance: bar.close * 0.02, takeProfit: bar.close * 1.06, reason: 'my_signal' };
},
};
要点:
- 策略不碰现金和仓位。 引擎按
target与本地风险预算决定实际数量。 - 返回
null/非法target不会静默放大风险。 引擎会退回"保持当前仓位"并标记invalid_intent。 - 止损永远是引擎的职责。
stopDistance/stopPrice会被夹到成交价的亏损侧,takeProfit会被夹到盈利侧;止损与止盈同 bar 同时触发时按止损优先处理, 跳空穿越止损时按该 bar 开盘价成交。 - 状态由策略自己维护(历史 bar、指标缓存),引擎只在
context.history里给出权益曲线尾部。
4.1 直接运行时 API
const core = require('./core/engine.cjs');
core.runBacktest({ bars, strategy, initialCash: 10000, risk: {...}, execution: {...} });
core.runBacktest({ bars, strategyFactory: params => makeStrategy(params), ..., flattenAtEnd: true });
core.walkForward({ bars, strategyFactory, paramGrid, trainSize: 220, testSize: 110, warmupBars: 60, ... });
core.detectRegime(bars, bars.length - 1, { adxPeriod: 14, adxThreshold: 20, volPercentile: 0.85 });
4.2 intent 扩展字段
| 字段 | 说明 |
|---|---|
noStop: true |
策略显式声明这一笔不设止损(买入持有基准等)。引擎改用 risk.sizeByExposure() 按目标敞口定价,不再套 0.5% 止损下限——否则风险预算会被一条策略根本没要求的止损线锁死(实测半仓) |
useStop(buy_hold 参数) |
挂止损的持有变体,默认关闭才是真正的基准 |
5 声明式策略 DSL
LLM 与手工编辑器共用同一套 schema。它是数据,由 createRuleStrategy 在本地解释执行。
{
"version": 2,
"name": "RSI pullback with trend filter",
"description": "趋势过滤下的 RSI 回调入场",
"direction": "long",
"rules": {
"entry": {
"left": { "indicator": "rsi", "period": 14 },
"operator": "less_than",
"right": { "value": 35 }
},
"exit": {
"left": { "indicator": "rsi", "period": 14 },
"operator": "greater_than",
"right": { "value": 60 }
}
},
"risk": { "stopAtr": 2, "targetAtr": 4, "riskPerTrade": 0.01 },
"regime": { "enabled": true, "filter": ["bull", "ranging"], "adxThreshold": 20 }
}
| 字段 | 取值 | 约束 |
|---|---|---|
direction |
long / short / both |
both 时必须提供 rules.shortEntry |
left.indicator / right.indicator |
sma ema rsi atr close volume |
越界即拒绝 |
period |
整数 2–500 | close/volume 不接受周期 |
operator |
crosses_above crosses_below greater_than less_than between |
between 只接受数值区间 |
right |
{ "value": n } 或 { "value": n, "max": m }(between) |
max > value |
risk.stopAtr |
1.5–8 | ATR(14) 止损倍数 |
risk.targetAtr |
2–20 | ATR(14) 止盈倍数 |
risk.riskPerTrade |
0.005–0.05 | 单笔风险占权益比例 |
regime.enabled |
布尔 | 打开后按市场状态过滤新开仓 |
regime.filter |
bull bear ranging choppy 子集 |
可再叠加白名单 |
regime.mas |
整数数组 2–500 | 默认 [20, 60, 180] |
regime.adxPeriod |
整数 2–500 | 默认 14 |
regime.adxThreshold |
5–60 | 默认 20(低于此值判为震荡) |
regime.volPercentile |
0.5–1 | 默认 0.85(ATR 分位高于此值判为高波动) |
语义:
- 入场条件在空仓时求值;出场条件只在持仓时求值,门槛过滤不影响出场。
- 市场状态每 bar 现算,只读当前及历史 bar(无未来函数)。
direction: both时多空可以分别用entry/exit与shortEntry/shortExit。- 校验失败会返回全部错误信息,不会退化成一个"看起来能跑"的策略。
6 LLM 护栏
LLM 的输出永远先经过 validateAdvisorDecision(多次采样时再加 voteAdvisorDecisions):
| 护栏 | 行为 |
|---|---|
| 硬边界 | 参数被 clamp 到白名单区间:fastPeriod 5–100、slowPeriod 10–200、trendPeriod 20–500、riskPerTrade 0.005–0.05、atrStop 1.5–8、atrTarget 2–20、maxDrawdown 0.05–0.8;越界会记录在 rejected |
| 一致性 | fastPeriod ≥ slowPeriod、atrTarget ≤ atrStop 自动顺延并记录 |
| 参数白名单 | 不在上表里的键被忽略并记录(LLM 无法注入未知旋钮) |
| 置信度门槛 | confidence < 0.4 时动作降级为 hold;唯一例外是 halt(拒绝停手从不更安全) |
| 动作白名单 | adjust / hold / halt / rebalance / flatten,未知动作降级 hold 并记录 |
| 标的与指令校验 | weights 与 portfolioCommands 里的未知标的、未知指令、负权重一律剔除并记录;总权重 > 1 时归一化 |
| 集成投票 | 多次采样时 action 多数决(平票降级 hold)、数值取中位数、置信度取均值 |
| 容错降级 | 网络失败、非 JSON、缺少内容 → action: hold,交易与研究流程不中断 |
| 决策台账 | 每条决策(含 rejected 列表、置信度、时间戳)写入本地台账,可事后回放 |
| 影子组合 | weights 只进 ShadowPortfolio 记账,逐 bar 按真实价格标记,输出增量 alpha |
LLM 没有任何下单入口。 本插件不读取交易所密钥,也不存在下单代码路径。
7 风险与成本模型
7.1 仓位与风险预算
风险预算 = 权益 × riskPerTrade × 置信度 × 波动率缩放 × regime 门控
数量上界1 = 风险预算 / |成交价 − 止损价|
数量上界2 = 权益 × maxExposure × maxLeverage / 成交价
实际数量 = min(上界1, 上界2, 权益 × 目标仓位 / 成交价)
- 止损距离下限为价格的 0.5%,避免止损过近导致数量爆炸。
confidence低于llmConfidenceFloor(默认 0.4)时数量为 0。- 波动率目标:
κ_vol = clamp(σ_target / σ̂, volFloor, volCap),σ̂为最近volLookback根 bar 收益率标准差;volTarget = 0时关闭。 - 回撤熔断:权益相对高水位回撤 ≥
maxDrawdown时置halted,之后只平仓不开仓。
7.2 成本口径
每笔成交价从中间价外推:买单 ×(1 + spread/2 + slippage),卖单 ×(1 − spread/2 − slippage),
再按 tickSize 取整;数量按 quantityStep 向下取整,低于 minNotional 拒单,
入场再乘 fillRatio(< 1 模拟部分成交)。
手续费 notional × feeRate 计入每笔成交;持仓期间资金费逐 bar 结算
notional × fundingRatePerBar(现货为 0)。绩效口径:
netPnl = 已实现净额 + 未实现净额
fees = 毛盈亏 − 已实现净额 + 未实现预估手续费
costDrag = (手续费 + 资金费) / 初始权益
所有平仓路径(止损、止盈、信号、end_of_data 强制平仓)都经过同一个 fill 路径,
成本无法被绕过。
8 样本外验证(Walk-Forward)
walkForward 的语义:
- 按
step(默认等于testSize)滚动切出trainSize与testSize两段互不重叠的窗口; - 在训练段上评估
paramGrid的每个参数组,按metric(默认calmar)取最优; - 用最优参数在测试段上跑一份全新的引擎实例——测试段从不参与选参;
- 测试段前置
warmupBars根来自训练段的历史,仅用于预热指标, 入场基准权益取预热结束那一刻,因此预热期的盈亏不计入样本外收益; - 各窗口的样本外收益链式相乘,得到完整 OOS 曲线,并输出
averageInSampleReturn、averageOutOfSampleReturn与averageDegradation。
工作台会把这套流程可视化:逐窗口的样本内收益、样本外收益、劣化幅度与选中参数。
9 局限与不做的事
- 不接实盘。 没有交易所密钥、没有下单通道、没有纸交易循环。这是研究/模拟工具。
- 单一持仓。 引擎同一时刻只持有一个方向的一笔仓位;不做多标的组合调度,
因此 v1 的相关性风控
σ_p=√(wᵀΣw)与指数趋势门控没有落地(regime 门控是它的本地替代)。 - bar 级确定性成交。 不建模盘内部分成交、延迟、订单簿深度与资金费率的时变特征;
fillRatio只是固定比例的入场折减。 - LLM 层依赖外部 API。 需要自备 OpenAI 兼容接口与 Key(DeepSeek 默认),
调用失败只会降级为
hold;API Key 仅保存在本机浏览器 localStorage。 - 回到区间依赖。 趋势类策略在震荡区间收益为负属正常现象——这正是把样本外验证与 影子组合做成一等公民的原因,而不是调参到"回测好看"。
- 界面语言为中文。
locale/zh.json、locale/en.json目前只提供插件清单用的标题与描述, 不是完整的翻译层。 - 启动期防护而非收益承诺。 任何回测数值都不代表未来结果。
10 本地开发
npm run build # 把 core/engine.cjs 内联进 client.js,并组装 dist/ 插件包
npm run check # 语法检查 + 引擎自检 + 客户端契约自检
npm run smoke # 只跑引擎自检(44 项)
npm run verify # 只跑客户端契约自检(38 项)
npm run verify:profile # 只跑 profile 解析自检(12 项,需要已装进某个 profile)
npm run check 覆盖的内容:
node --check全部 4 个入口文件(含内联后的client.js);- 引擎自检:市场状态无未来函数、DSL 校验与拒绝路径、成本与资金费口径、 部分成交、波动率缩放夹取、LLM 护栏(夹取/未知键/幻觉标的/置信度降级/集成投票平票)、 影子组合 alpha、Walk-Forward 窗口单调不重叠、回撤熔断、非法 OHLC 抛错;
- 客户端契约自检:以经典脚本方式执行
client.js,通过模块表物化 factory, 用假 slot 上下文执行apply(),再用一个微型 React(含 hooks/effects 与依赖数组 比较)渲染整棵组件树——真实跑通 regime 检测、回测与图表模型, 并断言关键 UI 文案与client.js内联的引擎副本与core/engine.cjs一致。
11 目录结构
kenlet-v2/
├── core/ 交易层(零依赖,浏览器与 Node 通用)
│ ├── CONTRACT.md 模块接口契约(先读)
│ ├── CHANGELOG.md 本轮变更与缺陷修复记录
│ ├── engine.cjs 引擎:TradingEngine · RiskManager(含 sizeByExposure) · ExecutionModel
│ │ runBacktest · walkForward · detectRegime · DSL 校验/解释
│ │ LLM 护栏(validateAdvisorDecision / voteAdvisorDecisions)· ShadowPortfolio
│ ├── indicators.cjs 指标库(唯一真源):13 指标 + series 统一入口 + INDICATORS 注册表
│ ├── registry.cjs 策略装填层:builtin/dsl/code/external 四来源 → onBar;代码沙箱三层隔离
│ ├── portfolio.cjs 组合相关性风控(v1 §3.6):σ_p=√(wᵀΣw) → κ_corr 预算缩放
│ ├── chart-model.cjs 可视化模型:纯数据 → 几何图元(蜡烛/色带/标记/导轨/净值)
│ ├── samples.cjs 内置样本:确定性 PRNG,BTC/ETH/SOL 日线相关系数≈0.78
│ └── llm-lab.cjs LLM 炼丹闭环:提示词/解析/校验/Ledger/AlchemyLab/walk-forward 复核
├── src/client-app.js Harness 客户端插件源码(构建前)
├── client.js 构建产物:7 个 core 模块按依赖序内联 + 组件
├── index.js Host 半边(无副作用;不持有密钥、不下单)
├── cordis.patch.yml bundle 补丁:插入 kenlet-v2 插件行
├── scripts/build.mjs 构建:多模块内联(含 Node-API 禁用扫描)+ 复制到 dist/
├── scripts/smoke-engine.mjs 引擎自检
├── scripts/verify-client.mjs 客户端契约自检(含逐模块内联一致性)
├── scripts/verify-profile.mjs profile 解析自检
├── scripts/verify-bundle.cjs 打包产物浏览器可用性自检(vm 沙箱求值)
├── scripts/verify-render.cjs 端到端渲染自检(微型 React 渲染整树,统计 SVG 图元)
├── locale/*.json 插件清单用的标题与描述
├── icon.svg
└── dist/ 可直接安装的自包含插件包
本地验证
export PATH="/c/Program Files/nodejs:$PATH" # 本机 node 不在默认 PATH 时
npm run check # 语法 + smoke-engine + verify-client + verify-bundle + verify-render
npm run build # 重新内联生成 client.js 与 dist/
npm run verify:profile
12 参考资料
- kenlet-v1 —— 三层自适应加密资产交易系统(统一执行引擎 / 组合指数算法 / LLM 决策层)。 v2 的风控与验证机制来自该项目已验证的实现,并在此剥离为不依赖 LLM 的交易层。
- Faber (2007) 趋势门控、Jegadeesh–Titman (1993) 动量、Moreira–Muir (2017) 波动率管理、 Wilder (1978) ADX/ATR —— 见 kenlet-v1 README 的参考文献。
仅用于研究与模拟;不构成投资建议。
No comments yet. Be the first to write one.