@dshtrading/strategies 0.1.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/LICENSE +75 -0
- package/lib/api/lib/index.d.ts +461 -0
- package/lib/custom-fs.d.ts +5 -0
- package/lib/custom-fs.js +72 -0
- package/lib/custom.d.ts +28 -0
- package/lib/custom.js +16 -0
- package/lib/engine.d.ts +6 -0
- package/lib/engine.js +165 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.js +12 -0
- package/lib/paradigms/bollinger-reversion.d.ts +5 -0
- package/lib/paradigms/bollinger-reversion.js +77 -0
- package/lib/paradigms/donchian-breakout.d.ts +5 -0
- package/lib/paradigms/donchian-breakout.js +71 -0
- package/lib/paradigms/ema-crossover.d.ts +5 -0
- package/lib/paradigms/ema-crossover.js +83 -0
- package/lib/paradigms/index.d.ts +12 -0
- package/lib/paradigms/index.js +23 -0
- package/lib/paradigms/momentum-12m.d.ts +5 -0
- package/lib/paradigms/momentum-12m.js +73 -0
- package/lib/paradigms/rsi-reversion.d.ts +5 -0
- package/lib/paradigms/rsi-reversion.js +89 -0
- package/lib/paradigms/sma-baseline.d.ts +5 -0
- package/lib/paradigms/sma-baseline.js +70 -0
- package/lib/plugin.d.ts +44 -0
- package/lib/plugin.js +214 -0
- package/lib/screeners/above-ma.d.ts +1 -0
- package/lib/screeners/above-ma.js +66 -0
- package/lib/screeners/index.d.ts +11 -0
- package/lib/screeners/index.js +18 -0
- package/lib/screeners/ma-bull-align.d.ts +1 -0
- package/lib/screeners/ma-bull-align.js +68 -0
- package/lib/screeners/near-high.d.ts +1 -0
- package/lib/screeners/near-high.js +48 -0
- package/lib/screeners/rsi-oversold.d.ts +1 -0
- package/lib/screeners/rsi-oversold.js +50 -0
- package/lib/screeners/types.d.ts +38 -0
- package/lib/screeners/volume-breakout.d.ts +1 -0
- package/lib/screeners/volume-breakout.js +63 -0
- package/lib/types.d.ts +81 -0
- package/lib/validate-node.js +40 -0
- package/lib/validate.d.ts +28 -0
- package/lib/validate.js +264 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Copyright (c) 2026 zhu1090093659 (dsh-trading)
|
|
2
|
+
|
|
3
|
+
# PolyForm Noncommercial License 1.0.0
|
|
4
|
+
|
|
5
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
6
|
+
|
|
7
|
+
## Acceptance
|
|
8
|
+
|
|
9
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
|
|
14
|
+
|
|
15
|
+
## Distribution License
|
|
16
|
+
|
|
17
|
+
The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
|
|
18
|
+
|
|
19
|
+
## Notices
|
|
20
|
+
|
|
21
|
+
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
|
|
22
|
+
|
|
23
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
24
|
+
|
|
25
|
+
## Changes and New Works License
|
|
26
|
+
|
|
27
|
+
The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
|
|
28
|
+
|
|
29
|
+
## Patent License
|
|
30
|
+
|
|
31
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
32
|
+
|
|
33
|
+
## Noncommercial Purposes
|
|
34
|
+
|
|
35
|
+
Any noncommercial purpose is a permitted purpose.
|
|
36
|
+
|
|
37
|
+
## Personal Uses
|
|
38
|
+
|
|
39
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
|
|
40
|
+
|
|
41
|
+
## Noncommercial Organizations
|
|
42
|
+
|
|
43
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
|
|
44
|
+
|
|
45
|
+
## Fair Use
|
|
46
|
+
|
|
47
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
48
|
+
|
|
49
|
+
## No Other Rights
|
|
50
|
+
|
|
51
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
52
|
+
|
|
53
|
+
## Patent Defense
|
|
54
|
+
|
|
55
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
56
|
+
|
|
57
|
+
## Violations
|
|
58
|
+
|
|
59
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
60
|
+
|
|
61
|
+
## No Liability
|
|
62
|
+
|
|
63
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
64
|
+
|
|
65
|
+
## Definitions
|
|
66
|
+
|
|
67
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
68
|
+
|
|
69
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
70
|
+
|
|
71
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
72
|
+
|
|
73
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
74
|
+
|
|
75
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
//#region ../api/lib/index.d.ts
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* @dshtrading/api — 纯类型契约包(crypto-slice-plan §关键接口草案)。
|
|
5
|
+
*
|
|
6
|
+
* 零运行时、零依赖:服务契约由连接器实现(ctx 键按市场命名空间,如 ctx.tradingCrypto),
|
|
7
|
+
* 消费方只依赖这里的类型。交易安全闸门(铁律 #3)在类型层面体现为
|
|
8
|
+
* OrderRequest.dryRun 默认 true、错误词汇含 LIVE_TRADING_DISABLED / APPROVAL_DENIED。
|
|
9
|
+
*
|
|
10
|
+
* @module @dshtrading/api
|
|
11
|
+
*/
|
|
12
|
+
/** K线周期(Binance 现货/合约 interval 词汇)。 */
|
|
13
|
+
type Interval = '1m' | '3m' | '5m' | '15m' | '30m' | '1h' | '2h' | '4h' | '6h' | '8h' | '12h' | '1d' | '3d' | '1w' | '1M';
|
|
14
|
+
/** 最新行情快照(公共数据,无需凭证)。 */
|
|
15
|
+
interface Ticker {
|
|
16
|
+
/** 交易对符号,**市场规范词汇**(docs/symbol-vocabulary.md:crypto=BTCUSDT,us=AAPL,cn=600519.SH,hk=00700.HK)。 */
|
|
17
|
+
readonly symbol: string;
|
|
18
|
+
/** 标的/公司名称(如“紫光股份”、“苹果”、“腾讯控股”;部分数据源可缺省)。 */
|
|
19
|
+
readonly name?: string;
|
|
20
|
+
/** 最新成交价。 */
|
|
21
|
+
readonly price: number;
|
|
22
|
+
/** 最优买价(部分数据源可缺省)。 */
|
|
23
|
+
readonly bid?: number;
|
|
24
|
+
/** 最优卖价(部分数据源可缺省)。 */
|
|
25
|
+
readonly ask?: number;
|
|
26
|
+
/** 24h 成交量(base 资产计)。 */
|
|
27
|
+
readonly volume?: number;
|
|
28
|
+
/** 快照时间(epoch ms)。 */
|
|
29
|
+
readonly timestamp: number;
|
|
30
|
+
/** 官方昨收(涨跌基准锚点;部分数据源可缺省,缺省时消费方退回日 K 自算)。 */
|
|
31
|
+
readonly prevClose?: number;
|
|
32
|
+
/** 相对昨收的涨跌幅(百分比,如 -0.89 表示 -0.89%;部分数据源可缺省)。 */
|
|
33
|
+
readonly changePercent?: number;
|
|
34
|
+
}
|
|
35
|
+
/** 单根 K 线。 */
|
|
36
|
+
interface Kline {
|
|
37
|
+
readonly openTime: number;
|
|
38
|
+
readonly open: number;
|
|
39
|
+
readonly high: number;
|
|
40
|
+
readonly low: number;
|
|
41
|
+
readonly close: number;
|
|
42
|
+
readonly volume: number;
|
|
43
|
+
readonly closeTime: number;
|
|
44
|
+
}
|
|
45
|
+
/** 衍生品市场指标快照(持仓量/多空比/资金费等)。 */
|
|
46
|
+
interface DerivativesData {
|
|
47
|
+
/** 交易对符号,市场规范词汇(如 BTCUSDT 或 BTCUSDT-SWAP)。 */
|
|
48
|
+
readonly symbol: string;
|
|
49
|
+
/** 数据来源(如 binance / okx)。 */
|
|
50
|
+
readonly source: string;
|
|
51
|
+
/** 未平仓合约量(Open Interest,base 币数或张数)。 */
|
|
52
|
+
readonly openInterest?: number;
|
|
53
|
+
/** 未平仓合约总价值(USD 或 quote 计价)。 */
|
|
54
|
+
readonly openInterestValue?: number;
|
|
55
|
+
/** 多空人数比(Long/Short Account Ratio)。 */
|
|
56
|
+
readonly longShortRatio?: number;
|
|
57
|
+
/** 大户持仓多空比(Top Trader Long/Short Position Ratio)。 */
|
|
58
|
+
readonly topTraderLongShortRatio?: number;
|
|
59
|
+
/** 主动买入/卖出量比(Taker Buy/Sell Volume Ratio)。 */
|
|
60
|
+
readonly takerBuySellRatio?: number;
|
|
61
|
+
/** 最新资金费率(小数,如 0.0001 表示 0.01%)。 */
|
|
62
|
+
readonly fundingRate?: number;
|
|
63
|
+
/** 预测(下一期)资金费率(小数;交易所未发布预测时缺省,issue #54)。 */
|
|
64
|
+
readonly nextFundingRate?: number;
|
|
65
|
+
/** 下一期资金费率结算时间(epoch ms,GUI 倒计时用,issue #54)。 */
|
|
66
|
+
readonly nextFundingTime?: number;
|
|
67
|
+
/** 标记价格(Mark Price,永续合约;issue #54 基差卡用)。 */
|
|
68
|
+
readonly markPrice?: number;
|
|
69
|
+
/** 指数价格(Index Price,现货指数;与 markPrice 配对计算基差)。 */
|
|
70
|
+
readonly indexPrice?: number;
|
|
71
|
+
/** 快照时间(epoch ms)。 */
|
|
72
|
+
readonly timestamp: number;
|
|
73
|
+
}
|
|
74
|
+
/** 衍生品时序数据点(资金费率历史 / OI 历史共用词汇,issue #54)。 */
|
|
75
|
+
interface DerivativesPoint {
|
|
76
|
+
/** 采样时间(epoch ms)。 */
|
|
77
|
+
readonly time: number;
|
|
78
|
+
/** 采样值(费率序列为小数费率;OI 序列单位与快照 openInterest 同语义)。 */
|
|
79
|
+
readonly value: number;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 衍生品历史序列(GUI「衍生品」页签趋势卡用,issue #54):
|
|
83
|
+
* 资金费率历史 + 未平仓量历史,时间一律升序(旧→新,与 K 线序列同向)。
|
|
84
|
+
* 可选方法 getDerivativesHistory 的返回面;任一序列不可得时该字段缺省,
|
|
85
|
+
* 消费方按卡降级(隐藏趋势图、保留快照读数)。
|
|
86
|
+
*/
|
|
87
|
+
interface DerivativesHistory {
|
|
88
|
+
/** 交易对符号,市场规范词汇(如 BTCUSDT-SWAP)。 */
|
|
89
|
+
readonly symbol: string;
|
|
90
|
+
/** 数据来源(如 binance / okx)。 */
|
|
91
|
+
readonly source: string;
|
|
92
|
+
/** 资金费率历史(时间升序,value 为小数费率)。 */
|
|
93
|
+
readonly fundingRates?: DerivativesPoint[];
|
|
94
|
+
/** OI 历史(时间升序,value 单位与 DerivativesData.openInterest 同语义)。 */
|
|
95
|
+
readonly openInterest?: DerivativesPoint[];
|
|
96
|
+
}
|
|
97
|
+
/** 股票市场标的基本面与财务估值快照(US/CN/HK)。 */
|
|
98
|
+
interface StockFundamentals {
|
|
99
|
+
/** 标的规范符号(如 AAPL, 600519.SH, 00700.HK)。 */
|
|
100
|
+
readonly symbol: string;
|
|
101
|
+
/** 公司/标的名称。 */
|
|
102
|
+
readonly name?: string;
|
|
103
|
+
/** 总市值(本位币计价)。 */
|
|
104
|
+
readonly marketCap?: number;
|
|
105
|
+
/** 流通市值(A 股/港股适用)。 */
|
|
106
|
+
readonly floatMarketCap?: number;
|
|
107
|
+
/** 滚动市盈率 PE (TTM)。 */
|
|
108
|
+
readonly peTtm?: number;
|
|
109
|
+
/** 静态市盈率 PE (静)。 */
|
|
110
|
+
readonly peStatic?: number;
|
|
111
|
+
/** 动态/预测市盈率 Forward / Dynamic PE。 */
|
|
112
|
+
readonly peDynamic?: number;
|
|
113
|
+
/** 市净率 PB。 */
|
|
114
|
+
readonly pb?: number;
|
|
115
|
+
/** 市销率 PS。 */
|
|
116
|
+
readonly ps?: number;
|
|
117
|
+
/** 每股收益 EPS。 */
|
|
118
|
+
readonly eps?: number;
|
|
119
|
+
/** 每股净资产 BPS。 */
|
|
120
|
+
readonly bps?: number;
|
|
121
|
+
/** 股息率(小数,如 0.015 表示 1.5%)。 */
|
|
122
|
+
readonly dividendYield?: number;
|
|
123
|
+
/** 换手率(小数或百分比)。 */
|
|
124
|
+
readonly turnoverRate?: number;
|
|
125
|
+
/** 振幅(百分比)。 */
|
|
126
|
+
readonly amplitudePercent?: number;
|
|
127
|
+
/** 涨停价(A 股适用)。 */
|
|
128
|
+
readonly limitUpPrice?: number;
|
|
129
|
+
/** 跌停价(A 股适用)。 */
|
|
130
|
+
readonly limitDownPrice?: number;
|
|
131
|
+
/** 52 周最高价。 */
|
|
132
|
+
readonly fiftyTwoWeekHigh?: number;
|
|
133
|
+
/** 52 周最低价。 */
|
|
134
|
+
readonly fiftyTwoWeekLow?: number;
|
|
135
|
+
/** 快照时间(epoch ms)。 */
|
|
136
|
+
readonly timestamp: number;
|
|
137
|
+
}
|
|
138
|
+
/** 盘口档位(价格 + 挂单量,量单位=标的基准单位:股票股、crypto 币)。 */
|
|
139
|
+
interface OrderbookLevel {
|
|
140
|
+
readonly price: number;
|
|
141
|
+
readonly amount: number;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 盘口快照(GUI「盘口」竖栏用,issue #39)。实现保证:bids 按价格降序(买一在前)、
|
|
145
|
+
* asks 按价格升序(卖一在前),档位数由数据源决定(沪深五档、crypto 常见 5~20 档)。
|
|
146
|
+
*/
|
|
147
|
+
interface Orderbook {
|
|
148
|
+
readonly symbol: string;
|
|
149
|
+
readonly bids: readonly OrderbookLevel[];
|
|
150
|
+
readonly asks: readonly OrderbookLevel[];
|
|
151
|
+
/** 快照时间(epoch ms)。 */
|
|
152
|
+
readonly timestamp: number;
|
|
153
|
+
}
|
|
154
|
+
/** 逐笔成交(taker 视角:side 为主动方;流水单条)。 */
|
|
155
|
+
interface TradeTick {
|
|
156
|
+
/** 交易所成交 id(字符串透传,排序稳定性由时间戳保证)。 */
|
|
157
|
+
readonly id: string;
|
|
158
|
+
readonly symbol: string;
|
|
159
|
+
readonly price: number;
|
|
160
|
+
/** 成交量(基准单位,同 OrderbookLevel.amount)。 */
|
|
161
|
+
readonly amount: number;
|
|
162
|
+
/** 主动方向:buy=主动买(外盘)、sell=主动卖(内盘);数据源缺方向时 unknown。 */
|
|
163
|
+
readonly side: 'buy' | 'sell' | 'unknown';
|
|
164
|
+
readonly timestamp: number;
|
|
165
|
+
}
|
|
166
|
+
type PositionSide = 'long' | 'short';
|
|
167
|
+
/** 持仓快照。 */
|
|
168
|
+
interface Position {
|
|
169
|
+
readonly symbol: string;
|
|
170
|
+
readonly side: PositionSide;
|
|
171
|
+
/** 仓位数量(正数;方向由 side 表达)。 */
|
|
172
|
+
readonly size: number;
|
|
173
|
+
readonly entryPrice: number;
|
|
174
|
+
readonly markPrice?: number;
|
|
175
|
+
readonly unrealizedPnl?: number;
|
|
176
|
+
readonly leverage?: number;
|
|
177
|
+
readonly timestamp: number;
|
|
178
|
+
}
|
|
179
|
+
type OrderSide = 'buy' | 'sell';
|
|
180
|
+
type OrderType = 'limit' | 'market';
|
|
181
|
+
type OrderStatus = 'new' | 'partially_filled' | 'filled' | 'canceled' | 'rejected' | 'expired';
|
|
182
|
+
/** 下单请求(安全闸门:dryRun 缺省视为 true)。 */
|
|
183
|
+
interface OrderRequest {
|
|
184
|
+
readonly symbol: string;
|
|
185
|
+
readonly side: OrderSide;
|
|
186
|
+
readonly type: OrderType;
|
|
187
|
+
readonly quantity: number;
|
|
188
|
+
/** limit 单必填。 */
|
|
189
|
+
readonly price?: number;
|
|
190
|
+
/** 缺省/true 时仅模拟,不触碰交易所。实盘还受插件 liveTrading 闸门与 approval 约束 [S4]。 */
|
|
191
|
+
readonly dryRun?: boolean;
|
|
192
|
+
}
|
|
193
|
+
/** 订单回执/状态。 */
|
|
194
|
+
interface Order {
|
|
195
|
+
readonly id: string;
|
|
196
|
+
readonly symbol: string;
|
|
197
|
+
readonly side: OrderSide;
|
|
198
|
+
readonly type: OrderType;
|
|
199
|
+
readonly status: OrderStatus;
|
|
200
|
+
readonly price?: number;
|
|
201
|
+
readonly quantity: number;
|
|
202
|
+
readonly filledQuantity?: number;
|
|
203
|
+
/** 本次订单是否为模拟单(回执必须显式回带,防 dry-run 语义丢失)。 */
|
|
204
|
+
readonly dryRun: boolean;
|
|
205
|
+
readonly timestamp: number;
|
|
206
|
+
}
|
|
207
|
+
interface AccountBalance {
|
|
208
|
+
readonly asset: string;
|
|
209
|
+
readonly free: number;
|
|
210
|
+
readonly locked: number;
|
|
211
|
+
}
|
|
212
|
+
/** 成交流水单条(GUI 交易台「成交历史」用,issue #40)。 */
|
|
213
|
+
interface TradeFill {
|
|
214
|
+
/** 交易所成交 id。 */
|
|
215
|
+
readonly id: string;
|
|
216
|
+
readonly symbol: string;
|
|
217
|
+
readonly side: OrderSide;
|
|
218
|
+
readonly price: number;
|
|
219
|
+
/** 成交量(base 币数)。 */
|
|
220
|
+
readonly amount: number;
|
|
221
|
+
/** 手续费(绝对值,币种由 feeAsset 表达)。 */
|
|
222
|
+
readonly fee?: number;
|
|
223
|
+
readonly feeAsset?: string;
|
|
224
|
+
readonly timestamp: number;
|
|
225
|
+
}
|
|
226
|
+
/** 订阅句柄:dispose 即退订(连接器用 ctx.effect/cordis 生命周期托管)。 */
|
|
227
|
+
interface Disposable {
|
|
228
|
+
dispose(): void;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* 行情服务契约:由市场连接器实现,注册到按市场命名空间的 ctx 键(如 ctx.tradingCrypto)。
|
|
232
|
+
* 符号词汇(2026-08-31 规范,docs/symbol-vocabulary.md):入参接受市场规范形与连接器原生形,
|
|
233
|
+
* 输出 `symbol` 一律市场规范形——消费方(GUI/Agent/工作流)与数据源方言解耦。
|
|
234
|
+
*/
|
|
235
|
+
interface MarketDataService {
|
|
236
|
+
getTicker(symbol: string): Promise<Ticker>;
|
|
237
|
+
getKlines(symbol: string, interval: Interval, limit?: number): Promise<Kline[]>;
|
|
238
|
+
subscribeTicker(symbol: string, cb: (ticker: Ticker) => void): Disposable;
|
|
239
|
+
/**
|
|
240
|
+
* 查询本市场/交易所支持的全部标的名册(动态全集,Issue #15)。
|
|
241
|
+
* 输出 `symbol` 一律市场规范词汇(docs/symbol-vocabulary.md)。
|
|
242
|
+
* 可选方法:无公开全集端点的数据源(如 tencent/yahoo/stooq)可缺省或由桥/前端回退。
|
|
243
|
+
*/
|
|
244
|
+
listInstruments?(): Promise<Array<{
|
|
245
|
+
symbol: string;
|
|
246
|
+
name?: string;
|
|
247
|
+
}>>;
|
|
248
|
+
/**
|
|
249
|
+
* 标的基本面与估值快照(GUI「基本面」页签用,2026-09-02)。
|
|
250
|
+
* 可选方法:仅当数据源在同一公共端点里携带基本面字段时实现
|
|
251
|
+
* (腾讯行情行 cn/hk 已实现);未实现的市场由消费方降级为派生数据(日K 52 周高低)。
|
|
252
|
+
* 输出 `symbol` 一律市场规范词汇(docs/symbol-vocabulary.md)。
|
|
253
|
+
*/
|
|
254
|
+
getFundamentals?(symbol: string): Promise<StockFundamentals>;
|
|
255
|
+
/**
|
|
256
|
+
* 衍生品市场指标快照(GUI「衍生品」面板用,issue #38,2026-09-02):持仓量/
|
|
257
|
+
* 多空比/资金费等微观结构数据,crypto 永续合约市场专属。
|
|
258
|
+
* 可选方法:现货/股票数据源不实现;未实现时消费方直接隐藏面板(不降级不报错)。
|
|
259
|
+
* 入参接受规范形与连接器原生形,输出 `symbol` 一律规范词汇 SWAP 形(BTCUSDT-SWAP)。
|
|
260
|
+
*/
|
|
261
|
+
getDerivatives?(symbol: string): Promise<DerivativesData>;
|
|
262
|
+
/**
|
|
263
|
+
* 衍生品历史序列(GUI「衍生品」页签趋势卡用,issue #54):资金费率历史 +
|
|
264
|
+
* OI 历史,时间升序。可选方法:无公开历史端点的数据源不实现;未实现或失败
|
|
265
|
+
* 时消费方隐藏趋势图、保留快照读数(与快照同族的降级纪律)。
|
|
266
|
+
* 入参接受规范形与连接器原生形,输出 symbol 一律规范词汇 SWAP 形。
|
|
267
|
+
*/
|
|
268
|
+
getDerivativesHistory?(symbol: string): Promise<DerivativesHistory>;
|
|
269
|
+
/**
|
|
270
|
+
* 盘口快照(GUI「盘口」竖栏用,issue #39):档位词汇见 Orderbook。可选方法:
|
|
271
|
+
* 数据源无盘口能力时不实现(如 stooq/yahoo、腾讯 r_hk 港股行档位全 0)。
|
|
272
|
+
* 入参接受规范形与连接器原生形,输出 `symbol` 一律市场规范词汇。
|
|
273
|
+
*/
|
|
274
|
+
getOrderbook?(symbol: string): Promise<Orderbook>;
|
|
275
|
+
/**
|
|
276
|
+
* 最近逐笔成交流水(GUI「分笔」用,issue #39):取最近 limit 笔(缺省 ≤50),
|
|
277
|
+
* **时间升序(旧→新)**,与 K 线序列同向;方向缺省的数据源 side='unknown'。
|
|
278
|
+
* 可选方法:无公共逐笔端点的数据源(腾讯沪深行情行)不实现。
|
|
279
|
+
*/
|
|
280
|
+
getRecentTrades?(symbol: string, limit?: number): Promise<TradeTick[]>;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* 交易服务契约:placeOrder 默认 dry-run;实盘前必须过插件 liveTrading 开关与
|
|
284
|
+
* ctx.approval.request(交互形态;headless 下 ask=deny,fail-closed [S4])。
|
|
285
|
+
*
|
|
286
|
+
* R3(okx 切片 2026-08-29)修订:cancelOrder 增加可选 symbol、新增 getOrder——
|
|
287
|
+
* OKX 按 (instId, ordId) 双键定位订单,单参 id 形态不够;无其他实现方,扩展向后兼容。
|
|
288
|
+
*/
|
|
289
|
+
interface TradeService {
|
|
290
|
+
placeOrder(req: OrderRequest): Promise<Order>;
|
|
291
|
+
/** 撤单。symbol 可选:按 (symbol, id) 双键定位订单的交易所(如 OKX)必须提供。 */
|
|
292
|
+
cancelOrder(id: string, symbol?: string): Promise<void>;
|
|
293
|
+
/** 查询单笔订单状态(按 (symbol, id) 双键)。 */
|
|
294
|
+
getOrder(symbol: string, id: string): Promise<Order>;
|
|
295
|
+
getPositions(): Promise<Position[]>;
|
|
296
|
+
/**
|
|
297
|
+
* 账户余额快照(issue #40 GUI 交易台;只读,需凭证)。
|
|
298
|
+
* 可选方法:实现方已有只读余额面时实现(connector-okx 的 crypto_get_balance 同源)。
|
|
299
|
+
*/
|
|
300
|
+
getBalances?(): Promise<AccountBalance[]>;
|
|
301
|
+
/**
|
|
302
|
+
* 当前挂单列表(issue #40 GUI 交易台;只读,需凭证)。
|
|
303
|
+
* 可选方法:无批量挂单端点的实现方可缺省(GUI 隐藏挂单区)。
|
|
304
|
+
* 输出 `symbol` 一律市场规范词汇;status 只含 new / partially_filled。
|
|
305
|
+
*/
|
|
306
|
+
listOpenOrders?(symbol?: string): Promise<Order[]>;
|
|
307
|
+
/**
|
|
308
|
+
* 最近成交流水(issue #40 GUI 交易台;只读,需凭证)。
|
|
309
|
+
* 可选方法:时间升序(旧→新),最多 limit 条(缺省 ≤50)。
|
|
310
|
+
*/
|
|
311
|
+
listTradeFills?(symbol?: string, limit?: number): Promise<TradeFill[]>;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Cordis Context 服务键(能力三角色之「声明」):市场命名空间键由契约包统一声明,
|
|
315
|
+
* 连接器 provide、消费方 inject 时获得完整类型。此处仅类型增强——本包保持
|
|
316
|
+
* 零运行时依赖,不产生任何 JS 输出。
|
|
317
|
+
*/
|
|
318
|
+
declare module '@deepseek-ai/cordis' {
|
|
319
|
+
interface Context {
|
|
320
|
+
/** crypto 市场行情服务(由 Binance 连接器以公共 REST 提供,无需凭证)。 */
|
|
321
|
+
tradingCryptoMarketData: MarketDataService;
|
|
322
|
+
/** us 市场行情服务(由 Stooq 连接器以公共 CSV 端点提供,无需凭证;us 复制 2026-08-31 补齐)。 */
|
|
323
|
+
tradingUsMarketData: MarketDataService;
|
|
324
|
+
/** cn 市场行情服务(由腾讯连接器以公共端点提供,无需凭证;cn+hk 切片 2026-08-31 补齐)。 */
|
|
325
|
+
tradingCnMarketData: MarketDataService;
|
|
326
|
+
/** hk 市场行情服务(由腾讯连接器同包双实例提供,config.market 分流;cn+hk 切片 2026-08-31 补齐)。 */
|
|
327
|
+
tradingHkMarketData: MarketDataService;
|
|
328
|
+
/**
|
|
329
|
+
* crypto 市场交易服务(R3 2026-08-29 补齐,crypto 市场第一个真实 TradeService):
|
|
330
|
+
* 由 connector-okx 实现(签名 demo/live 下单),与 connector-binance 经
|
|
331
|
+
* Config.enabled 互斥激活同一 tradingCryptoMarketData 键时一并 provide。
|
|
332
|
+
*/
|
|
333
|
+
tradingCryptoTrade: TradeService;
|
|
334
|
+
/**
|
|
335
|
+
* 交易服务注册表(issue #40 GUI 交易台,@dshtrading/api 类型声明):
|
|
336
|
+
* 与 tradingMarketDataRegistry 同构的宿主平面注册面——交易连接器 host 面数据行
|
|
337
|
+
* 注册,GUI 桥按路由当前值惰性解析。**注册不改变安全语义**:placeOrder 的
|
|
338
|
+
* 服务缝闸门(dryRun 缺省 true + liveTrading 显式开关)随服务实例生效;
|
|
339
|
+
* GUI 桥只放行 dry-run 下单与只读查询,实盘路径仍走 Agent 工具的 base 审批闸门。
|
|
340
|
+
*/
|
|
341
|
+
tradingTradeRegistry: TradeRegistry;
|
|
342
|
+
/**
|
|
343
|
+
* 市场路由服务(R5 2026-08-29 补齐,@dshtrading/router 提供):
|
|
344
|
+
* 连接器 apply 时 consult activeProvider(market) 决定是否激活——用户设置
|
|
345
|
+
* dshtrading.markets.<market>.provider 选谁谁激活(docs/exchange-routing.md)。
|
|
346
|
+
*/
|
|
347
|
+
tradingMarketRouter: MarketRouterService;
|
|
348
|
+
/**
|
|
349
|
+
* 行情服务注册表(2026-08-30 注册表模式定稿,@dshtrading/router 同插件提供):
|
|
350
|
+
* 连接器 host 面数据行注册,GUI 行情桥按路由当前值惰性解析(热切换)。
|
|
351
|
+
*/
|
|
352
|
+
tradingMarketDataRegistry: MarketDataRegistry;
|
|
353
|
+
/**
|
|
354
|
+
* 新闻聚合器注册表(Issue #37,@dshtrading/router 同插件提供):
|
|
355
|
+
* 各市场 Kit apply 时注册 aggregateNews 纯函数,GUI 行情桥按市场获取。
|
|
356
|
+
*/
|
|
357
|
+
tradingNewsRegistry: TradingNewsRegistry;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* 行情服务注册表契约(router 插件提供,2026-08-30 注册表模式定稿):
|
|
362
|
+
* 连接器 host 面数据行不再互斥式 provide 市场键,而是全部注册进本注册表;
|
|
363
|
+
* 消费方(GUI 行情桥)经 active() 按路由当前值惰性解析——settings 变更即刻生效
|
|
364
|
+
* (GUI 热切换),无 watch、无进程重启。preset 平面不走注册表:会话内数据源
|
|
365
|
+
* 一致性是有意语义,切交易所对会话 = 新建会话生效(docs/exchange-routing.md §2.2)。
|
|
366
|
+
*
|
|
367
|
+
* 与 tradeProvider 预留的衔接:本注册表只承载 MarketDataService;数据/交易分离
|
|
368
|
+
* 落地时 TradeService 走独立注册面(不复用本键),铁律 #4 到时再抽象。
|
|
369
|
+
*/
|
|
370
|
+
interface MarketDataRegistration {
|
|
371
|
+
/** 市场 slug(crypto/us/cn/hk;开放词汇,新市场 = 新键)。 */
|
|
372
|
+
readonly market: string;
|
|
373
|
+
/** 提供者 slug(binance/okx/…;开放词汇,第三方连接器可注册新 slug)。 */
|
|
374
|
+
readonly provider: string;
|
|
375
|
+
readonly service: MarketDataService;
|
|
376
|
+
}
|
|
377
|
+
interface MarketDataRegistry {
|
|
378
|
+
/**
|
|
379
|
+
* 注册一个市场的某 provider 行情服务;同 (market, provider) 重复注册抛错
|
|
380
|
+
* (配置错误必须响亮)。返回注销函数(调用方包进 ctx.effect 随 fiber 注销)。
|
|
381
|
+
*/
|
|
382
|
+
register(market: string, provider: string, service: MarketDataService): () => void;
|
|
383
|
+
/**
|
|
384
|
+
* 路由裁决后的当前激活注册项:router 选中的 provider 已注册 → 返回之;
|
|
385
|
+
* 选中了但未注册(包未装/enabled=false)→ undefined(调用方面向用户报错,
|
|
386
|
+
* 不静默降级到别家——用户设置是权威);router 无该市场路由(未知市场键)
|
|
387
|
+
* 且恰好一个注册项 → 返回之(新市场零配置可用);否则 undefined。
|
|
388
|
+
*/
|
|
389
|
+
active(market: string): MarketDataRegistration | undefined;
|
|
390
|
+
/** 某市场全部注册项(诊断/设置 UI 展示用)。 */
|
|
391
|
+
list(market: string): readonly MarketDataRegistration[];
|
|
392
|
+
}
|
|
393
|
+
/** 交易服务注册项(tradingTradeRegistry 的条目,issue #40)。 */
|
|
394
|
+
interface TradeRegistration {
|
|
395
|
+
readonly market: string;
|
|
396
|
+
readonly provider: string;
|
|
397
|
+
readonly service: TradeService;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* 交易服务注册表(router 插件同款注册模式,issue #40):交易连接器在 host 面
|
|
401
|
+
* 数据行注册(凭证经 ctx.credentials / 环境变量解析,缺失时只读方法 fail-closed
|
|
402
|
+
* 报 TRADING_CREDENTIALS_MISSING);注册面本身不做安全裁决——闸门在服务缝
|
|
403
|
+
* (placeOrder 三态)与桥层(GUI 只放行 dry-run)。
|
|
404
|
+
*/
|
|
405
|
+
interface TradeRegistry {
|
|
406
|
+
register(market: string, provider: string, service: TradeService): () => void;
|
|
407
|
+
active(market: string): TradeRegistration | undefined;
|
|
408
|
+
list(market: string): readonly TradeRegistration[];
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* 市场路由服务契约(router 插件提供):按市场查询当前激活的数据/交易所提供方。
|
|
412
|
+
* 用户设置(dshtrading namespace,settings.yaml)是权威;无设置时 = 组合默认值
|
|
413
|
+
* (现状零变化)。供应商取值与连接器的 provider slug 比对,相符者激活。
|
|
414
|
+
*/
|
|
415
|
+
interface MarketRouterService {
|
|
416
|
+
/** 某市场当前激活的 provider slug(settings resolved:用户层赢,缺省 base 默认)。 */
|
|
417
|
+
activeProvider(market: string): string | undefined;
|
|
418
|
+
/** 订阅激活变化(settings commit 驱动)。 */
|
|
419
|
+
watch(cb: (next: string | undefined, prev: string | undefined) => void): () => void;
|
|
420
|
+
}
|
|
421
|
+
/** 新闻/快讯条目(各市场 Kit 统一输出形状)。 */
|
|
422
|
+
interface NewsItem {
|
|
423
|
+
/** 来源标识(如 'eastmoney'、'yahoo'、'googlenews'、'binance'、'coindesk' 等)。 */
|
|
424
|
+
readonly source: string;
|
|
425
|
+
/** 新闻/快讯标题。 */
|
|
426
|
+
readonly title: string;
|
|
427
|
+
/** 详情页 URL。 */
|
|
428
|
+
readonly url: string;
|
|
429
|
+
/** ISO 8601 发布时间。 */
|
|
430
|
+
readonly publishedAt: string;
|
|
431
|
+
/** 关联标的代码(可选,格式随源不同)。 */
|
|
432
|
+
readonly relatedCodes?: readonly string[];
|
|
433
|
+
}
|
|
434
|
+
/** 新闻聚合请求选项。 */
|
|
435
|
+
interface AggregateNewsOptions {
|
|
436
|
+
/** 输出条数上限(1~50,默认 20)。 */
|
|
437
|
+
limit?: number | undefined;
|
|
438
|
+
/** 时间窗口(小时,1~168,默认 24)。 */
|
|
439
|
+
windowHours?: number | undefined;
|
|
440
|
+
/** 按标的代码过滤(可选)。 */
|
|
441
|
+
symbol?: string | undefined;
|
|
442
|
+
/** CryptoPanic API token(仅 crypto 市场使用,可选)。 */
|
|
443
|
+
cryptoPanicKey?: string | undefined;
|
|
444
|
+
}
|
|
445
|
+
/** 新闻聚合结果。 */
|
|
446
|
+
interface AggregateNewsResult {
|
|
447
|
+
/** 按发布时间倒序的新闻条目。 */
|
|
448
|
+
readonly items: readonly NewsItem[];
|
|
449
|
+
/** 失败的数据源名称(fail-soft 容错:可用源正常返回,不可用源记录在此)。 */
|
|
450
|
+
readonly unavailable: readonly string[];
|
|
451
|
+
}
|
|
452
|
+
/** 新闻聚合器函数签名(各 Kit 导出的 aggregateNews 符合此形状)。 */
|
|
453
|
+
type NewsAggregator = (options?: AggregateNewsOptions) => Promise<AggregateNewsResult>;
|
|
454
|
+
/** 新闻聚合器注册表契约(Issue #37,router 插件提供)。 */
|
|
455
|
+
interface TradingNewsRegistry {
|
|
456
|
+
register(market: string, aggregator: NewsAggregator): () => void;
|
|
457
|
+
get(market: string): NewsAggregator | undefined;
|
|
458
|
+
markets(): string[];
|
|
459
|
+
}
|
|
460
|
+
//#endregion
|
|
461
|
+
export { AccountBalance, AggregateNewsOptions, AggregateNewsResult, DerivativesData, DerivativesHistory, DerivativesPoint, Disposable, Interval, Kline, MarketDataRegistration, MarketDataRegistry, MarketDataService, MarketRouterService, NewsAggregator, NewsItem, Order, OrderRequest, OrderSide, OrderStatus, OrderType, Orderbook, OrderbookLevel, Position, PositionSide, StockFundamentals, Ticker, TradeFill, TradeRegistration, TradeRegistry, TradeService, TradeTick, TradingNewsRegistry };
|
package/lib/custom-fs.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { dirname } from "node:path";
|
|
2
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
//#region src/custom-fs.ts
|
|
4
|
+
/**
|
|
5
|
+
* 文件持久化版自定义策略存储(Node.js 宿主侧使用,落 ~/.dsh/strategies/custom.json)。
|
|
6
|
+
*
|
|
7
|
+
* 与 indicators/src/custom-fs.ts 同款 tmp + rename 原子写入模式(issue #31 规格)。
|
|
8
|
+
*/
|
|
9
|
+
function createFileCustomStrategyStore(filePath) {
|
|
10
|
+
let cache = null;
|
|
11
|
+
async function load() {
|
|
12
|
+
if (cache !== null) return cache;
|
|
13
|
+
try {
|
|
14
|
+
const content = await readFile(filePath, "utf8");
|
|
15
|
+
const parsed = JSON.parse(content);
|
|
16
|
+
const map = /* @__PURE__ */ new Map();
|
|
17
|
+
if (Array.isArray(parsed)) {
|
|
18
|
+
for (const item of parsed) if (item && typeof item.id === "string") map.set(item.id, item);
|
|
19
|
+
}
|
|
20
|
+
cache = map;
|
|
21
|
+
return map;
|
|
22
|
+
} catch (err) {
|
|
23
|
+
if (err?.code !== "ENOENT") console.error(`[dsh-trading/strategies] failed to read custom strategies from ${filePath}:`, err);
|
|
24
|
+
cache = /* @__PURE__ */ new Map();
|
|
25
|
+
return cache;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function flush(map) {
|
|
29
|
+
const dir = dirname(filePath);
|
|
30
|
+
const tmpPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
31
|
+
const data = JSON.stringify([...map.values()], null, 2);
|
|
32
|
+
try {
|
|
33
|
+
await mkdir(dir, { recursive: true });
|
|
34
|
+
await writeFile(tmpPath, data, "utf8");
|
|
35
|
+
let lastError;
|
|
36
|
+
for (let attempt = 0; attempt < 3; attempt++) try {
|
|
37
|
+
await rename(tmpPath, filePath);
|
|
38
|
+
return;
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err?.code !== "EPERM" && err?.code !== "EBUSY") throw err;
|
|
41
|
+
lastError = err;
|
|
42
|
+
await new Promise((resolve) => setTimeout(resolve, 25 * (attempt + 1)));
|
|
43
|
+
}
|
|
44
|
+
throw lastError;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`[dsh-trading/strategies] failed to atomic flush custom strategies to ${filePath}:`, error);
|
|
47
|
+
await unlink(tmpPath).catch(() => {});
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
async list() {
|
|
53
|
+
return [...(await load()).values()];
|
|
54
|
+
},
|
|
55
|
+
async get(id) {
|
|
56
|
+
return (await load()).get(id);
|
|
57
|
+
},
|
|
58
|
+
async save(record) {
|
|
59
|
+
const map = await load();
|
|
60
|
+
map.set(record.id, { ...record });
|
|
61
|
+
await flush(map);
|
|
62
|
+
},
|
|
63
|
+
async remove(id) {
|
|
64
|
+
const map = await load();
|
|
65
|
+
const existed = map.delete(id);
|
|
66
|
+
if (existed) await flush(map);
|
|
67
|
+
return existed;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
export { createFileCustomStrategyStore };
|
package/lib/custom.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { StrategyHorizon } from "./types.js";
|
|
2
|
+
//#region src/custom.d.ts
|
|
3
|
+
interface CustomStrategyRecord {
|
|
4
|
+
/** 策略唯一 ID,如 'ema-stop-takeprofit'(校验器保留 6 大范式 id) */
|
|
5
|
+
id: string;
|
|
6
|
+
/** 策略展示名,如 '双均线止损止盈' */
|
|
7
|
+
title: string;
|
|
8
|
+
/** 策略期限词汇:'short' 短线 | 'swing' 波段 | 'long' 长线 */
|
|
9
|
+
horizon: StrategyHorizon;
|
|
10
|
+
/** 一句话思路(UI 名册与对话卡片展示) */
|
|
11
|
+
summary: string;
|
|
12
|
+
/** 参数规格数组(StrategyParamSpec[])的 JSON 字符串 */
|
|
13
|
+
paramsJson: string;
|
|
14
|
+
/** JavaScript 纯函数源码,接收 (bars, params) 返回 StrategySignal[] */
|
|
15
|
+
computeSource: string;
|
|
16
|
+
/** 创建时间戳 */
|
|
17
|
+
createdAt: number;
|
|
18
|
+
}
|
|
19
|
+
interface CustomStrategyStore {
|
|
20
|
+
list(): Promise<CustomStrategyRecord[]>;
|
|
21
|
+
get(id: string): Promise<CustomStrategyRecord | undefined>;
|
|
22
|
+
save(record: CustomStrategyRecord): Promise<void>;
|
|
23
|
+
remove(id: string): Promise<boolean>;
|
|
24
|
+
}
|
|
25
|
+
/** 内存版自定义策略存储(纯浏览器与单测用)。 */
|
|
26
|
+
declare function createMemoryCustomStrategyStore(initial?: CustomStrategyRecord[]): CustomStrategyStore;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { CustomStrategyRecord, CustomStrategyStore, createMemoryCustomStrategyStore };
|
package/lib/custom.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/custom.ts
|
|
2
|
+
/** 内存版自定义策略存储(纯浏览器与单测用)。 */
|
|
3
|
+
function createMemoryCustomStrategyStore(initial = []) {
|
|
4
|
+
const map = /* @__PURE__ */ new Map();
|
|
5
|
+
for (const item of initial) map.set(item.id, item);
|
|
6
|
+
return {
|
|
7
|
+
list: async () => [...map.values()],
|
|
8
|
+
get: async (id) => map.get(id),
|
|
9
|
+
save: async (record) => {
|
|
10
|
+
map.set(record.id, { ...record });
|
|
11
|
+
},
|
|
12
|
+
remove: async (id) => map.delete(id)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { createMemoryCustomStrategyStore };
|
package/lib/engine.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { BacktestOptions, BacktestResult, StrategyDefinition } from "./types.js";
|
|
2
|
+
import { Kline } from "@dshtrading/indicators";
|
|
3
|
+
//#region src/engine.d.ts
|
|
4
|
+
declare function run(bars: readonly Kline[], strategy: StrategyDefinition, paramsOverride?: Record<string, number>, options?: BacktestOptions): BacktestResult;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { run };
|