@vibek/strategy 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/index.d.ts +255 -0
- package/index.js +2 -0
- package/package.json +34 -0
- package/testing.d.ts +348 -0
- package/testing.js +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vibek contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @vibek/strategy
|
|
2
|
+
|
|
3
|
+
Vibek 的公开 Strategy SDK 与 Bun Testing helper。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
Strategy SDK 与 `@vibek/cli` 使用同一版本线。策略工程必须精确依赖与所用 CLI 相同的版本,避免编译期契约和运行时不一致:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add --exact @vibek/strategy@0.5.4
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
当前包版本为 `0.5.4`;对应 CLI 应为 `@vibek/cli@0.5.4`。
|
|
14
|
+
|
|
15
|
+
## 使用
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { CtaTemplate, defineStrategy, type Bar } from '@vibek/strategy';
|
|
19
|
+
|
|
20
|
+
class MyStrategy extends CtaTemplate {
|
|
21
|
+
override onInit(): void {}
|
|
22
|
+
|
|
23
|
+
override onBar(_bar: Bar): void {
|
|
24
|
+
if (!this.trading) return;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default defineStrategy({ strategy: MyStrategy });
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Bun 测试从独立入口导入:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { setupStrategyTests } from '@vibek/strategy/testing';
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
生产策略只应从 `@vibek/strategy` 导入,不应依赖其他内部 `@vibek/*` 包。Testing helper 仅用于 Bun 测试,不应从生产策略入口导入。
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT License, Copyright (c) 2026 Vibek contributors.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/** Public declarations shipped with the standalone strategy package. */
|
|
2
|
+
export interface Bar {
|
|
3
|
+
code: string;
|
|
4
|
+
timeframe: string;
|
|
5
|
+
barTime: number;
|
|
6
|
+
open: number;
|
|
7
|
+
high: number;
|
|
8
|
+
low: number;
|
|
9
|
+
close: number;
|
|
10
|
+
volume: number;
|
|
11
|
+
tradingDay?: string;
|
|
12
|
+
boundaryReason?: 'break' | 'sessionEnd' | 'disconnect';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TickData {
|
|
16
|
+
code: string;
|
|
17
|
+
exchangeID?: string;
|
|
18
|
+
productID?: string;
|
|
19
|
+
priceTick?: number;
|
|
20
|
+
lastPrice: number;
|
|
21
|
+
updateTime: string;
|
|
22
|
+
updateMillisec: number;
|
|
23
|
+
actionDay?: string;
|
|
24
|
+
rawActionDay?: string;
|
|
25
|
+
tradingDay?: string;
|
|
26
|
+
volume: number;
|
|
27
|
+
datetime?: number;
|
|
28
|
+
receivedAt?: number;
|
|
29
|
+
dateSource?: 'actionDay' | 'receivedAt';
|
|
30
|
+
tradingDaySource?: 'quote' | 'mdLogin';
|
|
31
|
+
bidPrice1?: number;
|
|
32
|
+
bidVolume1?: number;
|
|
33
|
+
askPrice1?: number;
|
|
34
|
+
askVolume1?: number;
|
|
35
|
+
upperLimitPrice?: number;
|
|
36
|
+
lowerLimitPrice?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type EngineType = 'paper' | 'live';
|
|
40
|
+
export type OrderStatus =
|
|
41
|
+
| 'submitting'
|
|
42
|
+
| 'notTraded'
|
|
43
|
+
| 'partTraded'
|
|
44
|
+
| 'allTraded'
|
|
45
|
+
| 'cancelled'
|
|
46
|
+
| 'rejected';
|
|
47
|
+
export type OffsetFlag = 'open' | 'close' | 'closeToday' | 'closeYesterday';
|
|
48
|
+
export type OrderFailureCode =
|
|
49
|
+
| 'invalidRequest'
|
|
50
|
+
| 'maxOrderVolume'
|
|
51
|
+
| 'maxPosition'
|
|
52
|
+
| 'maxActiveOpenOrders'
|
|
53
|
+
| 'insufficientPosition'
|
|
54
|
+
| 'riskRejected'
|
|
55
|
+
| 'submitFailed'
|
|
56
|
+
| 'paperRejected';
|
|
57
|
+
|
|
58
|
+
export interface OrderData {
|
|
59
|
+
/** Normal order lifecycle event. Emitted events set this to 'order'. */
|
|
60
|
+
type?: 'order';
|
|
61
|
+
vtOrderId: string;
|
|
62
|
+
vtSymbol: string;
|
|
63
|
+
direction: 'long' | 'short';
|
|
64
|
+
offset: OffsetFlag;
|
|
65
|
+
price: number;
|
|
66
|
+
volume: number;
|
|
67
|
+
traded: number;
|
|
68
|
+
status: OrderStatus;
|
|
69
|
+
statusMessage: string;
|
|
70
|
+
datetime: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface OrderErrorData {
|
|
74
|
+
type: 'error';
|
|
75
|
+
stage: 'submit';
|
|
76
|
+
code: OrderFailureCode;
|
|
77
|
+
vtSymbol: string;
|
|
78
|
+
direction: 'long' | 'short';
|
|
79
|
+
offset: OffsetFlag;
|
|
80
|
+
price: number;
|
|
81
|
+
volume: number;
|
|
82
|
+
message: string;
|
|
83
|
+
datetime: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type OrderEvent = OrderData | OrderErrorData;
|
|
87
|
+
|
|
88
|
+
export interface TimerEvent {
|
|
89
|
+
readonly type: 'timer';
|
|
90
|
+
readonly now: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface TradesEvent {
|
|
94
|
+
readonly type: 'trades';
|
|
95
|
+
readonly now: number;
|
|
96
|
+
readonly trade: Readonly<TradeData>;
|
|
97
|
+
readonly order: (Readonly<OrderData> & { readonly origin: 'strategy' | 'external' }) | null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type ClaimOrderResult = { readonly ok: true } | { readonly ok: false; readonly reason: string };
|
|
101
|
+
|
|
102
|
+
export type StrategyHookValue =
|
|
103
|
+
| null
|
|
104
|
+
| string
|
|
105
|
+
| number
|
|
106
|
+
| boolean
|
|
107
|
+
| readonly StrategyHookValue[]
|
|
108
|
+
| { readonly [key: string]: StrategyHookValue };
|
|
109
|
+
|
|
110
|
+
export type StrategyHookData = Readonly<Record<string, StrategyHookValue>>;
|
|
111
|
+
|
|
112
|
+
export interface StrategyHook {
|
|
113
|
+
readonly name: string;
|
|
114
|
+
readonly strategyId: string;
|
|
115
|
+
readonly mode: EngineType;
|
|
116
|
+
readonly vtSymbol: string;
|
|
117
|
+
readonly occurredAt: number;
|
|
118
|
+
readonly data: StrategyHookData;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export type StrategyEvent = TimerEvent | TradesEvent;
|
|
122
|
+
export type StrategyEventHandler<T extends StrategyEvent['type'] = StrategyEvent['type']> =
|
|
123
|
+
(event: Extract<StrategyEvent, { type: T }>) => void;
|
|
124
|
+
|
|
125
|
+
export interface TradeData {
|
|
126
|
+
vtTradeId: string;
|
|
127
|
+
vtOrderId: string;
|
|
128
|
+
vtSymbol: string;
|
|
129
|
+
direction: 'long' | 'short';
|
|
130
|
+
offset: OffsetFlag;
|
|
131
|
+
price: number;
|
|
132
|
+
volume: number;
|
|
133
|
+
datetime: number;
|
|
134
|
+
tradingDay: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface ContractData {
|
|
138
|
+
readonly vtSymbol: string;
|
|
139
|
+
readonly instrumentID: string;
|
|
140
|
+
readonly exchangeID: string;
|
|
141
|
+
readonly productID: string;
|
|
142
|
+
readonly name: string;
|
|
143
|
+
readonly priceTick: number;
|
|
144
|
+
readonly size: number;
|
|
145
|
+
readonly minOrderVolume: number;
|
|
146
|
+
readonly maxOrderVolume: number;
|
|
147
|
+
readonly longMarginRatio: number;
|
|
148
|
+
readonly shortMarginRatio: number;
|
|
149
|
+
readonly isTrading: boolean;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface CommissionLeg {
|
|
153
|
+
/** Fraction of turnover (price * size * lots). */
|
|
154
|
+
readonly ratioByMoney: number;
|
|
155
|
+
/** Amount per lot, in the contract's currency. */
|
|
156
|
+
readonly perLot: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface CommissionData {
|
|
160
|
+
readonly open: CommissionLeg;
|
|
161
|
+
readonly close: CommissionLeg;
|
|
162
|
+
readonly closeToday: CommissionLeg;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export type StrategyTimeframe =
|
|
166
|
+
| '1min'
|
|
167
|
+
| '3min'
|
|
168
|
+
| '5min'
|
|
169
|
+
| '10min'
|
|
170
|
+
| '15min'
|
|
171
|
+
| '30min'
|
|
172
|
+
| '1h'
|
|
173
|
+
| '2h'
|
|
174
|
+
| '4h'
|
|
175
|
+
| '1d';
|
|
176
|
+
|
|
177
|
+
export interface LoadBarsRequest {
|
|
178
|
+
readonly timeframe: StrategyTimeframe;
|
|
179
|
+
readonly count: number;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export type StrategyVariables = Record<string, unknown>;
|
|
183
|
+
|
|
184
|
+
export abstract class CtaTemplate {
|
|
185
|
+
static readonly author: string;
|
|
186
|
+
static readonly parameters: readonly string[];
|
|
187
|
+
static readonly variables: readonly string[];
|
|
188
|
+
|
|
189
|
+
get strategyName(): string;
|
|
190
|
+
get vtSymbol(): string;
|
|
191
|
+
get mode(): EngineType;
|
|
192
|
+
get inited(): boolean;
|
|
193
|
+
get trading(): boolean;
|
|
194
|
+
get pos(): number;
|
|
195
|
+
|
|
196
|
+
abstract onInit(): void;
|
|
197
|
+
onStart(): void;
|
|
198
|
+
onStop(): void;
|
|
199
|
+
onTick(tick: TickData): void;
|
|
200
|
+
onBar(bar: Bar): void;
|
|
201
|
+
onTrade(trade: TradeData): void;
|
|
202
|
+
onOrder(order: OrderEvent): void;
|
|
203
|
+
registerEvent<T extends StrategyEvent['type']>(eventType: T, handler: StrategyEventHandler<T>): () => void;
|
|
204
|
+
claimOrder(vtOrderId: string): ClaimOrderResult;
|
|
205
|
+
|
|
206
|
+
buy(price: number, volume: number): string[];
|
|
207
|
+
sell(price: number, volume: number): string[];
|
|
208
|
+
short(price: number, volume: number): string[];
|
|
209
|
+
cover(price: number, volume: number): string[];
|
|
210
|
+
cancelOrder(vtOrderId: string): void;
|
|
211
|
+
cancelAll(): void;
|
|
212
|
+
loadBars(request: LoadBarsRequest, callback?: (bar: Bar) => void): void;
|
|
213
|
+
writeLog(message: string): void;
|
|
214
|
+
emitHook(name: string, data?: StrategyHookData): void;
|
|
215
|
+
/** Current runtime clock as epoch milliseconds. */
|
|
216
|
+
getCurrentTime(): number;
|
|
217
|
+
getContract(): ContractData;
|
|
218
|
+
getCommission(): CommissionData;
|
|
219
|
+
/** @deprecated Use getContract().priceTick. */
|
|
220
|
+
getPriceTick(): number;
|
|
221
|
+
/** @deprecated Use getContract().size. */
|
|
222
|
+
getSize(): number;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export type StrategyConstructor = (new () => CtaTemplate) & {
|
|
226
|
+
readonly author?: string;
|
|
227
|
+
readonly parameters?: readonly string[];
|
|
228
|
+
readonly variables?: readonly string[];
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export interface StrategyDefinition<TStrategy extends StrategyConstructor = StrategyConstructor> {
|
|
232
|
+
readonly strategy: TStrategy;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export type StrategyParameterKind = 'number' | 'boolean' | 'string' | 'json';
|
|
236
|
+
|
|
237
|
+
export interface StrategyParameterDescriptor {
|
|
238
|
+
readonly name: string;
|
|
239
|
+
readonly defaultValue: unknown;
|
|
240
|
+
readonly kind: StrategyParameterKind;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function defineStrategy<const TStrategy extends StrategyConstructor>(
|
|
244
|
+
definition: StrategyDefinition<TStrategy>,
|
|
245
|
+
): StrategyDefinition<TStrategy>;
|
|
246
|
+
|
|
247
|
+
export function isActiveOrder(order: Pick<OrderData, 'status'>): boolean;
|
|
248
|
+
export function parseTimeframe(timeframe: string): number;
|
|
249
|
+
|
|
250
|
+
export class BarGenerator {
|
|
251
|
+
constructor(timeframe: StrategyTimeframe);
|
|
252
|
+
pushBar(bar: Bar): Bar | null;
|
|
253
|
+
pushBars(bar: Bar): Bar[];
|
|
254
|
+
flush(boundaryReason?: Bar['boundaryReason']): Bar | null;
|
|
255
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
function p(e){return e.status==="submitting"||e.status==="notTraded"||e.status==="partTraded"}var y=Symbol.for("vibek.strategy.cta-template-runtime");class m{static author="";static parameters=[];static variables=[];#n="";#s="";#l="paper";#a=!1;#t=!1;#m=0;#i=null;#d=!1;#c=Object.freeze({bindContext:(e)=>this.#p(e),restoreVariables:(e)=>this.#y(e),setRuntimeState:(e)=>{if(e.inited!==void 0)this.#a=e.inited;if(e.trading!==void 0)this.#t=e.trading;if(e.pos!==void 0)this.#m=e.pos},getStateVariables:()=>this.#g()});get strategyName(){return this.#n}get vtSymbol(){return this.#s}get mode(){return this.#l}get inited(){return this.#a}get trading(){return this.#t}get pos(){return this.#m}onStart(){}onStop(){}onTick(e){}onBar(e){}onTrade(e){}onOrder(e){}registerEvent(e,t){return this.#e().registerEvent(this,e,t)}claimOrder(e){return this.#e().claimOrder(this,e)}buy(e,t){return this.#r("long","open",e,t)}sell(e,t){return this.#r("short","close",e,t)}short(e,t){return this.#r("short","open",e,t)}cover(e,t){return this.#r("long","close",e,t)}cancelOrder(e){if(this.#t)this.#e().cancelOrder(this,e)}cancelAll(){if(this.#t)this.#e().cancelAll(this)}loadBars(e,t=(r)=>this.onBar(r)){this.#e().loadBars(this,e,t)}writeLog(e){this.#e().writeLog(this,e)}emitHook(e,t={}){if(this.#a)this.#e().emitHook(this,e,t)}getCurrentTime(){return this.#e().getCurrentTime(this)}getContract(){return this.#e().getContract(this)}getCommission(){return this.#e().getCommission(this)}getPriceTick(){return this.getContract().priceTick}getSize(){return this.getContract().size}[y](){return this.#c}#r(e,t,r,n){if(!this.#t)return[];return this.#e().sendOrder(this,e,t,r,n)}#g(){let e={};for(let t of this.#o().variables??[])e[t]=this.#h(t);return e}#p(e){if(this.#d)throw Error("\u7B56\u7565\u5B9E\u4F8B\u5DF2\u7ECF\u7ED1\u5B9A\u8FD0\u884C\u4E0A\u4E0B\u6587");this.#d=!0,this.#i=e.ctaEngine,this.#n=e.strategyName,this.#s=e.vtSymbol,this.#l=e.mode,this.#f(e.setting)}#y(e){let t=new Set(this.#o().variables??[]);for(let[r,n]of Object.entries(e))if(t.has(r))this.#u(r,n)}#f(e){let t=new Set(this.#o().parameters??[]);for(let r of Object.keys(e))if(!t.has(r))throw Error(`\u7B56\u7565 ${this.#n} \u4E0D\u652F\u6301\u53C2\u6570 "${r}"`);for(let r of t)if(Object.hasOwn(e,r))this.#u(r,e[r])}#e(){if(!this.#i)throw Error("\u7B56\u7565\u5C1A\u672A\u7ED1\u5B9A CtaEngine");return this.#i}#o(){return this.constructor}#h(e){return this[e]}#u(e,t){this[e]=t}}function f(e){return e}function h(e){let t=/^(\d+)(min|h|d)$/.exec(e);if(!t)throw Error(`\u4E0D\u652F\u6301\u7684 K \u7EBF\u5468\u671F: ${e}`);let r=Number(t[1]);if(!Number.isInteger(r)||r<=0)throw Error(`\u4E0D\u652F\u6301\u7684 K \u7EBF\u5468\u671F: ${e}`);if(t[2]==="min")return r*60;if(t[2]==="h")return r*3600;return r*86400}var s=["1min","3min","5min","10min","15min","30min","1h","2h","4h","1d"],l={"1min":60,"3min":180,"5min":300,"10min":600,"15min":900,"30min":1800,"1h":3600,"2h":7200,"4h":14400,"1d":86400};var u=s.join("\u3001");var T=/^(\d{4})(\d{2})(\d{2})$/;function c(e,t,r){if(t!=="1d"){let n=l[t];return Math.floor(e/n)*n}return C(r??b(e))}function b(e){let t=new Date((e+28800)*1000);if(t.getUTCHours()>=20||t.getUTCDay()===0||t.getUTCDay()===6){t.setUTCDate(t.getUTCDate()+(t.getUTCHours()>=20?1:0));while(t.getUTCDay()===0||t.getUTCDay()===6)t.setUTCDate(t.getUTCDate()+1)}return`${t.getUTCFullYear()}${d(t.getUTCMonth()+1)}${d(t.getUTCDate())}`}function C(e){let t=T.exec(e);if(!t)throw Error(`\u975E\u6CD5\u4EA4\u6613\u65E5: ${e}`);let r=Number(t[1]),n=Number(t[2]),i=Number(t[3]),a=Date.UTC(r,n-1,i),o=new Date(a);if(o.getUTCFullYear()!==r||o.getUTCMonth()!==n-1||o.getUTCDate()!==i)throw Error(`\u975E\u6CD5\u4EA4\u6613\u65E5: ${e}`);return Math.floor(a/1000)-28800}function d(e){return String(e).padStart(2,"0")}var S=new Set(s);class g{timeframe;seconds;current=null;constructor(e){this.timeframe=e;if(!S.has(e))throw Error(`\u5468\u671F "${e}" \u4E0D\u53D7\u652F\u6301\uFF1B\u4EC5\u652F\u6301 ${u}`);this.seconds=l[e]}pushBar(e){let t=this.pushBars(e);if(t.length>1)throw Error("\u5355\u6839 1min bar \u540C\u65F6\u95ED\u5408\u4E86\u591A\u4E2A\u7A97\u53E3\uFF1B\u8BF7\u4F7F\u7528 pushBars() \u4FDD\u7559\u5168\u90E8\u4FE1\u53F7");return t[0]??null}pushBars(e){if(e.timeframe!=="1min")throw Error(`BarGenerator \u53EA\u63A5\u53D7\u95ED\u5408 1min bar\uFF0C\u6536\u5230 ${e.timeframe}`);if(this.seconds===60)return[e];let t=[],r=c(e.barTime,this.timeframe,e.tradingDay);if(this.current&&this.current.barTime!==r){let a=this.finish();if(a)t.push(a)}if(!this.current)this.start(e,r);else this.merge(e);let n=this.timeframe!=="1d"&&e.barTime+60>=r+this.seconds,i=this.seconds>=3600?e.boundaryReason==="sessionEnd":e.boundaryReason!==void 0;if(n||i){let a=this.finish(e.boundaryReason);if(a)t.push(a)}return t}flush(e){return this.finish(e)}start(e,t){this.current={code:e.code,timeframe:this.timeframe,barTime:t,open:e.open,high:e.high,low:e.low,close:e.close,volume:e.volume,...e.tradingDay?{tradingDay:e.tradingDay}:{}}}merge(e){let t=this.current;if(t.high=Math.max(t.high,e.high),t.low=Math.min(t.low,e.low),t.close=e.close,t.volume+=e.volume,e.tradingDay)t.tradingDay=e.tradingDay}finish(e){let t=this.current;if(!t)return null;return this.current=null,{...t,...e?{boundaryReason:e}:{}}}}export{g as BarGenerator,m as CtaTemplate,f as defineStrategy,p as isActiveOrder,h as parseTimeframe};
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibek/strategy",
|
|
3
|
+
"version": "0.5.4",
|
|
4
|
+
"description": "Public Vibek strategy SDK and Bun testing helper",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./index.d.ts",
|
|
10
|
+
"import": "./index.js",
|
|
11
|
+
"default": "./index.js"
|
|
12
|
+
},
|
|
13
|
+
"./testing": {
|
|
14
|
+
"types": "./testing.d.ts",
|
|
15
|
+
"import": "./testing.js",
|
|
16
|
+
"default": "./testing.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public",
|
|
21
|
+
"registry": "https://registry.npmjs.org/"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"bun": ">=1.4.0"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"index.js",
|
|
28
|
+
"index.d.ts",
|
|
29
|
+
"testing.js",
|
|
30
|
+
"testing.d.ts",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
]
|
|
34
|
+
}
|
package/testing.d.ts
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Bar,
|
|
3
|
+
CommissionData,
|
|
4
|
+
EngineType,
|
|
5
|
+
OrderData,
|
|
6
|
+
OrderEvent,
|
|
7
|
+
StrategyDefinition,
|
|
8
|
+
StrategyHook,
|
|
9
|
+
StrategyVariables,
|
|
10
|
+
TickData,
|
|
11
|
+
TradeData,
|
|
12
|
+
} from '@vibek/strategy'
|
|
13
|
+
|
|
14
|
+
type StrategyInstance<TDefinition extends StrategyDefinition> = InstanceType<TDefinition['strategy']>
|
|
15
|
+
type StrategyParameterList<TDefinition extends StrategyDefinition> = NonNullable<TDefinition['strategy']['parameters']>
|
|
16
|
+
type StrategyParameterName<TDefinition extends StrategyDefinition> = Extract<
|
|
17
|
+
StrategyParameterList<TDefinition>[number],
|
|
18
|
+
keyof StrategyInstance<TDefinition>
|
|
19
|
+
>
|
|
20
|
+
|
|
21
|
+
export type StrategySettingFor<TDefinition extends StrategyDefinition> = Partial<Pick<
|
|
22
|
+
StrategyInstance<TDefinition>,
|
|
23
|
+
StrategyParameterName<TDefinition>
|
|
24
|
+
>>
|
|
25
|
+
|
|
26
|
+
/** Fixture shared by Bun contract tests and user-authored strategy tests. */
|
|
27
|
+
export interface StrategyTestFixture<
|
|
28
|
+
TSetting extends Readonly<Record<string, unknown>> = Readonly<Record<string, unknown>>,
|
|
29
|
+
> {
|
|
30
|
+
readonly vtSymbol: string
|
|
31
|
+
readonly setting?: TSetting
|
|
32
|
+
readonly instrument: {
|
|
33
|
+
readonly priceTick: number
|
|
34
|
+
readonly size: number
|
|
35
|
+
readonly name?: string
|
|
36
|
+
readonly minOrderVolume?: number
|
|
37
|
+
readonly maxOrderVolume?: number
|
|
38
|
+
readonly longMarginRatio?: number
|
|
39
|
+
readonly shortMarginRatio?: number
|
|
40
|
+
readonly isTrading?: boolean
|
|
41
|
+
}
|
|
42
|
+
readonly commission?: CommissionData
|
|
43
|
+
readonly seedPrice?: number
|
|
44
|
+
readonly tradingDay?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface StrategyTestPositionInventory {
|
|
48
|
+
readonly longToday: number
|
|
49
|
+
readonly longYesterday: number
|
|
50
|
+
readonly shortToday: number
|
|
51
|
+
readonly shortYesterday: number
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface StrategyTestInitialState {
|
|
55
|
+
readonly variables?: StrategyVariables
|
|
56
|
+
readonly inventory?: Partial<StrategyTestPositionInventory>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface StrategyTestExecutionLimits {
|
|
60
|
+
readonly maxOrderVolume: number
|
|
61
|
+
readonly maxPosition: number
|
|
62
|
+
readonly maxActiveOpenOrders: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface StrategyTestDailyRiskLimits {
|
|
66
|
+
readonly totalOrderLimit: number
|
|
67
|
+
readonly totalCancelLimit: number
|
|
68
|
+
readonly totalTradeLimit: number
|
|
69
|
+
readonly instrumentOrderLimit: number
|
|
70
|
+
readonly instrumentCancelLimit: number
|
|
71
|
+
readonly instrumentTradeLimit: number
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface StrategyTestRiskLimits {
|
|
75
|
+
readonly dailyLimits?: Partial<StrategyTestDailyRiskLimits>
|
|
76
|
+
readonly duplicateOrderLimit?: number
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface StrategyTestPaperExecution {
|
|
80
|
+
readonly acceptOrder?: (order: Readonly<OrderData>) => { ok: true } | { ok: false; reason: string }
|
|
81
|
+
readonly fillOrder?: (
|
|
82
|
+
order: Readonly<OrderData>,
|
|
83
|
+
tick: Readonly<TickData>,
|
|
84
|
+
volume: number,
|
|
85
|
+
) => { referencePrice: number; price: number }
|
|
86
|
+
readonly onTrade?: (trade: Readonly<TradeData>) => void
|
|
87
|
+
readonly onOrderClosed?: (order: Readonly<OrderData>) => void
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface StrategyTestExternalOrder {
|
|
91
|
+
readonly vtOrderId: string
|
|
92
|
+
readonly vtSymbol?: string
|
|
93
|
+
readonly direction: 'long' | 'short'
|
|
94
|
+
readonly offset?: OrderData['offset']
|
|
95
|
+
readonly price: number
|
|
96
|
+
readonly volume: number
|
|
97
|
+
readonly traded?: number
|
|
98
|
+
readonly status?: OrderData['status']
|
|
99
|
+
readonly statusMessage?: string
|
|
100
|
+
readonly datetime?: number
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface StrategyTestReconciliation {
|
|
104
|
+
readonly reason?: 'initial' | 'reconnect'
|
|
105
|
+
readonly tradingDay?: string
|
|
106
|
+
readonly inventory?: Partial<StrategyTestPositionInventory>
|
|
107
|
+
readonly orders?: readonly StrategyTestExternalOrder[]
|
|
108
|
+
readonly trades?: readonly StrategyTestTrade[]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface StrategyTestTrade {
|
|
112
|
+
readonly vtTradeId: string
|
|
113
|
+
readonly vtOrderId: string
|
|
114
|
+
readonly volume: number
|
|
115
|
+
readonly price?: number
|
|
116
|
+
readonly datetime?: number
|
|
117
|
+
readonly tradingDay?: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type StrategyTestEvent = { readonly now: number } & (
|
|
121
|
+
| { readonly type: 'order'; readonly value: Readonly<OrderEvent> }
|
|
122
|
+
| { readonly type: 'trade'; readonly value: Readonly<TradeData> }
|
|
123
|
+
| { readonly type: 'hook'; readonly value: Readonly<StrategyHook> }
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
export interface StrategyTestRuntimeOptions<
|
|
127
|
+
TSetting extends Readonly<Record<string, unknown>> = Readonly<Record<string, unknown>>,
|
|
128
|
+
> {
|
|
129
|
+
readonly mode?: EngineType
|
|
130
|
+
readonly setting?: TSetting
|
|
131
|
+
readonly initialState?: StrategyTestInitialState
|
|
132
|
+
/** Exchange state is independent from the strategy's persisted state. */
|
|
133
|
+
readonly brokerState?: StrategyTestReconciliation;
|
|
134
|
+
readonly execution?: 'scripted' | 'ctp';
|
|
135
|
+
readonly gatewayState?: StrategyTestGatewaySnapshot;
|
|
136
|
+
readonly gatewaySession?: Partial<StrategyTestGatewaySession>;
|
|
137
|
+
readonly executionLimits?: Partial<StrategyTestExecutionLimits>
|
|
138
|
+
readonly riskLimits?: StrategyTestRiskLimits
|
|
139
|
+
readonly paperExecution?: StrategyTestPaperExecution
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface StrategyTestTraceEvent {
|
|
143
|
+
readonly sequence: number;
|
|
144
|
+
readonly now: number;
|
|
145
|
+
readonly cause: number | null;
|
|
146
|
+
readonly type: 'input' | 'callback' | 'order' | 'trade' | 'submission' | 'cancel'
|
|
147
|
+
| 'hook' | 'log' | 'fault' | 'state' | 'timer' | 'bar' | 'broker' | 'completion' | 'context';
|
|
148
|
+
readonly value: unknown;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface StrategyTestSubmissionRef { readonly submission: number; }
|
|
152
|
+
export interface StrategyTestRequestRef { readonly request: string; }
|
|
153
|
+
type ScenarioArgument<T> = T extends string ? T | StrategyTestSubmissionRef
|
|
154
|
+
: T extends number ? T | StrategyTestRequestRef
|
|
155
|
+
: T extends readonly unknown[] ? { readonly [K in keyof T]: ScenarioArgument<T[K]> }
|
|
156
|
+
: T extends object ? { readonly [K in keyof T]: ScenarioArgument<T[K]> } : T;
|
|
157
|
+
type BrokerCommand = Exclude<keyof StrategyTestBroker, 'snapshot' | 'pendingReports'>;
|
|
158
|
+
type GatewayCommand = Exclude<keyof StrategyTestGateway, 'requests' | 'pendingRequests'>;
|
|
159
|
+
|
|
160
|
+
export type StrategyTestAction =
|
|
161
|
+
| { readonly type: 'initialize'; readonly warmupBars?: readonly Bar[] }
|
|
162
|
+
| { readonly type: 'start' | 'stop' | 'restart' | 'crashAndRestart' }
|
|
163
|
+
| { readonly type: 'setTime'; readonly datetime: number }
|
|
164
|
+
| { readonly type: 'advanceTime'; readonly milliseconds: number }
|
|
165
|
+
| { readonly type: 'market'; readonly tick: Partial<TickData> & Pick<TickData, 'lastPrice'>; readonly closedBars?: readonly Bar[] }
|
|
166
|
+
| { readonly type: 'bar'; readonly value: Partial<Bar> & Pick<Bar, 'close'>; readonly datetime?: number }
|
|
167
|
+
| { readonly type: 'cancelOrder'; readonly vtOrderId: string | StrategyTestSubmissionRef }
|
|
168
|
+
| { readonly type: 'expectFault'; readonly message: string }
|
|
169
|
+
| { readonly type: 'await'; readonly task: string; readonly error?: string }
|
|
170
|
+
| { [K in BrokerCommand]: { readonly type: 'broker'; readonly method: K; readonly args: ScenarioArgument<Parameters<StrategyTestBroker[K]>> } }[BrokerCommand]
|
|
171
|
+
| { [K in GatewayCommand]: { readonly type: 'gateway'; readonly method: K; readonly args: ScenarioArgument<Parameters<StrategyTestGateway[K]>> } }[GatewayCommand];
|
|
172
|
+
|
|
173
|
+
export interface StrategyTestScenario {
|
|
174
|
+
readonly seed?: number;
|
|
175
|
+
readonly context?: {
|
|
176
|
+
readonly fixture: StrategyTestFixture;
|
|
177
|
+
readonly options: Omit<StrategyTestRuntimeOptions, 'paperExecution'>;
|
|
178
|
+
readonly customPaperExecution?: true;
|
|
179
|
+
};
|
|
180
|
+
readonly steps: readonly { readonly at?: number; readonly task?: string; readonly action: StrategyTestAction }[];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface StrategyTestScenarioResult {
|
|
184
|
+
readonly seed?: number;
|
|
185
|
+
readonly trace: readonly StrategyTestTraceEvent[];
|
|
186
|
+
readonly snapshot: StrategyTestRuntimeSnapshot;
|
|
187
|
+
readonly submissions: readonly StrategySubmission[];
|
|
188
|
+
readonly cancelRequests: readonly string[];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface StrategySubmission {
|
|
192
|
+
readonly vtOrderId: string
|
|
193
|
+
readonly direction: 'long' | 'short'
|
|
194
|
+
readonly offset: OrderData['offset']
|
|
195
|
+
readonly price: number
|
|
196
|
+
readonly volume: number
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface StrategyTestRuntimeSnapshot {
|
|
200
|
+
readonly inited: boolean
|
|
201
|
+
readonly trading: boolean
|
|
202
|
+
readonly pos: number
|
|
203
|
+
readonly activeOrders: number
|
|
204
|
+
readonly fault: string | null
|
|
205
|
+
readonly variables: StrategyVariables
|
|
206
|
+
readonly inventory: StrategyTestPositionInventory
|
|
207
|
+
/** Current deterministic market time as epoch milliseconds. */
|
|
208
|
+
readonly now: number
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface StrategyTestBroker {
|
|
212
|
+
holdReports(): void;
|
|
213
|
+
resumeReports(): void;
|
|
214
|
+
pendingReports(): readonly StrategyTestBrokerReport[];
|
|
215
|
+
deliverReport(id: string): void;
|
|
216
|
+
repeatReport(id: string): void;
|
|
217
|
+
snapshot(): StrategyTestBrokerSnapshot;
|
|
218
|
+
failNextSubmit(message?: string): void
|
|
219
|
+
rejectSubmit(vtOrderId: string, message?: string): void
|
|
220
|
+
acknowledge(vtOrderId: string): void
|
|
221
|
+
partialFill(vtOrderId: string, volume: number, price?: number): void
|
|
222
|
+
fill(vtOrderId: string, price?: number): void
|
|
223
|
+
reject(vtOrderId: string, message?: string): void
|
|
224
|
+
cancel(vtOrderId: string, message?: string): void
|
|
225
|
+
failNextCancel(message?: string): void
|
|
226
|
+
rejectCancel(vtOrderId: string, message?: string): void;
|
|
227
|
+
emitExternalOrder(order: StrategyTestExternalOrder): void
|
|
228
|
+
emitOrder(order: StrategyTestExternalOrder): void
|
|
229
|
+
emitTrade(trade: StrategyTestTrade): void
|
|
230
|
+
disconnect(): void
|
|
231
|
+
reconcile(snapshot?: StrategyTestReconciliation): void
|
|
232
|
+
fault(message?: string): void
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export type StrategyTestBrokerReport = { readonly id: string; readonly createdAt: number } & (
|
|
236
|
+
| { readonly type: 'order'; readonly value: Readonly<OrderData> }
|
|
237
|
+
| { readonly type: 'trade'; readonly value: Readonly<TradeData> }
|
|
238
|
+
| { readonly type: 'error'; readonly value: { readonly vtOrderId: string; readonly operation: 'submit' | 'cancel'; readonly message: string } }
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
export interface StrategyTestBrokerSnapshot {
|
|
242
|
+
readonly phase: 'running' | 'recovering' | 'faulted';
|
|
243
|
+
readonly tradingDay: string;
|
|
244
|
+
readonly inventory: StrategyTestPositionInventory;
|
|
245
|
+
readonly orders: readonly Readonly<OrderData>[];
|
|
246
|
+
readonly trades: readonly Readonly<TradeData>[];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export interface StrategyTestRuntime {
|
|
250
|
+
readonly trace: readonly StrategyTestTraceEvent[];
|
|
251
|
+
readonly events: readonly StrategyTestEvent[]
|
|
252
|
+
readonly submissions: readonly StrategySubmission[]
|
|
253
|
+
readonly cancelRequests: readonly string[]
|
|
254
|
+
readonly orderEvents: readonly Readonly<OrderEvent>[]
|
|
255
|
+
readonly tradeEvents: readonly Readonly<TradeData>[]
|
|
256
|
+
readonly hooks: readonly Readonly<StrategyHook>[]
|
|
257
|
+
/** Most recent 20 sanitized runtime log entries; older entries are discarded. */
|
|
258
|
+
readonly logs: readonly string[]
|
|
259
|
+
readonly broker: StrategyTestBroker
|
|
260
|
+
/** Raw CTP controls, available only with execution='ctp'. */
|
|
261
|
+
readonly gateway: StrategyTestGateway;
|
|
262
|
+
initialize(options?: { readonly warmupBars?: readonly Bar[] }): Promise<void>
|
|
263
|
+
start(): Promise<void>
|
|
264
|
+
stop(): Promise<void>
|
|
265
|
+
cancelOrder(vtOrderId: string): void
|
|
266
|
+
restart(): Promise<void>
|
|
267
|
+
crashAndRestart(): Promise<void>
|
|
268
|
+
setTime(datetime: number): void
|
|
269
|
+
advanceTime(milliseconds: number): void
|
|
270
|
+
pushBar(value: Partial<Bar> & Pick<Bar, 'close'>, options?: { readonly datetime?: number }): void
|
|
271
|
+
pushTick(value: Partial<TickData> & Pick<TickData, 'lastPrice'>): void
|
|
272
|
+
pushMarketEvent(value: Partial<TickData> & Pick<TickData, 'lastPrice'>, closedBars: readonly Bar[]): void
|
|
273
|
+
activeOrders(): readonly Readonly<OrderData>[]
|
|
274
|
+
snapshot(): StrategyTestRuntimeSnapshot
|
|
275
|
+
/** Verify and consume one exact fault, including faults from before a restart. */
|
|
276
|
+
expectFault(message: string): void;
|
|
277
|
+
/** Release resources and fail on any fault that was not explicitly verified. */
|
|
278
|
+
dispose(): Promise<void>;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export interface StrategyTestGatewaySession {
|
|
282
|
+
readonly tradingDay: string;
|
|
283
|
+
readonly frontID: number;
|
|
284
|
+
readonly sessionID: number;
|
|
285
|
+
readonly maxOrderRef?: string;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface StrategyTestGatewaySnapshot {
|
|
289
|
+
readonly orders?: readonly Readonly<Record<string, unknown>>[];
|
|
290
|
+
readonly trades?: readonly Readonly<Record<string, unknown>>[];
|
|
291
|
+
readonly positions?: readonly Readonly<Record<string, unknown>>[];
|
|
292
|
+
readonly accounts?: readonly Readonly<Record<string, unknown>>[];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface StrategyTestGatewayRequest {
|
|
296
|
+
readonly id: number;
|
|
297
|
+
readonly method: string;
|
|
298
|
+
readonly params: Readonly<Record<string, unknown>>;
|
|
299
|
+
readonly now: number;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export interface StrategyTestGateway {
|
|
303
|
+
readonly requests: readonly StrategyTestGatewayRequest[];
|
|
304
|
+
pendingRequests(): readonly StrategyTestGatewayRequest[];
|
|
305
|
+
waitForRequest(method: string): Promise<StrategyTestGatewayRequest>;
|
|
306
|
+
setSnapshot(snapshot: StrategyTestGatewaySnapshot): void;
|
|
307
|
+
hold(method: string): void;
|
|
308
|
+
resume(method: string): void;
|
|
309
|
+
reply(id: number, response?: { readonly ok: boolean; readonly data?: Record<string, unknown>; readonly error?: { readonly code: number; readonly msg: string } }): void;
|
|
310
|
+
completeQuery(id: number, rows?: readonly Readonly<Record<string, unknown>>[]): void;
|
|
311
|
+
push(method: string, params: Readonly<Record<string, unknown>>): void;
|
|
312
|
+
disconnect(): void;
|
|
313
|
+
reconnect(session?: Partial<StrategyTestGatewaySession>): Promise<void>;
|
|
314
|
+
queryOrders(): Promise<readonly Readonly<OrderData>[]>;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
type TestDefinition<T extends StrategyDefinition> = { readonly strategy: T; readonly fixture: StrategyTestFixture<StrategySettingFor<T>> };
|
|
318
|
+
|
|
319
|
+
export function createStrategyTest<const T extends StrategyDefinition>(
|
|
320
|
+
definition: TestDefinition<T>, options?: StrategyTestRuntimeOptions<StrategySettingFor<T>>,
|
|
321
|
+
): StrategyTestRuntime;
|
|
322
|
+
export function runStrategyScenario<const T extends StrategyDefinition>(
|
|
323
|
+
definition: TestDefinition<T>, scenario: StrategyTestScenario, options?: StrategyTestRuntimeOptions<StrategySettingFor<T>>,
|
|
324
|
+
): Promise<StrategyTestScenarioResult>;
|
|
325
|
+
export function assertStrategyReplay<const T extends StrategyDefinition>(
|
|
326
|
+
definition: TestDefinition<T>, scenario: StrategyTestScenario, options?: StrategyTestRuntimeOptions<StrategySettingFor<T>>,
|
|
327
|
+
): Promise<StrategyTestScenarioResult>;
|
|
328
|
+
export function captureStrategyScenario(runtime: StrategyTestRuntime, seed?: number): StrategyTestScenario;
|
|
329
|
+
export function serializeStrategyScenario(scenario: StrategyTestScenario): string;
|
|
330
|
+
export function parseStrategyScenario(value: string): StrategyTestScenario;
|
|
331
|
+
export class StrategyScenarioError extends Error {
|
|
332
|
+
readonly step: number;
|
|
333
|
+
readonly scenario: StrategyTestScenario;
|
|
334
|
+
readonly trace: readonly StrategyTestTraceEvent[];
|
|
335
|
+
constructor(step: number, scenario: StrategyTestScenario, trace: readonly StrategyTestTraceEvent[], cause: unknown);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Bun test file helper with automatic contract registration and per-test cleanup. */
|
|
339
|
+
export interface StrategyTestHelper<TDefinition extends StrategyDefinition = StrategyDefinition> {
|
|
340
|
+
createRuntime(options?: StrategyTestRuntimeOptions<StrategySettingFor<TDefinition>>): StrategyTestRuntime;
|
|
341
|
+
bar(value: Partial<Bar> & Pick<Bar, 'close'>): Bar;
|
|
342
|
+
tick(value: Partial<TickData> & Pick<TickData, 'lastPrice'>): TickData;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function setupStrategyTests<const TDefinition extends StrategyDefinition>(definition: {
|
|
346
|
+
readonly strategy: TDefinition;
|
|
347
|
+
readonly fixture: StrategyTestFixture<StrategySettingFor<TDefinition>>;
|
|
348
|
+
}): StrategyTestHelper<TDefinition>;
|
package/testing.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var wt=import.meta.require;function w(e){return e.status==="submitting"||e.status==="notTraded"||e.status==="partTraded"}var br=Symbol.for("vibek.strategy.cta-template-runtime");function St(e){if(!e||typeof e!=="object")return null;let t=e[br];if(typeof t!=="function")return null;let r=t.call(e);if(!r||typeof r.bindContext!=="function"||typeof r.restoreVariables!=="function"||typeof r.setRuntimeState!=="function"||typeof r.getStateVariables!=="function")return null;return r}var we=/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;var kr=["strategyName","vtSymbol","mode","inited","trading","pos"],Xn=new Set([...kr,"constructor","__proto__","prototype","onInit","onStart","onStop","onTick","onBar","onTrade","onOrder","buy","sell","short","cover","cancelOrder","cancelAll","loadBars","registerEvent","claimOrder","writeLog","emitHook","getCurrentTime","getContract","getCommission","getPriceTick","getSize"]);function Se(e,t,r){if(!Sr(e))throw Error("\u7B56\u7565 default export \u5FC5\u987B\u662F\u5BF9\u8C61");let n=Object.keys(e).find((p)=>p!=="strategy");if(n)throw Error(`\u7B56\u7565\u5B9A\u4E49\u4E0D\u652F\u6301\u5B57\u6BB5 "${n}"\uFF1Bid\u3001vtSymbol \u548C setting \u5FC5\u987B\u5728\u52A0\u8F7D\u65F6\u4F20\u5165`);if(typeof e.strategy!=="function")throw Error(`\u7B56\u7565 ${t} \u7684 strategy \u5FC5\u987B\u662F\u7C7B`);if(!Sr(r))throw Error(`\u7B56\u7565 ${t} \u7684 setting \u5FC5\u987B\u662F\u5BF9\u8C61`);let i=e.strategy,o=wr(i.parameters,"parameters",t),s=wr(i.variables,"variables",t),a=[...o,...s].find((p)=>Xn.has(p));if(a)throw Error(`\u7B56\u7565 ${t} \u7684 parameters/variables \u4E0D\u5E94\u58F0\u660E\u6846\u67B6\u5B57\u6BB5 ${a}`);let d=o.find((p)=>s.includes(p));if(d)throw Error(`\u7B56\u7565 ${t} \u7684\u5B57\u6BB5 ${d} \u4E0D\u80FD\u540C\u65F6\u5C5E\u4E8E parameters \u548C variables`);let c=Object.keys(r).find((p)=>!o.includes(p));if(c)throw Error(`\u7B56\u7565 ${t} \u4E0D\u652F\u6301\u53C2\u6570 "${c}"`);let u;try{u=new i}catch(p){throw Error(`\u7B56\u7565 ${t} \u6784\u9020\u5931\u8D25: ${p instanceof Error?p.message:String(p)}`)}let l=St(u);if(!l||typeof u.onInit!=="function")throw Error(`\u7B56\u7565 ${t} \u5FC5\u987B\u7EE7\u627F CtaTemplate \u5E76\u5B9E\u73B0 onInit()`);for(let p of kr)if(Object.hasOwn(u,p))throw Error(`\u7B56\u7565 ${t} \u4E0D\u5F97\u58F0\u660E\u6846\u67B6\u5B57\u6BB5 ${p}`);for(let p of[...o,...s])if(!(p in u))throw Error(`\u7B56\u7565 ${t} \u58F0\u660E\u4E86\u5B57\u6BB5 ${p}\uFF0C\u4F46\u5B9E\u4F8B\u6CA1\u6709\u5BF9\u5E94\u9ED8\u8BA4\u503C`);let f=o.map((p)=>{let k=Tr(ti(u,p),`\u7B56\u7565 ${t} \u53C2\u6570 ${p} \u9ED8\u8BA4\u503C`);return{name:p,defaultValue:k,kind:ei(k)}}),h=vr(r,`\u7B56\u7565 ${t} setting`),m=Object.fromEntries(f.map((p)=>[p.name,Object.hasOwn(h,p.name)?h[p.name]:p.defaultValue]));return{definition:{strategy:i},ctor:i,instance:u,runtime:l,effectiveSetting:vr(m,`\u7B56\u7565 ${t} \u751F\u6548 setting`),inspection:{name:i.name,author:i.author??"",parameters:f}}}function wr(e,t,r){if(e===void 0)return[];if(!Array.isArray(e))throw Error(`\u7B56\u7565 ${r} \u7684 ${t} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4`);let n=e.map((i,o)=>{if(typeof i!=="string"||!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(i))throw Error(`\u7B56\u7565 ${r} \u7684 ${t}[${o}] \u975E\u6CD5`);return i});if(new Set(n).size!==n.length)throw Error(`\u7B56\u7565 ${r} \u7684 ${t} \u5B58\u5728\u91CD\u590D\u9879`);return n}function ei(e){if(typeof e==="number")return"number";if(typeof e==="boolean")return"boolean";if(typeof e==="string")return"string";return"json"}function ti(e,t){return e[t]}function Sr(e){return e!==null&&typeof e==="object"&&!Array.isArray(e)}function vr(e,t){return Tr(e,t)}function Tr(e,t){return vt(e,t,new Set),JSON.parse(JSON.stringify(e))}function vt(e,t,r){if(e===null||typeof e==="string"||typeof e==="boolean")return;if(typeof e==="number"){if(!Number.isFinite(e))throw Error(`${t} \u5305\u542B\u975E\u6709\u9650\u6570\u5B57`);return}if(typeof e!=="object")throw Error(`${t} \u5305\u542B\u4E0D\u53EF\u5E8F\u5217\u5316\u7684 ${typeof e}`);if(r.has(e))throw Error(`${t} \u5305\u542B\u5FAA\u73AF\u5F15\u7528`);if(r.add(e),Array.isArray(e))for(let n=0;n<e.length;n++)vt(e[n],`${t}[${n}]`,r);else{let n=Object.getPrototypeOf(e);if(n!==Object.prototype&&n!==null)throw Error(`${t} \u5FC5\u987B\u662F\u666E\u901A\u5BF9\u8C61\u6216\u6570\u7EC4`);for(let[i,o]of Object.entries(e))vt(o,`${t}.${i}`,r)}r.delete(e)}class qe{timeframes;currentBars=new Map;completedBars=[];tickCallbacks=[];barCallbacks=[];lastTickTime=0;lastCumVolume=null;lastTradingDay="";constructor(e){this.timeframes=e.timeframes.map((t)=>({label:t,seconds:Ir(t)}))}onTick(e){this.tickCallbacks.push(e)}offTick(e){let t=this.tickCallbacks.indexOf(e);if(t>=0)this.tickCallbacks.splice(t,1)}onBar(e){this.barCallbacks.push(e)}offBar(e){let t=this.barCallbacks.indexOf(e);if(t>=0)this.barCallbacks.splice(t,1)}pushTick(e){let t=ni(e);if(this.lastTickTime>0&&t<this.lastTickTime)throw Error(`tick \u65F6\u95F4\u56DE\u9000: ${e.code} ${t} < ${this.lastTickTime}`);this.lastTickTime=t;let r=Boolean(e.tradingDay&&this.lastTradingDay&&e.tradingDay!==this.lastTradingDay),n=0;if(!r&&this.lastCumVolume!==null&&e.volume>=this.lastCumVolume)n=e.volume-this.lastCumVolume;if(this.lastCumVolume=e.volume,e.tradingDay)this.lastTradingDay=e.tradingDay;for(let i of this.tickCallbacks)i(e);for(let i of this.timeframes)this.updateBar(i,e,t,n)}completeThrough(e){let t=Math.floor(e/1000);for(let r of this.timeframes){let n=this.currentBars.get(r.label);if(n&&n.barTime+r.seconds<=t)this.finalizeBar(r.label)}}getCompletedBars(){return this.completedBars}getCurrentBar(e){return this.currentBars.get(e)??null}forceCompleteAll(e){for(let t of this.timeframes)this.finalizeBar(t.label,e)}forceComplete(e,t){if(this.timeframes.some((r)=>r.label===e))this.finalizeBar(e,t)}markDisconnected(){for(let e of this.currentBars.values())e.boundaryReason="disconnect";this.lastCumVolume=null}reset(){this.currentBars.clear(),this.completedBars=[],this.lastTickTime=0,this.lastCumVolume=null,this.lastTradingDay=""}clearCompletedBars(){this.completedBars=[]}_registrySize(){return this.currentBars.size}updateBar(e,t,r,n){let i=ri(r,e.seconds),o=t.lastPrice,s=this.currentBars.get(e.label);if(s&&s.barTime<i)this.finalizeBar(e.label),s=void 0;if(!s){this.currentBars.set(e.label,{code:t.code,timeframe:e.label,barTime:i,open:o,high:o,low:o,close:o,volume:n,tickCount:1,...t.tradingDay?{tradingDay:t.tradingDay}:{}});return}if(s.high=Math.max(s.high,o),s.low=Math.min(s.low,o),s.close=o,s.volume+=n,s.tickCount++,t.tradingDay)s.tradingDay=t.tradingDay}finalizeBar(e,t){let r=this.currentBars.get(e);if(!r)return;if(this.currentBars.delete(e),r.tickCount===0)return;let n=t??r.boundaryReason,i={code:r.code,timeframe:r.timeframe,barTime:r.barTime,open:r.open,high:r.high,low:r.low,close:r.close,volume:r.volume,...r.tradingDay?{tradingDay:r.tradingDay}:{},...n?{boundaryReason:n}:{}};if(this.completedBars.push(i),this.completedBars.length>1000)this.completedBars.shift();for(let o of this.barCallbacks)o(i)}}function Ir(e){let t=/^(\d+)(min|h|d)$/.exec(e);if(!t)throw Error(`\u4E0D\u652F\u6301\u7684 K \u7EBF\u5468\u671F: ${e}`);let r=Number(t[1]);if(!Number.isInteger(r)||r<=0)throw Error(`\u4E0D\u652F\u6301\u7684 K \u7EBF\u5468\u671F: ${e}`);if(t[2]==="min")return r*60;if(t[2]==="h")return r*3600;return r*86400}function ri(e,t){return Math.floor(e/t)*t}function ni(e){if(Number.isFinite(e.datetime))return Math.floor(e.datetime/1000);return Math.floor(Or(e.actionDay,e.updateTime,e.updateMillisec??0)/1000)}var Dr=28800000,kt=86400000;function ii(e){let r=((e+Dr)%kt+kt)%kt;return e-r}function oi(e){if(!e||!/^\d{8}$/.test(e))return null;let t=Number(e.slice(0,4)),r=Number(e.slice(4,6)),n=Number(e.slice(6,8)),i=new Date(Date.UTC(t,r-1,n));return i.getUTCFullYear()===t&&i.getUTCMonth()===r-1&&i.getUTCDate()===n?[t,r,n]:null}var Er=new Set,si=43200000;function Or(e,t,r){if(!t)return Date.now();let n=/^(\d{2}):(\d{2}):(\d{2})$/.exec(t);if(!n){let d=new Date(t).getTime();return Number.isNaN(d)?Date.now():d}let i=Number(n[1]),o=Number(n[2]),s=Number(n[3]);if(i>23||o>59||s>59||!Number.isInteger(r)||r<0||r>999)return Date.now();let a=oi(e);if(a){let d=Date.UTC(a[0],a[1]-1,a[2],i,o,s,r)-Dr,c=Math.abs(d-Date.now());if(c>si&&!Er.has(e))Er.add(e),console.warn(`[bar-aggregator] actionDay \u4E0E\u7CFB\u7EDF\u65E5\u671F\u504F\u5DEE ${Math.round(c/3600000)}h: ${e}`);return d}return ii(Date.now())+(i*3600+o*60+s)*1000+r}var Ve=["1min","3min","5min","10min","15min","30min","1h","2h","4h","1d"],se={"1min":60,"3min":180,"5min":300,"10min":600,"15min":900,"30min":1800,"1h":3600,"2h":7200,"4h":14400,"1d":86400};var ve=Ve.join("\u3001");var ai=/^(\d{4})(\d{2})(\d{2})$/;function Z(e){return typeof e==="string"&&Ve.includes(e)}function ze(e,t,r){if(t!=="1d"){let n=se[t];return Math.floor(e/n)*n}return di(r??Tt(e))}function Tt(e){let t=new Date((e+28800)*1000);if(t.getUTCHours()>=20||t.getUTCDay()===0||t.getUTCDay()===6){t.setUTCDate(t.getUTCDate()+(t.getUTCHours()>=20?1:0));while(t.getUTCDay()===0||t.getUTCDay()===6)t.setUTCDate(t.getUTCDate()+1)}return`${t.getUTCFullYear()}${Rr(t.getUTCMonth()+1)}${Rr(t.getUTCDate())}`}function di(e){let t=ai.exec(e);if(!t)throw Error(`\u975E\u6CD5\u4EA4\u6613\u65E5: ${e}`);let r=Number(t[1]),n=Number(t[2]),i=Number(t[3]),o=Date.UTC(r,n-1,i),s=new Date(o);if(s.getUTCFullYear()!==r||s.getUTCMonth()!==n-1||s.getUTCDate()!==i)throw Error(`\u975E\u6CD5\u4EA4\u6613\u65E5: ${e}`);return Math.floor(o/1000)-28800}function xr(e,t){let r=e==="1d"?600:se[e]/60;return t*r+(r-1)}function Rr(e){return String(e).padStart(2,"0")}var ci=new Set(Ve);class Ue{timeframe;seconds;current=null;constructor(e){this.timeframe=e;if(!ci.has(e))throw Error(`\u5468\u671F "${e}" \u4E0D\u53D7\u652F\u6301\uFF1B\u4EC5\u652F\u6301 ${ve}`);this.seconds=se[e]}pushBar(e){let t=this.pushBars(e);if(t.length>1)throw Error("\u5355\u6839 1min bar \u540C\u65F6\u95ED\u5408\u4E86\u591A\u4E2A\u7A97\u53E3\uFF1B\u8BF7\u4F7F\u7528 pushBars() \u4FDD\u7559\u5168\u90E8\u4FE1\u53F7");return t[0]??null}pushBars(e){if(e.timeframe!=="1min")throw Error(`BarGenerator \u53EA\u63A5\u53D7\u95ED\u5408 1min bar\uFF0C\u6536\u5230 ${e.timeframe}`);if(this.seconds===60)return[e];let t=[],r=ze(e.barTime,this.timeframe,e.tradingDay);if(this.current&&this.current.barTime!==r){let o=this.finish();if(o)t.push(o)}if(!this.current)this.start(e,r);else this.merge(e);let n=this.timeframe!=="1d"&&e.barTime+60>=r+this.seconds,i=this.seconds>=3600?e.boundaryReason==="sessionEnd":e.boundaryReason!==void 0;if(n||i){let o=this.finish(e.boundaryReason);if(o)t.push(o)}return t}flush(e){return this.finish(e)}start(e,t){this.current={code:e.code,timeframe:this.timeframe,barTime:t,open:e.open,high:e.high,low:e.low,close:e.close,volume:e.volume,...e.tradingDay?{tradingDay:e.tradingDay}:{}}}merge(e){let t=this.current;if(t.high=Math.max(t.high,e.high),t.low=Math.min(t.low,e.low),t.close=e.close,t.volume+=e.volume,e.tradingDay)t.tradingDay=e.tradingDay}finish(e){let t=this.current;if(!t)return null;return this.current=null,{...t,...e?{boundaryReason:e}:{}}}}function _r(e){if(e.timeframe!=="1min")throw Error(`\u7B56\u7565\u9884\u70ED\u53EA\u63A5\u53D7\u95ED\u5408 1min Bar\uFF0C\u6536\u5230 ${e.timeframe}`)}function*Cr(e,t){if(t==="1min"){for(let o=0;o<e.length;o++)_r(e[o]),yield{startIndex:o,endIndex:o,triggerIndex:o};return}let r=new Ue(t),n=0,i=-1/0;for(let o=0;o<e.length;o++){let s=e[o];if(_r(s),s.barTime<i)throw Error("\u7B56\u7565\u9884\u70ED Bar \u5FC5\u987B\u6309\u65F6\u95F4\u5347\u5E8F\u6392\u5217");i=s.barTime;let a=ze(s.barTime,t,s.tradingDay);for(let d of r.pushBars(s)){let c=d.barTime===a?o:o-1;if(c<n)throw Error("\u7B56\u7565\u9884\u70ED Bar \u805A\u5408\u8FB9\u754C\u65E0\u6548");yield{startIndex:n,endIndex:c,triggerIndex:o},n=c+1}}}function Et(e,t){let r=0;for(let n of Cr(e,t))r++;return r}function ke(e,t,r){if(!Number.isInteger(r)||r<=0)throw Error("\u9884\u70ED Bar count \u5FC5\u987B\u4E3A\u6B63\u6574\u6570");let n=[...Cr(e,t)],i=n.length;if(i<r)return{bars:[...e],availableCount:i,selectedCount:i};let o=n[i-r];return{bars:e.slice(o.startIndex),availableCount:i,selectedCount:r}}var ui=new Set(["CFFEX","SHFE","CZCE","DCE","INE","GFEX"]);function K(e){let t=e.trim(),[r="",n,...i]=t.split(".");if(!r||!n||i.length>0)throw Error(`vtSymbol \u975E\u6CD5: ${e}`);if(!/^[A-Za-z][A-Za-z0-9]*$/.test(r)||!/\d/.test(r))throw Error(`vtSymbol \u5408\u7EA6\u4EE3\u7801\u975E\u6CD5: ${e}`);if(!ui.has(n.toUpperCase()))throw Error(`vtSymbol \u4EA4\u6613\u6240\u975E\u6CD5: ${e}`);let o=r.toUpperCase();return{instrumentID:o,vtSymbol:`${o}.${n.toUpperCase()}`}}function Pr(e){let t=e.split(".")[1]?.toUpperCase();return t==="SHFE"||t==="INE"}var li="20260105",mi=3500;function Y(e){if(!ae(e))throw Error("\u6D4B\u8BD5\u5B9A\u4E49 fixture \u5FC5\u987B\u662F\u5BF9\u8C61");if(Te(e,["vtSymbol","setting","instrument","commission","seedPrice","tradingDay"],"fixture"),typeof e.vtSymbol!=="string")throw Error("fixture.vtSymbol \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");let t=K(e.vtSymbol);if(!ae(e.instrument))throw Error("fixture.instrument \u5FC5\u987B\u662F\u5BF9\u8C61");Te(e.instrument,["priceTick","size","name","minOrderVolume","maxOrderVolume","longMarginRatio","shortMarginRatio","isTrading"],"fixture.instrument");let r=E(e.instrument.priceTick,"fixture.instrument.priceTick"),n=x(e.instrument.size,"fixture.instrument.size"),i=e.instrument.name===void 0?t.instrumentID:e.instrument.name;if(typeof i!=="string")throw Error("fixture.instrument.name \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");let o=x(e.instrument.minOrderVolume===void 0?1:e.instrument.minOrderVolume,"fixture.instrument.minOrderVolume"),s=x(e.instrument.maxOrderVolume===void 0?1e5:e.instrument.maxOrderVolume,"fixture.instrument.maxOrderVolume");if(s<o)throw Error("fixture.instrument.maxOrderVolume \u5FC5\u987B\u4E0D\u5C0F\u4E8E minOrderVolume");let a=Ee(e.instrument.longMarginRatio===void 0?0.1:e.instrument.longMarginRatio,"fixture.instrument.longMarginRatio"),d=Ee(e.instrument.shortMarginRatio===void 0?0.1:e.instrument.shortMarginRatio,"fixture.instrument.shortMarginRatio"),c=e.instrument.isTrading===void 0?!0:e.instrument.isTrading;if(typeof c!=="boolean")throw Error("fixture.instrument.isTrading \u5FC5\u987B\u662F\u5E03\u5C14\u503C");let u=e.commission===void 0?void 0:pi(e.commission),l=e.setting===void 0?{}:ce(e.setting,"fixture.setting"),f=e.seedPrice===void 0?mi:E(e.seedPrice,"fixture.seedPrice"),h=e.tradingDay===void 0?li:X(e.tradingDay),m=/^([A-Z]+)\d+([A-Z]+)$/.exec(t.instrumentID),v=m?`${m[1]}_${m[2]}`:t.instrumentID.match(/^[A-Z]+/)?.[0]??"";if(!v)throw Error(`\u65E0\u6CD5\u4ECE ${t.instrumentID} \u63A8\u5BFC fixture \u4EA7\u54C1\u4EE3\u7801`);return{vtSymbol:t.vtSymbol,instrumentID:t.instrumentID,exchangeID:t.vtSymbol.split(".")[1],productID:v,setting:l,priceTick:r,size:n,name:i,minOrderVolume:o,maxOrderVolume:s,longMarginRatio:a,shortMarginRatio:d,isTrading:c,commission:u,seedPrice:f,tradingDay:h}}function Ge(e){let t=e.commission;if(!t)return null;return{instrumentID:e.instrumentID,exchangeID:e.exchangeID,investorRange:"1",brokerID:"test",investorID:"test",bizType:"1",investUnitID:"",openRatioByMoney:t.open.ratioByMoney,openRatioByVolume:t.open.perLot,closeRatioByMoney:t.close.ratioByMoney,closeRatioByVolume:t.close.perLot,closeTodayRatioByMoney:t.closeToday.ratioByMoney,closeTodayRatioByVolume:t.closeToday.perLot}}function pi(e){if(!ae(e))throw Error("fixture.commission \u5FC5\u987B\u662F\u5BF9\u8C61");return Te(e,["open","close","closeToday"],"fixture.commission"),{open:It(e.open,"fixture.commission.open"),close:It(e.close,"fixture.commission.close"),closeToday:It(e.closeToday,"fixture.commission.closeToday")}}function It(e,t){if(!ae(e))throw Error(`${t} \u5FC5\u987B\u662F\u5BF9\u8C61`);return Te(e,["ratioByMoney","perLot"],t),{ratioByMoney:Ee(e.ratioByMoney,`${t}.ratioByMoney`),perLot:Ee(e.perLot,`${t}.perLot`)}}function Ie(e,t,r){let n=-1;for(let i of e){if(i.code!==r)throw Error("\u6D4B\u8BD5 Bar \u5408\u7EA6\u4E0E fixture \u4E0D\u5339\u914D");if(i.barTime<=n)throw Error("\u6D4B\u8BD5 Bar \u5FC5\u987B\u6309\u65F6\u95F4\u4E25\u683C\u9012\u589E");if((i.barTime+(i.boundaryReason?0:60))*1000>t)throw Error("\u6D4B\u8BD5 Bar \u5C1A\u672A\u95ED\u5408\uFF0C\u4E0D\u80FD\u4F7F\u7528\u672A\u6765\u6570\u636E");n=i.barTime}}function Ke(e){let t=e.tradingDay;return Date.parse(`${t.slice(0,4)}-${t.slice(4,6)}-${t.slice(6,8)}T09:00:00+08:00`)}function Ot(e){let t=new Date(e+28800000);return`${t.getUTCFullYear()}${String(t.getUTCMonth()+1).padStart(2,"0")}${String(t.getUTCDate()).padStart(2,"0")}`}function H(e,t,r,n=Ke(e)/1000+r*60){if(t.timeframe!==void 0&&t.timeframe!=="1min")throw Error("bar.timeframe \u5FC5\u987B\u662F 1min");let i=E(t.close,"bar.close"),o=t.open===void 0?i:E(t.open,"bar.open"),s=t.high===void 0?Math.max(o,i):E(t.high,"bar.high"),a=t.low===void 0?Math.min(o,i):E(t.low,"bar.low");if(s<Math.max(o,i)||a>Math.min(o,i))throw Error("Bar OHLC \u5173\u7CFB\u975E\u6CD5");let d=t.volume===void 0?100+r:Ee(t.volume,"bar.volume"),c=t.barTime===void 0?n:T(t.barTime,"bar.barTime");return{code:$r(t.code??e.instrumentID),timeframe:"1min",barTime:c,open:o,high:s,low:a,close:i,volume:d,tradingDay:t.tradingDay===void 0?e.tradingDay:X(t.tradingDay),...t.boundaryReason?{boundaryReason:t.boundaryReason}:{}}}function De(e,t,r,n=Ke(e)+r*60000){let i=E(t.lastPrice,"tick.lastPrice"),o=t.datetime===void 0?n:T(t.datetime,"tick.datetime"),s=new Date(o+28800000),a=t.updateTime??[s.getUTCHours(),s.getUTCMinutes(),s.getUTCSeconds()].map((c)=>String(c).padStart(2,"0")).join(":");if(!/^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/.test(a))throw Error("tick.updateTime \u5FC5\u987B\u662F HH:MM:SS");let d=t.updateMillisec===void 0?o%1000:T(t.updateMillisec,"tick.updateMillisec");if(d>999)throw Error("tick.updateMillisec \u5FC5\u987B\u5728 0-999 \u4E4B\u95F4");return{code:$r(t.code??e.instrumentID),exchangeID:t.exchangeID??e.exchangeID,productID:t.productID??e.productID,priceTick:t.priceTick===void 0?e.priceTick:E(t.priceTick,"tick.priceTick"),lastPrice:i,updateTime:a,updateMillisec:d,actionDay:t.actionDay===void 0?Ot(o):X(t.actionDay),tradingDay:t.tradingDay===void 0?e.tradingDay:X(t.tradingDay),volume:t.volume===void 0?100+r:T(t.volume,"tick.volume"),datetime:o,bidPrice1:je(t,"bidPrice1",i,E),bidVolume1:je(t,"bidVolume1",1000,T),askPrice1:je(t,"askPrice1",i,E),askVolume1:je(t,"askVolume1",1000,T),...t.upperLimitPrice===void 0?{}:{upperLimitPrice:E(t.upperLimitPrice,"tick.upperLimitPrice")},...t.lowerLimitPrice===void 0?{}:{lowerLimitPrice:E(t.lowerLimitPrice,"tick.lowerLimitPrice")},...t.rawActionDay===void 0?{}:{rawActionDay:t.rawActionDay},...t.receivedAt===void 0?{}:{receivedAt:T(t.receivedAt,"tick.receivedAt")},...t.dateSource===void 0?{}:{dateSource:t.dateSource},...t.tradingDaySource===void 0?{}:{tradingDaySource:t.tradingDaySource}}}function $r(e){if(typeof e!=="string"||!/^[A-Za-z][A-Za-z0-9]*$/.test(e))throw Error("\u884C\u60C5 code \u975E\u6CD5");return e}function je(e,t,r,n){if(!Object.hasOwn(e,t))return r;return e[t]===void 0?void 0:n(e[t],`tick.${t}`)}function Ye(e){return{instrumentID:e.instrumentID,exchangeID:e.exchangeID,productID:e.productID,productClass:"1",exchangeInstID:e.instrumentID,instrumentName:e.name,volumeMultiple:e.size,priceTick:e.priceTick,maxLimitOrderVolume:e.maxOrderVolume,minLimitOrderVolume:e.minOrderVolume,maxMarketOrderVolume:0,minMarketOrderVolume:0,longMarginRatio:e.longMarginRatio,shortMarginRatio:e.shortMarginRatio,maxMarginSideAlgorithm:"",strikePrice:0,optionsType:"",combinationType:"",deliveryYear:0,deliveryMonth:0,createDate:"",openDate:"",expireDate:"",startDelivDate:"",endDelivDate:"",instLifePhase:"1",isTrading:e.isTrading?1:0,positionType:"",positionDateType:"",underlyingInstrID:"",underlyingMultiple:1}}function We(e){if(e===void 0)return de();if(!ae(e))throw Error("initialState.inventory \u5FC5\u987B\u662F\u5BF9\u8C61");return Te(e,["longToday","longYesterday","shortToday","shortYesterday"],"initialState.inventory"),{longToday:e.longToday===void 0?0:T(e.longToday,"longToday"),longYesterday:e.longYesterday===void 0?0:T(e.longYesterday,"longYesterday"),shortToday:e.shortToday===void 0?0:T(e.shortToday,"shortToday"),shortYesterday:e.shortYesterday===void 0?0:T(e.shortYesterday,"shortYesterday")}}function de(){return{longToday:0,longYesterday:0,shortToday:0,shortYesterday:0}}function Mr(e){return e.longToday+e.longYesterday-e.shortToday-e.shortYesterday}function gi(e,t){let r=new Date(Date.UTC(Number(e.slice(0,4)),Number(e.slice(4,6))-1,Number(e.slice(6,8))+t));return`${r.getUTCFullYear()}${String(r.getUTCMonth()+1).padStart(2,"0")}${String(r.getUTCDate()).padStart(2,"0")}`}function X(e){if(typeof e!=="string"||!/^\d{8}$/.test(e))throw Error("fixture.tradingDay \u5FC5\u987B\u662F YYYYMMDD");if(gi(e,0)!==e)throw Error(`fixture.tradingDay \u975E\u6CD5: ${e}`);return e}function ce(e,t){if(!ae(e))throw Error(`${t} \u5FC5\u987B\u662F\u5BF9\u8C61`);try{Dt(e,t,new Set);let r=JSON.stringify(e);if(r===void 0)throw Error("\u4E0D\u53EF\u5E8F\u5217\u5316");return JSON.parse(r)}catch(r){throw Error(`${t} \u5FC5\u987B\u53EF JSON \u5E8F\u5217\u5316: ${r instanceof Error?r.message:String(r)}`)}}function Dt(e,t,r){if(e===null||typeof e==="string"||typeof e==="boolean")return;if(typeof e==="number"){if(!Number.isFinite(e))throw Error(`${t} \u5305\u542B\u975E\u6709\u9650\u6570\u5B57`);return}if(typeof e!=="object")throw Error(`${t} \u5305\u542B\u4E0D\u53EF\u5E8F\u5217\u5316\u7684 ${typeof e}`);if(r.has(e))throw Error(`${t} \u5305\u542B\u5FAA\u73AF\u5F15\u7528`);if(r.add(e),Array.isArray(e))for(let n=0;n<e.length;n++)Dt(e[n],`${t}[${n}]`,r);else{let n=Object.getPrototypeOf(e);if(n!==Object.prototype&&n!==null)throw Error(`${t} \u5FC5\u987B\u662F\u666E\u901A\u5BF9\u8C61\u6216\u6570\u7EC4`);for(let[i,o]of Object.entries(e))Dt(o,`${t}.${i}`,r)}r.delete(e)}function Te(e,t,r){let n=Object.keys(e).find((i)=>!t.includes(i));if(n)throw Error(`${r} \u4E0D\u652F\u6301\u5B57\u6BB5 "${n}"`)}function E(e,t){if(typeof e!=="number"||!Number.isFinite(e)||e<=0)throw Error(`${t} \u5FC5\u987B\u662F\u6B63\u6570`);return e}function x(e,t){if(!Number.isSafeInteger(e)||e<=0)throw Error(`${t} \u5FC5\u987B\u662F\u6B63\u6574\u6570`);return e}function T(e,t){if(!Number.isSafeInteger(e)||e<0)throw Error(`${t} \u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570`);return e}function Ee(e,t){if(typeof e!=="number"||!Number.isFinite(e)||e<0)throw Error(`${t} \u5FC5\u987B\u662F\u975E\u8D1F\u6570`);return e}function ae(e){return e!==null&&typeof e==="object"&&!Array.isArray(e)}class Oe{fixture;sequence;now;observations;phase="running";instrument;orders=new Map;trades=new Map;reportedOrders=new Map;deliveredTrades=new Set;orderHandlers=new Set;tradeHandlers=new Set;errorHandlers=new Set;reconcileHandlers=new Set;faultHandlers=new Set;inventories=new Map;tradingDay;nextSubmitFailure;nextCancelFailure;holdingReports=!1;reportSequence=0;reports=new Map;pending=new Set;deliveredReports=new Set;constructor(e,t,r,n,i,o){this.fixture=e;this.sequence=r;this.now=n;this.observations=i;if(this.inventories.set(e.vtSymbol,{...t}),this.tradingDay=e.tradingDay,this.instrument=Ye(e),o)this.reconcile({...o,reason:"initial"})}snapshot(){return{phase:this.phase,tradingDay:this.tradingDay,inventory:{...this.inventories.get(this.fixture.vtSymbol)??de()},orders:[...this.orders.values()].map(L),trades:[...this.trades.values()].map((e)=>({...e}))}}holdReports(){this.holdingReports=!0}resumeReports(){this.requireConnected(),this.holdingReports=!1;for(let e of[...this.pending])this.deliverReport(e)}pendingReports(){return[...this.pending].map((e)=>structuredClone(this.reports.get(e)))}deliverReport(e){if(this.requireConnected(),!this.pending.delete(e))throw Error(`Pending broker report does not exist: ${e}`);this.deliver(this.reports.get(e))}repeatReport(e){this.requireConnected();let t=this.reports.get(e);if(!t||!this.deliveredReports.has(e))throw Error(`Delivered broker report does not exist: ${e}`);this.deliver(t,!0)}async prepareInstrument(e,t){if(e!==this.fixture.instrumentID||t!==this.fixture.exchangeID)throw Error(`\u6D4B\u8BD5\u5408\u7EA6\u4E0D\u5339\u914D: ${e}.${t}`);return this.instrument}async prepareCommission(e,t){if(e!==this.fixture.instrumentID||t!==this.fixture.exchangeID)throw Error(`\u6D4B\u8BD5\u624B\u7EED\u8D39\u5408\u7EA6\u4E0D\u5339\u914D: ${e}.${t}`)}getInstrument(e){return e===this.fixture.instrumentID?this.instrument:null}getCommission(e,t){if(e!==this.fixture.instrumentID||t!==this.fixture.exchangeID)return null;return Ge(this.fixture)}async queryPositions(e){return{phase:this.phase,positions:this.positionSnapshot().filter((t)=>!e||t.instrumentID===e.toUpperCase())}}async queryOrders(e){return[...this.orders.values()].filter((t)=>!e||t.instrumentID===e.toUpperCase()).map((t)=>L(t))}submitOrder(e){if(this.requireConnected(),this.nextSubmitFailure){let o=this.nextSubmitFailure;throw this.nextSubmitFailure=void 0,Error(o)}let t,r;do t=String(++this.sequence.order),r=`CTP.${this.tradingDay}.1.1.${t}`;while(this.orders.has(r));let n=e.direction==="buy"?"long":"short",i={vtOrderId:r,identity:{tradingDay:this.tradingDay,frontID:1,sessionID:1,orderRef:t,instrumentID:this.fixture.instrumentID,exchangeID:this.fixture.exchangeID},vtSymbol:this.fixture.vtSymbol,instrumentID:this.fixture.instrumentID,exchangeID:this.fixture.exchangeID,orderSysID:`TEST-${t}`,side:e.direction,direction:n,offset:e.offsetFlag,price:e.price,volume:e.volume,traded:0,status:"submitting",statusMessage:"",datetime:this.now()};return this.orders.set(r,i),this.reportedOrders.set(r,L(i)),this.observations.submissions.push({vtOrderId:r,direction:n,offset:e.offsetFlag,price:e.price,volume:e.volume}),this.observations.record("submission",this.observations.submissions.at(-1)),{vtOrderId:r,orderRef:t}}cancelOrder(e){if(this.observations.cancelRequests.push(e),this.observations.record("cancel",{vtOrderId:e}),this.requireConnected(),this.nextCancelFailure){let t=this.nextCancelFailure;throw this.nextCancelFailure=void 0,Error(t)}}onOrder(e){return this.orderHandlers.add(e),()=>this.orderHandlers.delete(e)}onTrade(e){return this.tradeHandlers.add(e),()=>this.tradeHandlers.delete(e)}onOrderError(e){return this.errorHandlers.add(e),()=>this.errorHandlers.delete(e)}onReconciled(e){return this.reconcileHandlers.add(e),()=>this.reconcileHandlers.delete(e)}onFault(e){return this.faultHandlers.add(e),()=>this.faultHandlers.delete(e)}failNextSubmit(e="test submit failed"){this.nextSubmitFailure=e}rejectSubmit(e,t="test submit rejected"){let r=this.requireActiveOrder(e);if(r.traded!==0||this.tradedVolume(e)>0)throw Error("\u5DF2\u6210\u4EA4\u59D4\u6258\u4E0D\u80FD\u62D2\u5355");if(r.status="rejected",r.statusMessage=t,this.phase!=="running")return;this.reportedOrders.set(e,L(r)),this.queueReport({type:"error",value:{vtOrderId:e,orderRef:r.identity.orderRef,instrumentID:r.instrumentID,operation:"submit",message:t}})}rejectCancel(e,t="test cancel rejected"){let r=this.requireOrder(e);this.requireConnected(),this.queueReport({type:"error",value:{vtOrderId:e,orderRef:r.identity.orderRef,instrumentID:r.instrumentID,operation:"cancel",message:t}})}emitExternalOrder(e){if(this.orders.has(e.vtOrderId))throw Error(`\u6D4B\u8BD5\u59D4\u6258\u5DF2\u5B58\u5728: ${e.vtOrderId}`);this.emitOrder(e)}emitOrder(e){let t=this.normalizeOrder(e);this.orders.set(t.vtOrderId,t),this.publishOrder(t)}emitTrade(e){let t=this.recordTrade(e);this.publishTrade(t)}disconnect(){if(this.phase!=="faulted")this.phase="recovering"}connect(){this.phase="running",this.reportedOrders.clear(),this.deliveredTrades.clear(),this.nextSubmitFailure=void 0,this.nextCancelFailure=void 0,this.pending.clear(),this.holdingReports=!1,this.reconcile({reason:"initial"})}reconcile(e={}){if(this.phase==="faulted")throw Error("\u6D4B\u8BD5 Broker \u5DF2 fault\uFF0C\u5FC5\u987B\u91CD\u542F Runtime");this.phase="recovering",this.pending.clear();try{let t=e.tradingDay===void 0?this.tradingDay:X(e.tradingDay);if(t<this.tradingDay)throw Error("\u6D4B\u8BD5 Broker TradingDay \u4E0D\u80FD\u56DE\u9000");if(t!==this.tradingDay)for(let o of this.inventories.values())o.longYesterday+=o.longToday,o.longToday=0,o.shortYesterday+=o.shortToday,o.shortToday=0;this.tradingDay=t;let r=e.orders===void 0?[...this.orders.values()].filter((o)=>o.identity.tradingDay===t).map(L):e.orders.map((o)=>this.normalizeOrder(o));if(e.orders!==void 0){for(let o of this.orders.values())if(o.identity.tradingDay===t)this.orders.delete(o.vtOrderId);for(let o of r)this.orders.set(o.vtOrderId,o)}for(let o of e.trades??[])this.recordTrade(o);if(e.inventory!==void 0)this.inventories.set(this.fixture.vtSymbol,We(e.inventory));let n=[...this.trades.values()].filter((o)=>o.tradingDay===t),i={reason:e.reason??"reconnect",tradingDay:t,accounts:[],positions:this.positionSnapshot(),orders:r,trades:n,recoveredAt:this.now()};if(i.reason==="reconnect"){for(let o of this.reportedOrders.values())if(w(o)&&o.identity.tradingDay===t&&!r.some((s)=>s.vtOrderId===o.vtOrderId))throw Error(`\u6D3B\u52A8\u59D4\u6258\u672A\u51FA\u73B0\u5728\u6062\u590D\u5FEB\u7167: ${o.vtOrderId}`);for(let o of r)this.publishOrder(o,!0);for(let o of n)this.publishTrade(o,!0)}else{for(let o of r)this.reportedOrders.set(o.vtOrderId,L(o));for(let o of n)this.deliveredTrades.add(o.vtTradeId)}for(let o of this.reconcileHandlers)o(i);this.phase="running"}catch(t){throw this.fault(t instanceof Error?t.message:String(t)),t}}fault(e="test gateway fault"){if(this.phase==="faulted")return;this.phase="faulted";for(let t of this.faultHandlers)t(e)}acknowledge(e){this.updateOrder(e,(t)=>{if(t.traded!==0||this.tradedVolume(e)>0)throw Error("\u5DF2\u6210\u4EA4\u59D4\u6258\u4E0D\u80FD\u91CD\u65B0\u786E\u8BA4");t.status="notTraded",t.statusMessage="test acknowledged"})}partialFill(e,t,r){this.fillVolume(e,t,r)}fill(e,t){let r=this.requireActiveOrder(e);this.fillVolume(e,r.volume-Math.max(r.traded,this.tradedVolume(e)),t)}reject(e,t="test rejected"){this.updateOrder(e,(r)=>{if(r.traded!==0||this.tradedVolume(e)>0)throw Error("\u5DF2\u6210\u4EA4\u59D4\u6258\u4E0D\u80FD\u62D2\u5355");r.status="rejected",r.statusMessage=t})}cancel(e,t="test cancelled"){this.updateOrder(e,(r)=>{r.traded=Math.max(r.traded,this.tradedVolume(e)),r.status="cancelled",r.statusMessage=t})}failNextCancel(e="test cancel failed"){this.nextCancelFailure=e}settleActiveOrders(){for(let e of[...this.orders.values()])if(e.status==="submitting"||e.status==="notTraded"||e.status==="partTraded")this.cancel(e.vtOrderId,"test cleanup")}fillVolume(e,t,r){if(!Number.isInteger(t)||t<=0)throw Error("\u6210\u4EA4\u6570\u91CF\u5FC5\u987B\u662F\u6B63\u6574\u6570");let n=this.requireActiveOrder(e),i=n.volume-Math.max(n.traded,this.tradedVolume(e));if(t>i)throw Error(`\u6210\u4EA4\u6570\u91CF ${t} \u8D85\u8FC7\u5269\u4F59\u6570\u91CF ${i}`);let o=E(r??n.price,"\u6210\u4EA4\u4EF7\u683C");n.traded=n.volume-i+t,n.status=n.traded===n.volume?"allTraded":"partTraded",n.statusMessage="test filled",this.publishOrder(n);let s;do s=`TESTTRADE.${++this.sequence.trade}`;while(this.trades.has(s));this.emitTrade({vtTradeId:s,vtOrderId:n.vtOrderId,price:o,volume:t,datetime:this.now(),tradingDay:this.tradingDay})}updateOrder(e,t){let r=this.requireActiveOrder(e);t(r),this.publishOrder(r)}normalizeOrder(e){let t=this.orders.get(e.vtOrderId),r=hi({...this.fixture,tradingDay:this.tradingDay},{...e,vtSymbol:e.vtSymbol??t?.vtSymbol},this.now());if(t){if(t.vtSymbol!==r.vtSymbol||t.direction!==r.direction||t.offset!==r.offset||t.volume!==r.volume)throw Error(`\u6D4B\u8BD5\u59D4\u6258\u8EAB\u4EFD\u5B57\u6BB5\u53D1\u751F\u53D8\u5316: ${r.vtOrderId}`);if(r.identity={...t.identity},r.orderSysID=t.orderSysID,r.traded<t.traded||Ar(r)<Ar(t))return L(t)}return r}tradedVolume(e){let t=0;for(let r of this.trades.values())if(r.vtOrderId===e)t+=r.volume;return t}recordTrade(e){if(typeof e.vtTradeId!=="string"||!e.vtTradeId.trim())throw Error("\u6D4B\u8BD5\u6210\u4EA4 ID \u4E0D\u80FD\u4E3A\u7A7A");let t=this.trades.get(e.vtTradeId);if(t)return t;let r=this.requireOrder(e.vtOrderId),n=x(e.volume,"\u6210\u4EA4\u6570\u91CF"),i=this.tradedVolume(r.vtOrderId)+n;if(i>r.volume||!w(r)&&i>r.traded)throw Error("\u6D4B\u8BD5\u6210\u4EA4\u6570\u91CF\u8D85\u8FC7\u59D4\u6258\u5B9E\u9645\u6210\u4EA4\u8303\u56F4");let o={vtTradeId:e.vtTradeId,vtOrderId:r.vtOrderId,vtSymbol:r.vtSymbol,instrumentID:r.instrumentID,exchangeID:r.exchangeID,orderSysID:r.orderSysID,tradeID:e.vtTradeId,side:r.side,direction:r.direction,offset:r.offset,price:E(e.price??r.price,"\u6210\u4EA4\u4EF7\u683C"),volume:n,datetime:e.datetime===void 0?this.now():T(e.datetime,"\u6210\u4EA4\u65F6\u95F4"),tradingDay:e.tradingDay===void 0?this.tradingDay:X(e.tradingDay)};if(o.tradingDay!==this.tradingDay)throw Error("\u6D4B\u8BD5\u6210\u4EA4 TradingDay \u4E0E Broker \u4E0D\u4E00\u81F4");this.trades.set(o.vtTradeId,o);let s=this.inventories.get(o.vtSymbol)??de();return this.inventories.set(o.vtSymbol,s),yi(s,o.direction,o.offset,o.volume),o}positionSnapshot(){return[...this.inventories].flatMap(([e,t])=>fi({...this.fixture,...K(e),exchangeID:e.split(".")[1],tradingDay:this.tradingDay},t))}publishOrder(e,t=!1){if(!t&&this.phase!=="running")return;this.queueReport({type:"order",value:L(e)},t)}publishTrade(e,t=!1){if(!t&&this.phase!=="running")return;this.queueReport({type:"trade",value:{...e}},t)}queueReport(e,t=!1){let r={...e,id:`report-${++this.reportSequence}`,createdAt:this.now()};if(this.reports.set(r.id,r),this.observations.record("broker",{operation:"queued",report:r}),!this.holdingReports||t)this.deliver(r);else this.pending.add(r.id)}deliver(e,t=!1){if(this.deliveredReports.add(e.id),this.observations.record("broker",{operation:"delivered",duplicate:t,report:e}),e.type==="trade"){let i=e.value;if(!t&&this.deliveredTrades.has(i.vtTradeId))return;this.deliveredTrades.add(i.vtTradeId);for(let o of this.tradeHandlers)o({...i});return}if(e.type==="error"){for(let i of this.errorHandlers)i({...e.value});return}let r=e.value,n=this.reportedOrders.get(r.vtOrderId);if(!t&&n&&n.status===r.status&&n.traded===r.traded&&n.statusMessage===r.statusMessage&&n.orderSysID===r.orderSysID)return;this.reportedOrders.set(r.vtOrderId,L(r));for(let i of this.orderHandlers)i(L(r))}requireConnected(){if(this.phase!=="running")throw Error(`\u6D4B\u8BD5 Broker \u672A\u5C31\u7EEA: ${this.phase}`)}requireOrder(e){let t=this.orders.get(e);if(!t)throw Error(`\u6D4B\u8BD5\u59D4\u6258\u4E0D\u5B58\u5728: ${e}`);return t}requireActiveOrder(e){let t=this.requireOrder(e);if(!w(t))throw Error(`\u6D4B\u8BD5\u59D4\u6258\u5DF2\u7EC8\u6B62: ${e} (${t.status})`);return t}}function Ar(e){if(e.status==="submitting")return 0;if(e.status==="notTraded")return 1;if(e.status==="partTraded")return 2;return 3}function hi(e,t,r){if(typeof t.vtOrderId!=="string"||!t.vtOrderId.trim())throw Error("\u6D4B\u8BD5\u59D4\u6258 ID \u4E0D\u80FD\u4E3A\u7A7A");let{vtSymbol:n,instrumentID:i}=K(t.vtSymbol??e.vtSymbol),o=n.split(".")[1];if(t.direction!=="long"&&t.direction!=="short")throw Error("\u6D4B\u8BD5\u59D4\u6258\u65B9\u5411\u975E\u6CD5");let s=t.offset??"open";if(!["open","close","closeToday","closeYesterday"].includes(s))throw Error("\u6D4B\u8BD5\u59D4\u6258\u5F00\u5E73\u975E\u6CD5");let a=x(t.volume,"\u6D4B\u8BD5\u59D4\u6258\u6570\u91CF"),d=t.traded===void 0?0:T(t.traded,"\u6D4B\u8BD5\u59D4\u6258\u5DF2\u6210\u4EA4\u6570\u91CF"),c=t.status??"notTraded";if(!["submitting","notTraded","partTraded","allTraded","cancelled","rejected"].includes(c)||d>a||(c==="submitting"||c==="notTraded"||c==="rejected")&&d!==0||c==="partTraded"&&(d===0||d===a)||c==="allTraded"&&d!==a)throw Error("\u6D4B\u8BD5\u59D4\u6258\u72B6\u6001\u4E0E\u6210\u4EA4\u6570\u91CF\u4E0D\u4E00\u81F4");return{vtOrderId:t.vtOrderId,identity:{tradingDay:e.tradingDay,frontID:1,sessionID:1,orderRef:t.vtOrderId.split(".").at(-1),instrumentID:i,exchangeID:o},vtSymbol:n,instrumentID:i,exchangeID:o,orderSysID:`TEST-${t.vtOrderId}`,side:t.direction==="long"?"buy":"sell",direction:t.direction,offset:s,price:E(t.price,"\u6D4B\u8BD5\u59D4\u6258\u4EF7\u683C"),volume:a,traded:d,status:c,statusMessage:t.statusMessage??"",datetime:t.datetime===void 0?r:T(t.datetime,"\u6D4B\u8BD5\u59D4\u6258\u65F6\u95F4")}}function fi(e,t){let r=[],n=(i,o,s)=>{if(o+s===0)return;r.push({instrumentID:e.instrumentID,exchangeID:e.exchangeID,vtSymbol:e.vtSymbol,direction:i,hedgeFlag:"1",investUnitID:"",tradingDay:e.tradingDay,volume:o+s,todayVolume:o,yesterdayVolume:s,frozen:0,price:e.seedPrice,pnl:0,margin:0,positionCost:0})};return n("long",t.longToday,t.longYesterday),n("short",t.shortToday,t.shortYesterday),r}function yi(e,t,r,n){if(r==="open"){if(t==="long")e.longToday+=n;else e.shortToday+=n;return}let i=t==="short"?"longToday":"shortToday",o=t==="short"?"longYesterday":"shortYesterday";if(r==="closeToday")e[i]=Math.max(0,e[i]-n);else if(r==="closeYesterday")e[o]=Math.max(0,e[o]-n);else{let s=Math.min(e[o],n);e[o]-=s,e[i]=Math.max(0,e[i]-(n-s))}}function L(e){return{...e,identity:{...e.identity}}}class Qe extends Error{method;code;constructor(e,t,r){super(r);this.method=e;this.code=t;this.name="GatewayRpcError"}}var Je=(e)=>e.toUpperCase();function Re(e){return e.map((t)=>({...t,instrumentID:Je(t.instrumentID)}))}class Rt{transport;loginResult=null;connected=!1;queryQueue=Promise.resolve();lastQueryAt=0;minQueryIntervalMs;queryTimeoutMs;normalizeInstrumentID;cleanups;pendingQueries=new Set;disposed=!1;clock;constructor(e,t={}){this.transport=e;this.minQueryIntervalMs=t.minQueryIntervalMs??0,this.queryTimeoutMs=t.queryTimeoutMs??15000,this.normalizeInstrumentID=t.normalizeInstrumentID??((r)=>r),this.clock=t.clock??{now:Date.now,schedule:(r,n)=>{let i=setTimeout(r,n);return()=>clearTimeout(i)}},this.cleanups=[e.on("onDisconnected",()=>{this.connected=!1}),e.on("onReconnecting",()=>{this.connected=!1}),e.on("onReconnected",(r)=>{this.connected=!0,this.loginResult=r.params}),e.onExit(()=>{this.connected=!1})]}async login(e){let t=await this.request("login",e,"\u4EA4\u6613\u767B\u5F55\u5931\u8D25");return this.loginResult=t,this.connected=!0,t}async logout(e,t){await this.request("logout",{broker:e,user:t},"\u767B\u51FA\u5931\u8D25"),this.loginResult=null,this.connected=!1}async changePassword(e){await this.request("changePassword",e,"\u4FEE\u6539\u5BC6\u7801\u5931\u8D25")}async insertOrder(e){let t=this.loginResult,r=await this.request("insertOrder",this.normalizeParams(e),"\u4E0B\u5355\u5931\u8D25"),n=r.tradingDay??t?.tradingDay,i=r.frontID??t?.frontID,o=r.sessionID??t?.sessionID;if(!n||!i||!o)throw Error("\u4E0B\u5355\u6210\u529F\u4F46\u4EA4\u6613\u4F1A\u8BDD\u8EAB\u4EFD\u4E0D\u53EF\u7528");return{tradingDay:n,frontID:i,sessionID:o,orderRef:r.orderRef,requestID:r.requestID}}cancelOrder(e){return this.request("cancelOrder",this.normalizeParams(e),"\u64A4\u5355\u5931\u8D25")}queryAccount(e,t){return this.query(()=>this.requestAndWait("queryAccount",{broker:e,investorId:t},"onQueryAccountResult",(r)=>r.accounts??[],"\u67E5\u8BE2\u8D26\u6237\u5931\u8D25",!0))}queryPositions(e,t,r){return this.query(()=>this.requestAndWait("queryPosition",{broker:e,investorId:t,instrumentID:r?this.normalizeInstrumentID(r):""},"onQueryPositionResult",(n)=>Re(n.positions??[]),"\u67E5\u8BE2\u6301\u4ED3\u5931\u8D25",!0))}queryOrders(e){return this.query(()=>this.requestAndWait("queryOrder",this.normalizeParams(e),"onQueryOrderResult",(t)=>Re(t.orders??[]),"\u67E5\u8BE2\u59D4\u6258\u5931\u8D25",!0))}queryTrades(e){return this.query(()=>this.requestAndWait("queryTrade",this.normalizeParams(e),"onQueryTradeResult",(t)=>Re(t.trades??[]),"\u67E5\u8BE2\u6210\u4EA4\u5931\u8D25",!0))}querySettlementInfo(e){return this.query(()=>this.requestAndWait("querySettlementInfo",e,"onQuerySettlementInfoResult",(t)=>t.settlementInfos??t.settlements??[],"\u67E5\u8BE2\u7ED3\u7B97\u5355\u5931\u8D25",!0))}queryInstrument(e={}){return this.query(()=>this.requestAndWait("queryInstrument",this.normalizeParams(e),"onQueryInstrumentResult",(t)=>Re(t.instruments??[]),"\u67E5\u8BE2\u5408\u7EA6\u89C4\u683C\u5931\u8D25",!0,e.instrumentID||e.exchangeID||e.exchangeInstID||e.productID?this.queryTimeoutMs:60000))}queryCommissionRates(e){return this.query(()=>this.requestAndWait("queryInstrumentCommissionRate",this.normalizeParams(e),"onQueryCommissionRateResult",(t)=>Re(t.commissionRates??[]),"\u67E5\u8BE2\u624B\u7EED\u8D39\u7387\u5931\u8D25",!0,e.instrumentID?this.queryTimeoutMs:60000))}async queryCommissionRate(e){return(await this.queryCommissionRates(e))[0]??null}onOrder(e){return this.transport.on("onOrder",(t)=>{let r=t.params;e({...r,instrumentID:Je(r.instrumentID)})})}onTrade(e){return this.transport.on("onTrade",(t)=>{let r=t.params;e({...r,instrumentID:Je(r.instrumentID)})})}onOrderError(e){return this.transport.on("onOrderError",(t)=>{let r=t.params;e({...r,instrumentID:r.instrumentID?Je(r.instrumentID):void 0})})}onConnection(e,t){return this.transport.on(e,t)}dispose(){this.disposed=!0;for(let e of[...this.pendingQueries])e();for(let e of this.cleanups.splice(0))e()}query(e){let t=this.queryQueue.then(async()=>{if(this.disposed)throw Error("Trader gateway API disposed");let r=Math.max(0,this.minQueryIntervalMs-(this.clock.now()-this.lastQueryAt));if(r>0)await new Promise((n,i)=>{let o=()=>{s(),this.pendingQueries.delete(o),i(Error("Trader gateway API disposed"))},s=this.clock.schedule(()=>{this.pendingQueries.delete(o),n()},r);this.pendingQueries.add(o)});if(this.disposed)throw Error("Trader gateway API disposed");try{return await e()}finally{this.lastQueryAt=this.clock.now()}});return this.queryQueue=t.then(()=>{return},()=>{return}),t}requestAndWait(e,t,r,n,i,o=!1,s=this.queryTimeoutMs){return new Promise((a,d)=>{let c=null,u=null,l=null,f=[],h=()=>{c?.(),c=null,u?.(),u=null,this.pendingQueries.delete(m)},m=()=>{h(),d(Error("Trader gateway API disposed"))};this.pendingQueries.add(m),c=this.clock.schedule(()=>{h(),d(Error(`${e} \u67E5\u8BE2\u8D85\u65F6`))},s);let v=(p)=>{h();let k=p.params;if(k.ok===!1)d(Error(k.error??`${e} \u67E5\u8BE2\u5931\u8D25`));else a(n(p.params))};u=this.transport.on(r,(p)=>{if(!o){v(p);return}if(l===null){f.push(p);return}if(p.params.requestID===l)v(p)}),this.transport.request(e,t).then((p)=>{if(!p.ok){h(),d(Error(p.error?.msg??i));return}if(o){let k=p.data?.requestID;if(!Number.isInteger(k)){h(),d(Error(`${e} \u672A\u8FD4\u56DE\u6709\u6548 requestID`));return}l=k;let V=f.find((M)=>M.params.requestID===l);if(V)v(V)}}).catch((p)=>{h(),d(p)})})}async request(e,t,r){let n=await this.transport.request(e,t);if(!n.ok)throw new Qe(e,n.error?.code,n.error?.msg??r);return n.data}normalizeParams(e){if(!e.instrumentID&&!e.productID)return e;return{...e,...e.instrumentID?{instrumentID:this.normalizeInstrumentID(e.instrumentID)}:{},...e.productID?{productID:this.normalizeInstrumentID(e.productID)}:{}}}}var wi=28800000,Si=Number.MAX_VALUE/2,Br=1e5;class Ct extends Error{}function b(e,t){let r=e?.trim()??"";if(!r)throw Error(`${t} \u4E3A\u7A7A`);return r}function vi(e){if(!/^\d{8}$/.test(e))return!1;let t=Number(e.slice(0,4)),r=Number(e.slice(4,6)),n=Number(e.slice(6,8)),i=new Date(Date.UTC(t,r-1,n));return i.getUTCFullYear()===t&&i.getUTCMonth()===r-1&&i.getUTCDate()===n}function _(e,t){let r=e?.trim()??"";if(!vi(r))throw Error(`${t} \u975E\u6CD5: ${r}`);return r}function N(e,t){if(!Number.isInteger(e)||e<0)throw Error(`${t} \u975E\u6CD5: ${e}`);return e}function Xe(e,t){if(!Number.isInteger(e)||e<-2147483648||e>2147483647)throw Error(`${t} \u975E\u6CD5: ${e}`);return e}function Ze(e,t){if(!Number.isFinite(e))throw Error(`${t} \u975E\u6CD5: ${e}`);return e}function Pt(e,t){if(!Number.isFinite(e)||e<=0||e>=Si)throw Error(`${t} \u975E\u6CD5: ${e}`);return e}function $t(e,t){if(!Number.isInteger(e)||e<=0)throw Error(`${t} \u975E\u6CD5: ${e}`);return e}function Nr(e,t,r){let n=_(e,`${r}\u65E5\u671F`),i=/^(\d{2}):(\d{2}):(\d{2})$/.exec(t);if(!i)throw Error(`${r}\u65F6\u95F4\u975E\u6CD5: ${t}`);let o=Number(i[1]),s=Number(i[2]),a=Number(i[3]);if(o>23||s>59||a>59)throw Error(`${r}\u65F6\u95F4\u975E\u6CD5: ${t}`);return Date.UTC(Number(n.slice(0,4)),Number(n.slice(4,6))-1,Number(n.slice(6,8)),o,s,a)-wi}function Fr(e){if(e==="0")return{side:"buy",direction:"long"};if(e==="1")return{side:"sell",direction:"short"};throw Error(`CTP Direction \u4E0D\u652F\u6301: ${e}`)}function qr(e){switch(e){case"0":return"open";case"1":return"close";case"3":return"closeToday";case"4":return"closeYesterday";default:throw Error(`CTP OffsetFlag \u4E0D\u652F\u6301: ${e}`)}}function ki(e){if(!["0","1","2","3","4","5","6"].includes(e.orderSubmitStatus))throw Error(`CTP OrderSubmitStatus \u4E0D\u652F\u6301: ${e.orderSubmitStatus}`);if(e.orderSubmitStatus==="4")return"rejected";switch(e.orderStatus){case"0":return"allTraded";case"1":return"partTraded";case"2":case"4":case"5":return"cancelled";case"3":case"b":case"c":return"notTraded";case"a":return"submitting";default:throw Error(`CTP OrderStatus \u4E0D\u652F\u6301: ${e.orderStatus}`)}}function Hr(e,t){return`${e}|${t}`}function Lr(e,t){return`${e}|${t}`}function _t(e){let t=_(e.tradingDay,"\u59D4\u6258 TradingDay"),r=N(e.frontID,"\u59D4\u6258 FrontID"),n=Xe(e.sessionID,"\u59D4\u6258 SessionID"),i=b(e.orderRef,"\u59D4\u6258 OrderRef");return`CTP.${t}.${r}.${n}.${i}`}function xt(e){let t=b(e.instrumentID,"\u59D4\u6258 InstrumentID").toUpperCase(),r=b(e.exchangeID,"\u59D4\u6258 ExchangeID").toUpperCase(),n=_(e.tradingDay,"\u59D4\u6258 TradingDay"),i=b(e.orderRef,"\u59D4\u6258 OrderRef"),o={tradingDay:n,frontID:N(e.frontID,"\u59D4\u6258 FrontID"),sessionID:Xe(e.sessionID,"\u59D4\u6258 SessionID"),orderRef:i,instrumentID:t,exchangeID:r},s=Fr(e.direction),a=$t(e.volumeTotalOriginal,"\u59D4\u6258\u6570\u91CF"),d=N(e.volumeTraded,"\u59D4\u6258\u6210\u4EA4\u6570\u91CF");if(d>a)throw Error(`\u59D4\u6258\u6210\u4EA4\u6570\u91CF\u5927\u4E8E\u59D4\u6258\u6570\u91CF: ${d}/${a}`);return{vtOrderId:_t(o),identity:o,vtSymbol:`${t}.${r}`,instrumentID:t,exchangeID:r,orderSysID:e.orderSysID.trim(),...s,offset:qr(e.combOffsetFlag),price:Pt(e.limitPrice,"\u59D4\u6258\u4EF7\u683C"),volume:a,traded:d,status:ki(e),statusMessage:e.statusMsg,datetime:Nr(e.insertDate,e.insertTime,"\u59D4\u6258"),...e.traceId?{traceId:e.traceId}:{}}}function Ti(e,t,r){let n=b(e.instrumentID,"\u6210\u4EA4 InstrumentID").toUpperCase(),i=b(e.exchangeID,"\u6210\u4EA4 ExchangeID").toUpperCase(),o=_(e.tradingDay||r,"\u6210\u4EA4 TradingDay"),s=b(e.orderSysID,"\u6210\u4EA4 OrderSysID"),a=b(e.orderRef,"\u6210\u4EA4 OrderRef"),d=t(i,s,o,a);if(!d)throw new Ct(`\u6210\u4EA4\u65E0\u6CD5\u5173\u8054\u59D4\u6258: ${i}/${s}/${a}`);let c=b(e.tradeID,"\u6210\u4EA4 TradeID"),u=Fr(e.direction);return{vtTradeId:`CTP.${o}.${i}.${c}`,vtOrderId:d,vtSymbol:`${n}.${i}`,instrumentID:n,exchangeID:i,orderSysID:s,tradeID:c,...u,offset:qr(e.offsetFlag),price:Pt(e.price,"\u6210\u4EA4\u4EF7\u683C"),volume:$t(e.volume,"\u6210\u4EA4\u6570\u91CF"),datetime:Nr(e.tradeDate,e.tradeTime,"\u6210\u4EA4"),tradingDay:o,...e.traceId?{traceId:e.traceId}:{}}}function Ei(e,t=new Map){let r=new Map;for(let n of e){let i=b(n.instrumentID,"\u6301\u4ED3 InstrumentID").toUpperCase(),o=b(n.exchangeID,`\u6301\u4ED3 ${i} ExchangeID`).toUpperCase(),s=N(n.position,`\u6301\u4ED3 ${i} Position`),a=N(n.todayPosition,`\u6301\u4ED3 ${i} TodayPosition`);if(N(n.ydPosition,`\u6301\u4ED3 ${i} YdPosition`),a>s)throw Error(`\u6301\u4ED3 ${i} TodayPosition \u5927\u4E8E Position`);if(n.positionDate!=="1"&&n.positionDate!=="2")throw Error(`\u6301\u4ED3 ${i} PositionDate \u4E0D\u652F\u6301: ${n.positionDate}`);if(n.direction==="1"){if(s!==0)throw Error(`\u6682\u4E0D\u652F\u6301\u51C0\u6301\u4ED3\u8BB0\u5F55: ${i}`);continue}let d=n.direction==="2"?"long":n.direction==="3"?"short":(()=>{throw Error(`\u6301\u4ED3 ${i} PosiDirection \u4E0D\u652F\u6301: ${n.direction}`)})(),c=b(n.hedgeFlag,`\u6301\u4ED3 ${i} HedgeFlag`),u=n.investUnitID.trim(),l=_(n.tradingDay,`\u6301\u4ED3 ${i} TradingDay`),f=`${i}|${o}|${d}|${c}|${u}`,h=r.get(f);if(!h)h={instrumentID:i,exchangeID:o,direction:d,hedgeFlag:c,investUnitID:u,tradingDay:l,volume:0,yesterdayVolume:0,frozen:0,pnl:0,margin:0,positionCost:0},r.set(f,h);else if(h.tradingDay!==l)throw Error(`\u6301\u4ED3 ${i} \u6DF7\u5165\u591A\u4E2A TradingDay`);if(h.volume+=s,o==="SHFE"||o==="INE"){if(n.ydPosition>0&&a===0)h.yesterdayVolume+=s}else h.yesterdayVolume+=s-a;h.frozen+=d==="long"?N(n.shortFrozen,`\u6301\u4ED3 ${i} ShortFrozen`):N(n.longFrozen,`\u6301\u4ED3 ${i} LongFrozen`),h.pnl+=Ze(n.positionProfit,`\u6301\u4ED3 ${i} PositionProfit`),h.margin+=Ze(n.useMargin,`\u6301\u4ED3 ${i} UseMargin`),h.positionCost+=Ze(n.positionCost,`\u6301\u4ED3 ${i} PositionCost`)}return[...r.values()].map((n)=>{if(n.yesterdayVolume>n.volume)throw Error(`\u6301\u4ED3 ${n.instrumentID} \u6628\u4ED3\u5927\u4E8E\u603B\u4ED3`);let i=t.get(n.instrumentID)?.volumeMultiple??0,o=n.volume>0&&i>0?n.positionCost/(n.volume*i):0;return{instrumentID:n.instrumentID,exchangeID:n.exchangeID,vtSymbol:`${n.instrumentID}.${n.exchangeID}`,direction:n.direction,hedgeFlag:n.hedgeFlag,investUnitID:n.investUnitID,tradingDay:n.tradingDay,volume:n.volume,todayVolume:n.volume-n.yesterdayVolume,yesterdayVolume:n.yesterdayVolume,frozen:n.frozen,price:o,pnl:n.pnl,margin:n.margin,positionCost:n.positionCost}})}function U(e){return e.status==="submitting"||e.status==="notTraded"||e.status==="partTraded"}class Mt{api;options;contracts=new Map;orders=new Map;orderIdBySys=new Map;orderIdByRef=new Map;ambiguousOrderRefs=new Set;seenTrades=new Set;tradeLedgerDay="";pendingCancels=new Set;unresolvedTrades=[];pendingEvents=[];orderHandlers=new Set;tradeHandlers=new Set;errorHandlers=new Set;phaseHandlers=new Set;reconcileHandlers=new Set;faultHandlers=new Set;cleanups=[];session=null;currentSnapshot=null;recoveryTask=null;recoveryObservedOrderIds=null;_phase="idle";constructor(e,t){this.api=e;this.options=t;b(t.broker,"\u9002\u914D\u5668 BrokerID"),b(t.investorId,"\u9002\u914D\u5668 InvestorID"),this.cleanups.push(e.onOrder((r)=>this.receive({kind:"order",value:r})),e.onTrade((r)=>this.receive({kind:"trade",value:r})),e.onOrderError((r)=>this.receive({kind:"error",value:r})),e.onConnection("onDisconnected",()=>this.beginRecovery()),e.onConnection("onReconnecting",()=>this.beginRecovery()),e.onConnection("onReconnected",(r)=>{try{let n=r.params;this.setSession(n),this.reconcile(!0).catch((i)=>this.fail(i))}catch(n){this.fail(n)}}),e.transport.on("onReconnectFailed",(r)=>{this.fail(Error(String(r.params.error??"CTP \u91CD\u8FDE\u5931\u8D25")))}))}get phase(){return this._phase}get snapshot(){return this.currentSnapshot}async initialize(e){return this.setSession(e),this.reconcile(!1)}registerInstrument(e){this.contracts.set(e.instrumentID.toUpperCase(),e)}async queryPositions(e){this.ensureReady();let t=await this.api.queryPositions(this.options.broker,this.options.investorId,e);return this.normalizePositions(t)}async queryOrders(e){this.ensureReady();let r=(await this.api.queryOrders({broker:this.options.broker,investorId:this.options.investorId,instrumentID:e})).map((i)=>(this.validateOwner(i,"\u59D4\u6258"),this.acceptOrder(xt(i),!1))),n=new Map(this.cachedOrders(e).map((i)=>[i.vtOrderId,i]));for(let i of r)n.set(i.vtOrderId,i);return[...n.values()].sort((i,o)=>o.datetime-i.datetime)}cachedOrders(e){let t=e?.toUpperCase();return[...this.orders.values()].filter((r)=>!t||r.instrumentID===t).sort((r,n)=>n.datetime-r.datetime)}async queryAccounts(){this.ensureReady();let e=await this.api.queryAccount(this.options.broker,this.options.investorId);return this.validateAccounts(e,this.requireSession().tradingDay),e}async queryTrades(e){return this.ensureReady(),(await this.api.queryTrades({broker:this.options.broker,investorId:this.options.investorId,instrumentID:e})).map((r)=>(this.validateOwner(r,"\u6210\u4EA4"),this.normalizeTrade(r)))}submitOrder(e){if(this.ensureReady(),e.direction!=="buy"&&e.direction!=="sell")throw Error(`\u59D4\u6258\u65B9\u5411\u4E0D\u652F\u6301: ${String(e.direction)}`);if(!["open","close","closeToday","closeYesterday"].includes(e.offsetFlag))throw Error(`\u59D4\u6258\u5F00\u5E73\u4E0D\u652F\u6301: ${String(e.offsetFlag)}`);Pt(e.price,"\u59D4\u6258\u4EF7\u683C"),$t(e.volume,"\u59D4\u6258\u6570\u91CF");let t=this.requireSession(),r={tradingDay:_(t.tradingDay,"\u767B\u5F55 TradingDay"),frontID:N(t.frontID,"\u767B\u5F55 FrontID"),sessionID:Xe(t.sessionID,"\u767B\u5F55 SessionID"),orderRef:b(e.orderRef,"\u59D4\u6258 OrderRef"),instrumentID:b(e.instrumentID,"\u59D4\u6258 InstrumentID").toUpperCase(),exchangeID:b(e.exchangeID,"\u59D4\u6258 ExchangeID").toUpperCase()},n=_t(r);return this.bindOrderRef(r.tradingDay,r.orderRef,n),this.orders.set(n,{vtOrderId:n,identity:r,vtSymbol:`${r.instrumentID}.${r.exchangeID}`,instrumentID:r.instrumentID,exchangeID:r.exchangeID,orderSysID:"",side:e.direction,direction:e.direction==="buy"?"long":"short",offset:e.offsetFlag,price:e.price,volume:e.volume,traded:0,status:"submitting",statusMessage:"",datetime:(this.options.now??Date.now)(),...e.traceId?{traceId:e.traceId}:{}}),this.api.insertOrder(e).then((i)=>{if(i.tradingDay!==r.tradingDay||i.frontID!==r.frontID||i.sessionID!==r.sessionID||i.orderRef!==r.orderRef)this.fail(Error(`\u59D4\u6258 ${n} \u7684\u4F1A\u8BDD\u8EAB\u4EFD\u53D1\u751F\u53D8\u5316`))}).catch((i)=>{if(this._phase==="closed")return;let o=i instanceof Error?i.message:String(i);if(this.beginRecovery(),i instanceof Qe)this.emitError({vtOrderId:n,orderRef:r.orderRef,instrumentID:r.instrumentID,operation:"submit",message:o});else{let s=this.orders.get(n);if(s&&U(s)){let a={...s,statusMessage:`\u62A5\u5355\u7ED3\u679C\u5F85\u786E\u8BA4: ${o}`};this.orders.set(n,a),this.emitOrder(a)}}this.reconcileAfterRequestFailure()}),{vtOrderId:n,orderRef:r.orderRef}}cancelOrder(e){this.ensureReady();let t=this.orders.get(e);if(!t)throw Error(`\u627E\u4E0D\u5230\u5B9E\u76D8\u59D4\u6258: ${e}`);if(!U(t))throw Error(`\u59D4\u6258\u5DF2\u7EC8\u7ED3\uFF0C\u4E0D\u80FD\u64A4\u5355: ${e}`);this.pendingCancels.add(e),this.api.cancelOrder({orderRef:t.identity.orderRef,frontID:t.identity.frontID,sessionID:t.identity.sessionID,exchangeID:t.identity.exchangeID,instrumentID:t.identity.instrumentID}).catch((r)=>{if(this._phase==="closed")return;this.emitError({vtOrderId:e,orderRef:t.identity.orderRef,instrumentID:t.instrumentID,operation:"cancel",message:r instanceof Error?r.message:String(r)}),this.reconcileAfterRequestFailure()})}onOrder(e){return this.orderHandlers.add(e),()=>this.orderHandlers.delete(e)}onTrade(e){return this.tradeHandlers.add(e),()=>this.tradeHandlers.delete(e)}onOrderError(e){return this.errorHandlers.add(e),()=>this.errorHandlers.delete(e)}onPhase(e){return this.phaseHandlers.add(e),()=>this.phaseHandlers.delete(e)}onReconciled(e){return this.reconcileHandlers.add(e),()=>this.reconcileHandlers.delete(e)}onFault(e){return this.faultHandlers.add(e),()=>this.faultHandlers.delete(e)}close(){if(this._phase==="closed")return;this.setPhase("closed");for(let e of this.cleanups.splice(0))e();this.pendingEvents.length=0,this.unresolvedTrades.length=0,this.orderHandlers.clear(),this.tradeHandlers.clear(),this.errorHandlers.clear(),this.phaseHandlers.clear(),this.reconcileHandlers.clear(),this.faultHandlers.clear()}beginRecovery(){if(this._phase==="closed"||this._phase==="faulted")return;this.setPhase("recovering")}reconcileAfterRequestFailure(){if(this.beginRecovery(),this.api.connected)this.reconcile(!0).catch((e)=>this.fail(e))}async reconcile(e){if(this.recoveryTask)return this.recoveryTask;let t=this.performReconcile(e);this.recoveryTask=t;try{return await t}finally{if(this.recoveryTask===t)this.recoveryTask=null,this.recoveryObservedOrderIds=null}}async performReconcile(e){let t=this.requireSession();this.recoveryObservedOrderIds=new Set,this.setPhase("recovering");let r=await this.api.queryOrders({broker:this.options.broker,investorId:this.options.investorId}),n=new Map(this.orders);this.orderIdBySys.clear(),this.orderIdByRef.clear(),this.ambiguousOrderRefs.clear();let i=r.map((m)=>(this.validateOwner(m,"\u59D4\u6258"),this.acceptOrder(xt(m),!1))),s=(await this.api.queryTrades({broker:this.options.broker,investorId:this.options.investorId})).map((m)=>(this.validateOwner(m,"\u6210\u4EA4"),this.normalizeTrade(m))),a=[];for(let m of s){if(this.seenTrades.has(m.vtTradeId))continue;a.push(m),this.rememberTrade(m)}let d=await this.api.queryPositions(this.options.broker,this.options.investorId),c=this.normalizePositions(d),u=await this.api.queryAccount(this.options.broker,this.options.investorId);if(this.validateAccounts(u,t.tradingDay),this._phase==="closed"||this._phase==="faulted")throw Error(`CTP \u6062\u590D\u5DF2\u7EC8\u6B62\uFF08${this._phase}\uFF09`);let l={reason:e?"reconnect":"initial",tradingDay:_(t.tradingDay,"\u767B\u5F55 TradingDay"),accounts:u,positions:c,orders:i,trades:s,recoveredAt:(this.options.now??Date.now)()};if(this.currentSnapshot=l,e){for(let m of i){let v=n.get(m.vtOrderId);if(!v||this.orderFingerprint(v)!==this.orderFingerprint(m))this.emitOrder(m)}for(let m of a)this.emitTrade(m)}let f=this.pendingEvents.splice(0);for(let m of f)this.process(m);if(this.flushUnresolvedTrades(),this.unresolvedTrades.length>0)throw Error(`${this.unresolvedTrades.length} \u7B14\u6210\u4EA4\u65E0\u6CD5\u5173\u8054\u59D4\u6258`);let h=this.recoveryObservedOrderIds;for(let[m,v]of n)if(v.identity.tradingDay===l.tradingDay&&U(v)&&this.orders.has(m)&&!h.has(m))throw Error(`\u6D3B\u52A8\u59D4\u6258\u672A\u51FA\u73B0\u5728\u6062\u590D\u5FEB\u7167: ${m}`);l.orders=[...h].map((m)=>this.orders.get(m)).filter((m)=>m!==void 0&&m.identity.tradingDay===l.tradingDay);for(let m of this.reconcileHandlers)m(l);return this.setPhase("ready"),l}receive(e){if(this._phase==="closed"||this._phase==="faulted")return;if(this._phase!=="ready"){if(this.pendingEvents.length>=Br){this.fail(Error("CTP \u6062\u590D\u671F\u95F4\u4E8B\u4EF6\u7F13\u5B58\u6EA2\u51FA"));return}this.pendingEvents.push(e);return}try{this.process(e)}catch(t){this.fail(t)}}process(e){if(e.kind==="order")this.validateOwner(e.value,"\u59D4\u6258"),this.acceptOrder(xt(e.value),!0),this.flushUnresolvedTrades();else if(e.kind==="trade")this.validateOwner(e.value,"\u6210\u4EA4"),this.processTrade(e.value,!0);else this.processError(e.value)}acceptOrder(e,t){let r=this.orders.get(e.vtOrderId),n=e;if(r){if(r.instrumentID!==e.instrumentID||r.exchangeID!==e.exchangeID||r.side!==e.side||r.offset!==e.offset||r.volume!==e.volume)throw Error(`\u59D4\u6258\u8EAB\u4EFD\u5B57\u6BB5\u53D1\u751F\u53D8\u5316: ${e.vtOrderId}`);if(r.orderSysID&&e.orderSysID&&r.orderSysID!==e.orderSysID)throw Error(`\u59D4\u6258 OrderSysID \u53D1\u751F\u53D8\u5316: ${e.vtOrderId}`);if(e.traded<r.traded||this.orderStage(e)<this.orderStage(r))n=r;else n={...e,orderSysID:e.orderSysID||r.orderSysID,...e.traceId||!r.traceId?{}:{traceId:r.traceId}}}if(this.orders.set(n.vtOrderId,n),this.recoveryObservedOrderIds?.add(n.vtOrderId),this.bindOrderRef(n.identity.tradingDay,n.identity.orderRef,n.vtOrderId),n.orderSysID)this.orderIdBySys.set(Lr(n.exchangeID,n.orderSysID),n.vtOrderId);if(!U(n))this.pendingCancels.delete(n.vtOrderId);if(t&&(!r||this.orderFingerprint(r)!==this.orderFingerprint(n)))this.emitOrder(n);return n}normalizeTrade(e){return Ti(e,(t,r,n,i)=>this.orderIdBySys.get(Lr(t,r))??this.orderIdByRef.get(Hr(n,i))??null,this.session?.tradingDay)}bindOrderRef(e,t,r){let n=Hr(e,t);if(this.ambiguousOrderRefs.has(n))return;let i=this.orderIdByRef.get(n);if(i&&i!==r){this.orderIdByRef.delete(n),this.ambiguousOrderRefs.add(n);return}this.orderIdByRef.set(n,r)}normalizePositions(e){let t=_(this.requireSession().tradingDay,"\u767B\u5F55 TradingDay");for(let r of e)if(this.validateOwner(r,"\u6301\u4ED3"),_(r.tradingDay,`\u6301\u4ED3 ${r.instrumentID} TradingDay`)!==t)throw Error(`\u6301\u4ED3 TradingDay \u4E0E\u767B\u5F55\u4F1A\u8BDD\u4E0D\u4E00\u81F4: ${r.tradingDay}/${t}`);return Ei(e,this.contracts)}validateOwner(e,t){let r=b(e.brokerID,`${t} BrokerID`),n=b(e.investorID,`${t} InvestorID`);if(r!==this.options.broker||n!==this.options.investorId)throw Error(`${t}\u8D26\u6237\u8EAB\u4EFD\u4E0D\u5339\u914D: ${r}/${n}`)}validateAccounts(e,t){let r=_(t,"\u767B\u5F55 TradingDay");if(e.length===0)throw Error("\u8D26\u6237\u67E5\u8BE2\u8FD4\u56DE\u7A7A\u7ED3\u679C");for(let n of e){let i=b(n.brokerID,"\u8D26\u6237 BrokerID");if(b(n.accountID,"\u8D26\u6237 AccountID"),i!==this.options.broker)throw Error(`\u8D26\u6237 BrokerID \u4E0D\u5339\u914D: ${i}`);if(_(n.tradingDay,"\u8D26\u6237 TradingDay")!==r)throw Error(`\u8D26\u6237 TradingDay \u4E0E\u767B\u5F55\u4F1A\u8BDD\u4E0D\u4E00\u81F4: ${n.tradingDay}/${r}`);for(let[o,s]of Object.entries({preBalance:n.preBalance,balance:n.balance,available:n.available,currMargin:n.currMargin,frozenMargin:n.frozenMargin,frozenCash:n.frozenCash,frozenCommission:n.frozenCommission,closeProfit:n.closeProfit,positionProfit:n.positionProfit,commission:n.commission,deposit:n.deposit,withdraw:n.withdraw}))Ze(s,`\u8D26\u6237 ${o}`)}}processTrade(e,t){let r;try{r=this.normalizeTrade(e)}catch(n){if(n instanceof Ct){if(this.unresolvedTrades.length>=Br)throw Error("\u65E0\u6CD5\u5173\u8054\u7684\u6210\u4EA4\u7F13\u5B58\u6EA2\u51FA");this.unresolvedTrades.push(e);return}throw n}if(this.seenTrades.has(r.vtTradeId))return;if(this.rememberTrade(r),t)this.emitTrade(r)}flushUnresolvedTrades(){if(this.unresolvedTrades.length===0)return;let e=this.unresolvedTrades.splice(0);for(let t of e)this.processTrade(t,!0)}processError(e){let t=this.requireSession(),r=b(e.orderRef,"\u9519\u8BEF\u56DE\u62A5 OrderRef"),n=_t({...t,orderRef:r}),i=this.pendingCancels.has(n)?"cancel":"submit";this.emitError({vtOrderId:n,orderRef:r,...e.instrumentID?{instrumentID:e.instrumentID.toUpperCase()}:{},operation:i,message:e.errorMsg})}rememberTrade(e){if(!this.tradeLedgerDay||e.tradingDay>this.tradeLedgerDay){if(this.tradeLedgerDay)this.seenTrades.clear();this.tradeLedgerDay=e.tradingDay}this.seenTrades.add(e.vtTradeId)}orderFingerprint(e){return`${e.status}|${e.traded}|${e.orderSysID}|${e.statusMessage}`}orderStage(e){switch(e.status){case"submitting":return 0;case"notTraded":return 1;case"partTraded":return 2;case"allTraded":case"cancelled":case"rejected":return 3}}ensureReady(){if(this._phase!=="ready")throw Error(`CTP \u4EA4\u6613\u9002\u914D\u5668\u672A\u5C31\u7EEA\uFF08${this._phase}\uFF09`)}requireSession(){if(!this.session)throw Error("CTP \u4EA4\u6613\u4F1A\u8BDD\u4E0D\u53EF\u7528");return this.session}setSession(e){_(e.tradingDay,"\u767B\u5F55 TradingDay"),N(e.frontID,"\u767B\u5F55 FrontID"),Xe(e.sessionID,"\u767B\u5F55 SessionID"),this.session=e}setPhase(e){if(this._phase===e)return;if(this._phase==="closed")return;if(this._phase==="faulted"&&e!=="closed")return;this._phase=e;for(let t of this.phaseHandlers)t(e)}fail(e){if(this._phase==="closed"||this._phase==="faulted")return;let t=e instanceof Error?e.message:String(e);this.setPhase("faulted");for(let r of this.faultHandlers)r(t)}emitOrder(e){for(let t of this.orderHandlers)t(e)}emitTrade(e){for(let t of this.tradeHandlers)t(e)}emitError(e){this.pendingCancels.delete(e.vtOrderId);let t=this.orders.get(e.vtOrderId);if(t&&U(t)){let r=e.operation==="submit"?{...t,status:"rejected",statusMessage:e.message}:{...t,statusMessage:`\u64A4\u5355\u5931\u8D25: ${e.message}`};this.orders.set(e.vtOrderId,r),this.emitOrder(r)}for(let r of this.errorHandlers)r(e)}}var At={queryOrder:{event:"onQueryOrderResult",field:"orders"},queryTrade:{event:"onQueryTradeResult",field:"trades"},queryPosition:{event:"onQueryPositionResult",field:"positions"},queryAccount:{event:"onQueryAccountResult",field:"accounts"}};class Vr{session;clock;trace;requests=[];pending=new Map;handlers=new Map;exits=new Set;held=new Set;waiters=new Set;state;constructor(e,t,r,n={}){this.session=e;this.clock=t;this.trace=r;this.state=structuredClone(n)}request(e,t,r=30000){let n={id:this.requests.length+1,method:e,params:structuredClone(t),now:this.clock.now};return this.requests.push(n),this.trace.record("broker",{operation:"rpc",request:n}),new Promise((i,o)=>{let s=this.clock.schedule(()=>{this.pending.delete(n.id),o(Error(`Test gateway ${e} request timed out`))},r);this.pending.set(n.id,{request:n,resolve:i,reject:o,cancelTimeout:s});for(let a of this.waiters)if(a.method===e)this.waiters.delete(a),a.resolve(structuredClone(n));if(!this.held.has(e))this.complete(n.id)})}pendingRequests(){return[...this.pending.values()].map((e)=>structuredClone(e.request))}waitForRequest(e){let t=this.pendingRequests().find((r)=>r.method===e);return t?Promise.resolve(t):new Promise((r,n)=>this.waiters.add({method:e,resolve:r,reject:n}))}setSnapshot(e){this.state={...this.state,...structuredClone(e)}}hold(e){this.held.add(e)}resume(e){this.held.delete(e);for(let{request:t}of[...this.pending.values()])if(t.method===e)this.complete(t.id)}reply(e,t){let r=this.pending.get(e);if(!r)throw Error(`Pending RPC does not exist: ${e}`);this.pending.delete(e),r.cancelTimeout();let n=r.request.method==="insertOrder"?{...this.session,orderRef:r.request.params.orderRef,requestID:e}:{requestID:e},i={id:e,...t??{ok:!0,data:n}};this.trace.record("broker",{operation:"rpcReply",response:i}),r.resolve(i)}completeQuery(e,t){let r=this.requests.find((o)=>o.id===e);if(!r||!Object.hasOwn(At,r.method))throw Error(`Query RPC does not exist: ${e}`);let n=At[r.method];if(this.pending.has(e))this.reply(e);let i=n.field==="accounts"?[{brokerID:"test",accountID:"test",tradingDay:this.session.tradingDay,preBalance:1e6,balance:1e6,available:1e6,currMargin:0,frozenMargin:0,frozenCash:0,frozenCommission:0,closeProfit:0,positionProfit:0,commission:0,deposit:0,withdraw:0}]:[];this.push(n.event,{requestID:e,[n.field]:structuredClone(t??this.state[n.field]??i)})}push(e,t){let r={method:e,params:structuredClone(t)};this.trace.record("broker",{operation:"push",message:r});for(let n of this.handlers.get(e)??[])n(structuredClone(r))}on(e,t){let r=this.handlers.get(e)??new Set;return r.add(t),this.handlers.set(e,r),()=>this.off(e,t)}off(e,t){this.handlers.get(e)?.delete(t)}onExit(e){return this.exits.add(e),()=>this.exits.delete(e)}resetClient(){let e=Error("Test gateway client closed");for(let t of this.pending.values())t.cancelTimeout(),t.reject(e);for(let t of this.waiters)t.reject(e);this.pending.clear(),this.waiters.clear(),this.held.clear()}complete(e){let t=this.pending.get(e).request;if(Object.hasOwn(At,t.method))this.completeQuery(e);else this.reply(e)}}class xe{clock;trace;observations;gateway;fixture;transport;instrument;api;adapter;orderRef;orders=new Set;trades=new Set;errors=new Set;reconciled=new Set;faults=new Set;constructor(e,t,r,n,i){this.clock=t;this.trace=r;this.observations=i;this.fixture=e,this.instrument=Ye(e);let o={tradingDay:e.tradingDay,frontID:1,sessionID:1,...n.gatewaySession};this.orderRef=BigInt(o.maxOrderRef??"0"),this.transport=new Vr(o,t,r,n.gatewayState);let s=this;this.gateway={get requests(){return structuredClone(s.transport.requests)},pendingRequests:()=>this.transport.pendingRequests(),waitForRequest:(a)=>this.transport.waitForRequest(a),setSnapshot:(a)=>this.transport.setSnapshot(a),hold:(a)=>this.transport.hold(a),resume:(a)=>this.transport.resume(a),reply:(a,d)=>this.transport.reply(a,d),completeQuery:(a,d)=>this.transport.completeQuery(a,d),push:(a,d)=>this.transport.push(a,d),disconnect:()=>this.transport.push("onDisconnected",{}),reconnect:(a)=>this.reconnect(a),queryOrders:()=>this.requireAdapter().queryOrders()}}get phase(){if(this.adapter?.phase==="ready")return"running";return this.adapter?.phase==="faulted"||this.adapter?.phase==="closed"?"faulted":"recovering"}async connect(){if(this.adapter)this.close();this.api=new Rt(this.transport,{clock:{now:()=>this.clock.now,schedule:this.clock.schedule}}),this.api.loginResult=this.transport.session,this.api.connected=!0,this.adapter=new Mt(this.api,{broker:"test",investorId:"test",now:()=>this.clock.now}),this.adapter.registerInstrument(this.instrument),this.adapter.onOrder((e)=>{for(let t of this.orders)t(e)}),this.adapter.onTrade((e)=>{for(let t of this.trades)t(e)}),this.adapter.onOrderError((e)=>{for(let t of this.errors)t(e)}),this.adapter.onReconciled((e)=>{for(let t of this.reconciled)t(e)}),this.adapter.onFault((e)=>{for(let t of this.faults)t(e)}),await this.adapter.initialize(this.transport.session)}close(){this.adapter?.close(),this.api?.dispose(),this.transport.resetClient()}async prepareInstrument(e,t){if(e!==this.instrument.instrumentID||t!==this.instrument.exchangeID)throw Error("Test instrument mismatch");return this.instrument}async prepareCommission(e,t){if(e!==this.instrument.instrumentID||t!==this.instrument.exchangeID)throw Error("Test commission mismatch")}getInstrument(e){return e===this.instrument.instrumentID?this.instrument:null}getCommission(e,t){if(e!==this.instrument.instrumentID||t!==this.instrument.exchangeID)return null;return Ge(this.fixture)}async queryPositions(e){if(this.phase!=="running")return{phase:this.phase,positions:[]};let t=await this.requireAdapter().queryPositions(e);return{phase:this.phase,positions:t}}queryOrders(e){return this.requireAdapter().queryOrders(e)}submitOrder(e){let t=String(++this.orderRef);if(t.length>12)throw Error("CTP OrderRef \u5DF2\u8017\u5C3D\uFF0C\u8BF7\u91CD\u542F\u4EA4\u6613\u4F1A\u8BDD");let r=this.requireAdapter().submitOrder({instrumentID:e.instrumentID,direction:e.direction,offsetFlag:e.offsetFlag,price:e.price,volume:e.volume,exchangeID:this.instrument.exchangeID,orderRef:t,traceId:`vibek:${e.strategyId}:${t}`}),n={vtOrderId:r.vtOrderId,direction:e.direction==="buy"?"long":"short",offset:e.offsetFlag,price:e.price,volume:e.volume};return this.observations.submissions.push(n),this.trace.record("submission",n),r}cancelOrder(e){this.observations.cancelRequests.push(e),this.trace.record("cancel",{vtOrderId:e}),this.requireAdapter().cancelOrder(e)}onOrder(e){return this.orders.add(e),()=>this.orders.delete(e)}onTrade(e){return this.trades.add(e),()=>this.trades.delete(e)}onOrderError(e){return this.errors.add(e),()=>this.errors.delete(e)}onReconciled(e){return this.reconciled.add(e),()=>this.reconciled.delete(e)}onFault(e){return this.faults.add(e),()=>this.faults.delete(e)}reconnect(e={}){let t=this.requireAdapter();return Object.assign(this.transport.session,e),new Promise((r,n)=>{let i=t.onPhase((o)=>{if(o==="ready")i(),r();else if(o==="faulted"||o==="closed")i(),n(Error(`CTP reconciliation ${o}`))});this.transport.push("onDisconnected",{}),this.transport.push("onReconnected",{...this.transport.session})})}requireAdapter(){if(!this.adapter)throw Error("CTP test adapter is not initialized");return this.adapter}}class Bt{fixture;history;now;generate;constructor(e,t,r,n){this.fixture=e;this.history=t;this.now=r;this.generate=n}async getBars(e,t){if(this.history.length===0&&this.generate)this.history.push(...Ii(e,t,this.fixture,this.now));Ie(this.history,this.now,e);let r=ke(this.history,t.timeframe,t.count);if(r.selectedCount!==t.count)throw Error(`\u6D4B\u8BD5\u9884\u70ED\u6570\u636E\u4E0D\u8DB3\uFF1A\u9700\u8981 ${t.count} \u6839 ${t.timeframe}\uFF0C\u4EC5\u6709 ${r.availableCount} \u6839`);return r.bars.map((n)=>({...n}))}destroy(){}}function Ii(e,t,r,n){if(t.count>1e5)throw Error(`\u6D4B\u8BD5\u9884\u70ED\u6570\u636E\u8BF7\u6C42\u8FC7\u5927: ${t.count}`);if(t.timeframe==="1d"){let d=Math.floor(n/60000)*60;return Array.from({length:t.count},(c,u)=>{let l=d-(t.count-u)*86400;return H(r,{code:e,close:r.seedPrice+u*r.priceTick,barTime:l,tradingDay:Ot(l*1000),boundaryReason:"sessionEnd"},u)})}let i=se[t.timeframe]/60,o=t.count*i;if(o>1e5)throw Error(`\u6D4B\u8BD5\u9884\u70ED\u6570\u636E\u8BF7\u6C42\u8FC7\u5927: ${o} \u6839 1min Bar`);let a=Math.floor(n/(i*60000))*i*60-o*60;return Array.from({length:o},(d,c)=>H(r,{code:e,close:r.seedPrice+c*r.priceTick,barTime:a+c*60,tradingDay:Tt(a+c*60)},c))}import{mkdtempSync as zo,rmSync as Uo}from"fs";import{tmpdir as jo}from"os";import{join as Go}from"path";class Ht{now;tasks=new Map;sequence=0;constructor(e){this.now=e}schedule=(e,t)=>{if(!Number.isSafeInteger(t)||t<0||!Number.isSafeInteger(this.now+t))throw Error("Scheduled delay must be a nonnegative safe integer");let r=++this.sequence;return this.tasks.set(r,{at:this.now+t,callback:e}),()=>{this.tasks.delete(r)}};advanceTo(e,t){if(!Number.isSafeInteger(e)||e<this.now)throw Error("\u6D4B\u8BD5\u65F6\u95F4\u4E0D\u80FD\u56DE\u9000\u6216\u8D85\u8FC7\u5B89\u5168\u6574\u6570\u8303\u56F4");let r=(Math.floor(this.now/1000)+1)*1000,n=0;while(!0){let i=1/0;for(let s of this.tasks.values())i=Math.min(i,s.at);let o=Math.min(i,t?r:1/0);if(o>e)break;if(this.now=o,t&&o===r)t(),r+=1000;for(let[s,a]of this.tasks)if(a.at<=this.now){if(this.tasks.delete(s),++n>1e5)throw Error("Test scheduler exceeded 100000 callbacks in one advance");a.callback()}}this.now=e}}import{AsyncLocalStorage as Di}from"async_hooks";class Lt{now;entries=[];context=new Di;constructor(e){this.now=e}get events(){return this.entries.slice()}close(){this.context.disable()}record(e,t){let r=this.entries.length+1;return this.entries.push(zr({sequence:r,now:this.now(),cause:this.context.getStore()??null,type:e,value:structuredClone(t)})),r}input(e,t){let r=this.record("input",e);return this.context.run(r,()=>{let n=t();if(n instanceof Promise)return n.then((i)=>(this.record("completion",{input:r,status:"fulfilled"}),i),(i)=>{throw this.record("completion",{input:r,status:"rejected",error:i instanceof Error?i.message:String(i)}),i});return n})}bind(e){let t=this.context.getStore()??null;return()=>this.context.run(t,e)}}function zr(e,t=new Set){if(e&&typeof e==="object"){if(t.has(e))return e;t.add(e);for(let r of Object.values(e))zr(r,t);Object.freeze(e)}return e}import{existsSync as ln}from"fs";import{join as I}from"path";import{existsSync as _i,mkdirSync as ya,readFileSync as Ci,renameSync as ba,unlinkSync as wa,writeFileSync as Sa}from"fs";import{homedir as Ce}from"os";import{dirname as Pi,isAbsolute as $i,join as C,resolve as zt}from"path";import{isIP as Oi}from"net";function Nt(e){if(e.trim()==="")return[];let t=[],r=new Set,n=e.split(",").map((i)=>i.trim()).filter(Boolean);for(let[i,o]of n.entries()){let s=o.indexOf("@");if(s<=0||s!==o.lastIndexOf("@"))throw Error(`ctp.front_candidates \u7B2C ${i+1} \u9879\u683C\u5F0F\u9519\u8BEF\uFF0C\u5E94\u4E3A \u63CF\u8FF0@IP`);let a=o.slice(0,s).trim(),d=o.slice(s+1).trim();if(a==="")throw Error(`ctp.front_candidates \u7B2C ${i+1} \u9879\u7F3A\u5C11\u7EBF\u8DEF\u63CF\u8FF0`);if(Oi(d)===0)throw Error(`ctp.front_candidates \u7B2C ${i+1} \u9879\u4E0D\u662F\u6709\u6548 IP: ${d||"(\u7A7A)"}`);if(r.has(d))throw Error(`ctp.front_candidates \u5305\u542B\u91CD\u590D IP: ${d}`);r.add(d),t.push({description:a,ip:d})}return t}var _e=[{start:"09:00:00",end:"10:15:00",boundaryReason:"break",acceptsPreOpenTick:!0},{start:"10:30:00",end:"11:30:00",boundaryReason:"break"},{start:"13:30:00",end:"15:00:00",boundaryReason:"sessionEnd"}],Ri=[{start:"09:30:00",end:"11:30:00",boundaryReason:"break",acceptsPreOpenTick:!0},{start:"13:00:00",end:"15:00:00",boundaryReason:"sessionEnd"}],xi=[{start:"09:30:00",end:"11:30:00",boundaryReason:"break",acceptsPreOpenTick:!0},{start:"13:00:00",end:"15:15:00",boundaryReason:"sessionEnd"}];function Ft(e){return[{start:"21:00:00",end:e,boundaryReason:"break",acceptsPreOpenTick:!0},..._e]}var et=Ft("23:00:00"),Ur=Ft("01:00:00"),jr=Ft("02:30:00"),qt=new Map;function D(e,t,r){for(let n of t){let i=n.toUpperCase();qt.set(`${e}.${i}`,{exchangeID:e,productID:i,segments:r})}}D("CFFEX",["IF","IH","IC","IM"],Ri);D("CFFEX",["T","TF","TS","TL"],xi);D("SHFE",["AU","AG"],jr);D("SHFE",["CU","AL","ZN","PB","NI","SN","SS","AO","AD"],Ur);D("SHFE",["RB","WR","HC","RU","BU","FU","SP","BR"],et);D("INE",["SC"],jr);D("INE",["BC"],Ur);D("INE",["NR","LU"],et);D("INE",["EC"],_e);D("DCE",["A","B","C","CS","M","Y","P","L","L_F","V","V_F","PP","PP_F","J","JM","I","EG","EB","PG"],et);D("DCE",["JD","FB","BB","RR","LH","LG","BZ"],_e);D("CZCE",["CF","SR","TA","OI","MA","FG","RM","ZC","CY","SA","PF","PX","SH","PR"],et);D("CZCE",["WH","PM","RI","RS","JR","LR","SF","SM","AP","CJ","UR","PK"],_e);D("GFEX",["SI","LC","PS","PT","PD"],_e);function Gr(e,t){return qt.get(`${e.toUpperCase()}.${t.toUpperCase()}`)??null}function tt(e,t){let r=Gr(e,t);if(!r)throw Error(`\u4EA7\u54C1\u65F6\u6BB5\u76EE\u5F55\u4E0D\u5305\u542B ${t.toUpperCase()}.${e.toUpperCase()}`);return r}function Vt(){return[...qt.values()]}var Mi="VIBEK_CONFIG_PATH",Ai="VIBEK_PROCESS_ACCOUNT",Ut=/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/,Kr=["trace","debug","info","warn","error"],Yr=["realtime","replay"];class ee extends Error{path;diagnostics;constructor(e,t){super(`\u914D\u7F6E\u6587\u4EF6\u65E0\u6548: ${e}
|
|
3
|
+
${t.map((r)=>` - ${r}`).join(`
|
|
4
|
+
`)}`);this.path=e;this.diagnostics=t;this.name="ConfigValidationError"}}class Qr{errors=[];table(e,t){if(Bi(e))return e;return this.errors.push(`${t} \u5FC5\u987B\u662F TOML \u8868`),{}}keys(e,t,r){let n=new Set(t);for(let i of Object.keys(e))if(!n.has(i))this.errors.push(`\u672A\u77E5\u914D\u7F6E\u9879 ${r?`${r}.`:""}${i}`)}string(e,t,r,n={}){let i=e[t],o=`${r}.${t}`;if(i===void 0){if(n.required)this.errors.push(`\u7F3A\u5C11\u914D\u7F6E\u9879 ${o}`);return}if(typeof i!=="string"){this.errors.push(`${o} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);return}if(n.nonEmpty&&i.trim()===""){this.errors.push(`${o} \u4E0D\u80FD\u4E3A\u7A7A`);return}return i}boolean(e,t,r){let n=e[t];if(n===void 0)return;if(typeof n!=="boolean"){this.errors.push(`${r}.${t} \u5FC5\u987B\u662F\u5E03\u5C14\u503C`);return}return n}integer(e,t,r,n,i){let o=e[t];if(o===void 0)return;if(!Number.isInteger(o)||o<n||o>i){this.errors.push(`${r}.${t} \u5FC5\u987B\u662F ${n}..${i} \u7684\u6574\u6570`);return}return o}strings(e,t,r){let n=e[t];if(n===void 0)return;if(!Array.isArray(n)||n.some((i)=>typeof i!=="string")){this.errors.push(`${r}.${t} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4`);return}return[...n]}}function Bi(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function j(e,t,r,n=""){let i=t[r];if(i===void 0)return;return e.table(i,n?`${n}.${r}`:r)}function rt(e,t,r){if(e!==void 0&&!/^([01]\d|2[0-3]):[0-5]\d$/.test(e))r.push(`${t} \u5FC5\u987B\u4F7F\u7528 HH:MM\uFF0824 \u5C0F\u65F6\uFF09\u683C\u5F0F`)}function Wr(e,t,r){if(e===void 0)return;try{let n=new URL(e),i=Number(n.port);if(n.protocol!=="tcp:"||!n.hostname||!Number.isInteger(i)||i<1||i>65535)throw Error("invalid")}catch{r.push(`${t} \u5FC5\u987B\u662F tcp://host:port`)}}var Jr=/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;function Hi(e,t){if(t===void 0)return;let r="account.timer_hooks";if(!Array.isArray(t))return e.errors.push(`${r} \u5FC5\u987B\u662F TOML \u8868\u6570\u7EC4`),[];let n=[],i=new Set;for(let[o,s]of t.entries()){let a=`${r}[${o}]`,d=e.table(s,a);e.keys(d,["name","cron","timezone","enabled"],a);let c=e.string(d,"name",a,{required:!0,nonEmpty:!0}),u=e.string(d,"cron",a,{required:!0,nonEmpty:!0}),l=e.string(d,"timezone",a,{required:!0,nonEmpty:!0}),f=e.boolean(d,"enabled",a);if(c!==void 0)if(!Jr.test(c))e.errors.push(`${a}.name \u5FC5\u987B\u5339\u914D ${Jr.source}`);else if(i.has(c))e.errors.push(`${r} \u4E0D\u5141\u8BB8\u91CD\u590D\u540D\u79F0: ${c}`);else i.add(c);let h=u?.trim().replace(/\s+/g," "),m=l?.trim(),v=!1;if(m)try{Bun.cron.parse("0 0 * * *",Date.now(),{tz:m}),v=!0}catch{e.errors.push(`${a}.timezone \u4E0D\u662F\u6709\u6548\u7684 IANA \u65F6\u533A`)}if(h!==void 0&&h.split(" ").length!==5)e.errors.push(`${a}.cron \u5FC5\u987B\u662F 5 \u6BB5 cron \u8868\u8FBE\u5F0F`);else if(h!==void 0&&v)try{if(Bun.cron.parse(h,Date.now(),{tz:m})===null)e.errors.push(`${a}.cron \u5728\u672A\u6765 8 \u5E74\u5185\u6CA1\u6709\u89E6\u53D1\u65F6\u95F4`)}catch{e.errors.push(`${a}.cron \u4E0D\u662F\u6709\u6548\u7684 cron \u8868\u8FBE\u5F0F`)}if(c!==void 0&&h!==void 0&&m!==void 0)n.push({name:c,cron:h,timezone:m,...f===void 0?{}:{enabled:f}})}return n}var Zr=new Set(["CFFEX","SHFE","CZCE","DCE","INE","GFEX"]);function Li(e){let t=e.trim(),r=/^([A-Za-z][A-Za-z0-9]*)\.([A-Za-z]+)$/.exec(t),n=r??/^([A-Za-z][A-Za-z0-9]*)$/.exec(t);if(!n||!/\d/.test(n[1]))throw Error(`\u5408\u7EA6 ${JSON.stringify(e)} \u975E\u6CD5\uFF0C\u5E94\u4F7F\u7528 CODE \u6216 CODE.EXCHANGE`);let i=n[1].toUpperCase(),o=Ni(i),s=r?n[2].toUpperCase():o;if(!s||!Zr.has(s)){if(r)throw Error(`\u5408\u7EA6\u4EA4\u6613\u6240\u975E\u6CD5: ${e}`);throw Error(`\u65E0\u6CD5\u63A8\u65AD\u5408\u7EA6 ${i} \u7684\u4EA4\u6613\u6240\uFF0C\u8BF7\u4F7F\u7528 ${i}.EXCHANGE`)}if(r&&o&&o!==s)throw Error(`\u5408\u7EA6 ${i} \u5E94\u4F7F\u7528\u4EA4\u6613\u6240 ${o}\uFF0C\u800C\u4E0D\u662F ${s}`);return{instrumentID:i,exchangeID:s,vtSymbol:`${i}.${s}`}}function Ni(e){let t=/^[A-Z]+/.exec(e)?.[0];if(!t)return null;let r=new Set;for(let n of Vt())if((n.productID.endsWith("_F")?n.productID.slice(0,-2):n.productID)===t)r.add(n.exchangeID);return r.size===1?[...r][0]:null}function jt(e){let t=[],r=new Map;for(let n of e){let i=n.trim(),o=/^([A-Za-z][A-Za-z0-9]*)\.([A-Za-z]+)$/.exec(i);if(!o||!/\d/.test(o[1]))throw Error(`market.record_instruments \u9879 ${JSON.stringify(n)} \u975E\u6CD5\uFF0C\u5E94\u4F7F\u7528 CODE.EXCHANGE`);let s=o[1].toUpperCase(),a=o[2].toUpperCase();if(!Zr.has(a))throw Error(`market.record_instruments \u4EA4\u6613\u6240\u975E\u6CD5: ${n}`);let d=r.get(s);if(d&&d!==a)throw Error(`market.record_instruments \u5408\u7EA6\u4EA4\u6613\u6240\u51B2\u7A81: ${s}.${d} / ${s}.${a}`);if(d)continue;r.set(s,a),t.push({instrumentID:s,exchangeID:a,vtSymbol:`${s}.${a}`})}return t}function Fi(e,t){let r=new Set(e.map((i)=>i.vtSymbol)),n=[...new Set(t.filter((i)=>!r.has(i.vtSymbol)).map((i)=>i.vtSymbol))];if(n.length>0)throw Error(`account.market.bar_hooks \u4F7F\u7528\u4E86\u672A\u5F55\u5236\u8BA2\u9605\u7684\u5408\u7EA6: ${n.join(", ")}`)}function nt(e,t){let r=[],n=new Set;for(let[i,o]of e.entries()){let s;try{s=Li(o.instrument)}catch(d){throw Error(`account.market.bar_hooks[${i}].instrument: ${d instanceof Error?d.message:String(d)}`)}if(!Z(o.timeframe))throw Error(`account.market.bar_hooks[${i}].timeframe \u5FC5\u987B\u662F ${ve}`);let a=`${s.vtSymbol}/${o.timeframe}`;if(n.has(a))throw Error(`account.market.bar_hooks \u4E0D\u5141\u8BB8\u91CD\u590D\u7EC4\u5408: ${a}`);n.add(a),r.push({...s,timeframe:o.timeframe})}return Fi(t,r),r}function Gt(e,t){let r=e?.trim()??"",n=t?.trim()??"";if(!r||!n)return"";let i=`${r}-${n}`;return Ut.test(i)?i:""}function qi(e,t){let n=e.table(t,"account");e.keys(n,["broker_id","user","ctp","timer_hooks","logging","market","reference_data","trading","schedule"],"account");let i=e.string(n,"broker_id","account",{required:!0,nonEmpty:!0})??"",o=e.string(n,"user","account",{required:!0,nonEmpty:!0})??"";if(!Gt(i,o)&&i&&o)e.errors.push(`${"account"}.broker_id \u4E0E ${"account"}.user \u65E0\u6CD5\u7EC4\u6210\u5408\u6CD5\u8D26\u53F7\u540D`);let a=e.table(n.ctp,"account.ctp");e.keys(a,["market_front","trader_front","front_candidates","password","app_id","auth_code","production_mode"],"account.ctp");let d=e.string(a,"market_front","account.ctp",{required:!0,nonEmpty:!0})??"",c=e.string(a,"trader_front","account.ctp",{required:!0,nonEmpty:!0})??"",u=e.strings(a,"front_candidates","account.ctp"),l=e.string(a,"password","account.ctp"),f=e.string(a,"app_id","account.ctp",{required:!0,nonEmpty:!0})??"",h=e.string(a,"auth_code","account.ctp",{required:!0,nonEmpty:!0})??"",m=e.boolean(a,"production_mode","account.ctp");if(Wr(d||void 0,"account.ctp.market_front",e.errors),Wr(c||void 0,"account.ctp.trader_front",e.errors),u)try{Nt(u.join(","))}catch(S){e.errors.push(`account.ctp.front_candidates: ${S instanceof Error?S.message:String(S)}`)}let v=Hi(e,n.timer_hooks),p=j(e,n,"logging","account");if(p){e.keys(p,["level"],"account.logging");let S=e.string(p,"level","account.logging");if(S!==void 0&&!Kr.includes(S))e.errors.push(`${"account"}.logging.level \u5FC5\u987B\u662F ${Kr.join(" | ")}`)}let k=j(e,n,"market","account");if(k){e.keys(k,["db_path","record_instruments","bar_hooks","tick_time_mode","max_future_tick_ms"],"account.market"),e.string(k,"db_path","account.market");let S=e.strings(k,"record_instruments","account.market"),z=k.bar_hooks,oe=[];if(z!==void 0)if(!Array.isArray(z))e.errors.push(`${"account"}.market.bar_hooks \u5FC5\u987B\u662F TOML \u8868\u6570\u7EC4`);else for(let[A,Zn]of z.entries()){let be=`account.market.bar_hooks[${A}]`,bt=e.table(Zn,be);e.keys(bt,["instrument","timeframe"],be);let yr=e.string(bt,"instrument",be,{required:!0,nonEmpty:!0}),Fe=e.string(bt,"timeframe",be,{required:!0});if(yr!==void 0&&Z(Fe))oe.push({instrument:yr,timeframe:Fe});else if(Fe!==void 0&&!Z(Fe))e.errors.push(`${be}.timeframe \u5FC5\u987B\u662F ${ve}`)}let Ne=e.string(k,"tick_time_mode","account.market");if(Ne!==void 0&&!Yr.includes(Ne))e.errors.push(`${"account"}.market.tick_time_mode \u5FC5\u987B\u662F ${Yr.join(" | ")}`);if(e.integer(k,"max_future_tick_ms","account.market",0,2592000000),S)try{let A=jt(S);nt(oe,A)}catch(A){e.errors.push(A instanceof Error?A.message:String(A))}else if(oe.length>0)try{nt(oe,[])}catch(A){e.errors.push(A instanceof Error?A.message:String(A))}}let V=j(e,n,"reference_data","account");if(V)e.keys(V,["contracts_path","commissions_path"],"account.reference_data"),e.string(V,"contracts_path","account.reference_data"),e.string(V,"commissions_path","account.reference_data");let M=j(e,n,"trading","account");if(M){e.keys(M,["max_order_volume","max_position","max_active_open_orders","daily_limits","duplicate_orders"],"account.trading"),e.integer(M,"max_order_volume","account.trading",1,1e4),e.integer(M,"max_position","account.trading",1,1e5),e.integer(M,"max_active_open_orders","account.trading",1,1000);let S=j(e,M,"daily_limits","account.trading");if(S)e.keys(S,["total_order_limit","total_cancel_limit","total_trade_limit","instrument_order_limit","instrument_cancel_limit","instrument_trade_limit"],"account.trading.daily_limits"),e.integer(S,"total_order_limit","account.trading.daily_limits",1,1e7),e.integer(S,"total_cancel_limit","account.trading.daily_limits",1,1e7),e.integer(S,"total_trade_limit","account.trading.daily_limits",1,1e7),e.integer(S,"instrument_order_limit","account.trading.daily_limits",1,1e7),e.integer(S,"instrument_cancel_limit","account.trading.daily_limits",1,1e7),e.integer(S,"instrument_trade_limit","account.trading.daily_limits",1,1e7);let z=j(e,M,"duplicate_orders","account.trading");if(z)e.keys(z,["duplicate_order_limit"],"account.trading.duplicate_orders"),e.integer(z,"duplicate_order_limit","account.trading.duplicate_orders",2,1000)}let R=j(e,n,"schedule","account");if(R){e.keys(R,["enabled","day_start","day_stop","night_start","night_stop","max_start_attempts","retry_base_ms","tick_ms"],"account.schedule"),e.boolean(R,"enabled","account.schedule");let S=e.string(R,"day_start","account.schedule"),z=e.string(R,"day_stop","account.schedule"),oe=e.string(R,"night_start","account.schedule"),Ne=e.string(R,"night_stop","account.schedule");rt(S,"account.schedule.day_start",e.errors),rt(z,"account.schedule.day_stop",e.errors),rt(oe,"account.schedule.night_start",e.errors),rt(Ne,"account.schedule.night_stop",e.errors),e.integer(R,"max_start_attempts","account.schedule",1,10),e.integer(R,"retry_base_ms","account.schedule",5000,600000),e.integer(R,"tick_ms","account.schedule",5000,120000)}return{identity:{broker_id:i,user:o},ctp:{market_front:d,trader_front:c,...u===void 0?{}:{front_candidates:u},...l===void 0?{}:{password:l},app_id:f,auth_code:h,...m===void 0?{}:{production_mode:m}},...v===void 0?{}:{timer_hooks:v},...p===void 0?{}:{logging:p},...k===void 0?{}:{market:k},...V===void 0?{}:{reference_data:V},...M===void 0?{}:{trading:M},...R===void 0?{}:{schedule:R}}}function Vi(e,t=F()){let r=new Qr,n=r.table(e,"config");r.keys(n,["account","storage","server"],"");let i=j(r,n,"storage");if(i)r.keys(i,["data_root"],"storage"),r.string(i,"data_root","storage",{nonEmpty:!0});let o=j(r,n,"server");if(o){r.keys(o,["daemon_port","webui_port"],"server");let l=r.integer(o,"daemon_port","server",1,65535),f=r.integer(o,"webui_port","server",1,65535);if(l!==void 0&&l===f)r.errors.push("server.daemon_port \u4E0E server.webui_port \u4E0D\u80FD\u76F8\u540C")}let s=n.account,a=s===void 0?void 0:qi(r,s),d=a?.reference_data?.contracts_path?.trim(),c=a?.reference_data?.commissions_path?.trim(),u=a?Gt(a.identity.broker_id,a.identity.user):"";if(a&&u){let l={account:a,...i===void 0?{}:{storage:i}},f=C(le(l,t),"accounts",u),h=d?W(d,t):C(f,"contracts.jsonl"),m=c?W(c,t):C(f,"commissions.jsonl");if(h===m)r.errors.push("account.reference_data.contracts_path \u4E0E commissions_path \u4E0D\u80FD\u6307\u5411\u540C\u4E00\u6587\u4EF6")}if(r.errors.length>0)throw new ee(t,r.errors);return{...a===void 0?{}:{account:a},...i===void 0?{}:{storage:i},...o===void 0?{}:{server:o}}}function zi(e,t=F()){let r;try{r=Bun.TOML.parse(e)}catch(n){throw new ee(t,[`TOML \u8BED\u6CD5\u9519\u8BEF: ${n instanceof Error?n.message:String(n)}`])}return Vi(r,t)}function F(){let e=process.env[Mi]?.trim();if(e)return zt(e);if(process.platform==="win32"){let t=process.env.APPDATA??(process.env.USERPROFILE?C(process.env.USERPROFILE,"AppData","Roaming"):C(Ce(),"AppData","Roaming"));return C(t,"vibek","config.toml")}return C(process.env.XDG_CONFIG_HOME??C(Ce(),".config"),"vibek","config.toml")}function Ui(e=F()){if(!_i(e))return{path:e,exists:!1,file:{},errors:[]};try{return{path:e,exists:!0,file:zi(Ci(e,"utf8"),e),errors:[]}}catch(t){if(t instanceof ee)return{path:e,exists:!0,file:null,errors:[...t.diagnostics]};return{path:e,exists:!0,file:null,errors:[t instanceof Error?t.message:String(t)]}}}function ue(e=F()){let t=Ui(e);if(t.errors.length>0||t.file===null)throw new ee(e,t.errors);return t.file}function Xr(e=ue()){let t=Gt(e.account?.identity.broker_id,e.account?.identity.user),r=process.env[Ai]?.trim();if(r){if(r!==t)throw new ee(F(),[`\u5185\u90E8\u8FDB\u7A0B\u8D26\u53F7 ${JSON.stringify(r)} \u4E0E\u5F53\u524D\u914D\u7F6E ${JSON.stringify(t)} \u4E0D\u4E00\u81F4`]);return{name:r,source:"process"}}return t?{name:t,source:"config"}:{name:"",source:"none"}}function W(e,t=F()){let r=e==="~"?Ce():e.startsWith("~/")||e.startsWith("~\\")?C(Ce(),e.slice(2)):e;return $i(r)?zt(r):zt(Pi(t),r)}function le(e=ue(),t=F()){return W(e.storage?.data_root??C(Ce(),".vibek"),t)}function ji(e=ue()){return C(le(e),"accounts")}function en(e,t=ue()){if(!Ut.test(e))throw Error(`\u8D26\u53F7\u540D\u975E\u6CD5: ${e}`);return C(ji(t),e)}import{existsSync as tn,readFileSync as Gi}from"fs";import{isAbsolute as Ki,join as rn,relative as Yi,resolve as Wi}from"path";var Ji=1;function Pe(e=le()){return rn(e,"gateway")}function Qi(e=Pe()){return rn(e,"current.json")}function nn(e=Pe(),t={}){let r=Qi(e);if(!tn(r))return null;try{let n=JSON.parse(Gi(r,"utf8")),i=Zi(n),o=t.platform??process.platform,s=t.arch??process.arch;if(i.platform!==o||i.arch!==s)throw Error(`\u5E73\u53F0\u4E0D\u5339\u914D: ${i.platform}-${i.arch} / ${o}-${s}`);let a=Kt(e,i);if(t.requireExecutable!==!1&&!tn(a))throw Error(`executable \u4E0D\u5B58\u5728: ${a}`);return i}catch(n){if(t.tolerateInvalid)return null;throw Error(`\u53D7\u7BA1 gateway \u72B6\u6001\u65E0\u6548: ${r}: ${n instanceof Error?n.message:String(n)}`)}}function Kt(e,t){let r=Wi(e,t.relativeExecutable),n=Yi(e,r);if(!n||n.startsWith("..")||Ki(n))throw Error("relativeExecutable \u8D8A\u51FA\u53D7\u7BA1 gateway \u76EE\u5F55");return r}function Zi(e){if(!e||typeof e!=="object"||Array.isArray(e))throw Error("\u5FC5\u987B\u662F JSON \u5BF9\u8C61");let t=e,r=t.source;if(!r||typeof r!=="object"||Array.isArray(r))throw Error("source \u5FC5\u987B\u662F JSON \u5BF9\u8C61");let n=r;if(n.kind!=="official"&&n.kind!=="local")throw Error("source.kind \u975E\u6CD5");let i=["releaseKey","gatewayVersion","ctpSdkVersion","platform","arch","relativeExecutable","contentSha256","installedAt"];for(let o of i)if(typeof t[o]!=="string"||!t[o].trim())throw Error(`${o} \u975E\u6CD5`);if(t.formatVersion!==Ji)throw Error("formatVersion \u4E0D\u517C\u5BB9");if(!Number.isInteger(t.protocolVersion)||t.protocolVersion<1)throw Error("protocolVersion \u975E\u6CD5");if(!/^[a-f0-9]{64}$/.test(t.contentSha256))throw Error("contentSha256 \u975E\u6CD5");if(t.archiveSha256!==void 0&&!/^[a-f0-9]{64}$/.test(String(t.archiveSha256)))throw Error("archiveSha256 \u975E\u6CD5");if(typeof n.reference!=="string"||!n.reference)throw Error("source.reference \u975E\u6CD5");return t}import{existsSync as an,realpathSync as Xi}from"fs";import{dirname as eo,join as dn,resolve as on}from"path";var sn=process.platform==="win32"?"ctp_gateway.exe":"ctp_gateway",it=import.meta.dir,cn=it;try{cn=eo(Xi(Bun.main))}catch{}var to="vibek-package.json";function ro(e){return[...new Set(e)].find((t)=>an(dn(t,to)))??""}var ot=ro([cn,it]);var Yt=[on(it,"..","..","ctp-gateway","build","bin","Debug",sn),on(it,"..","..","..","..","ctp-gateway","build","bin","Debug",sn)];function no(e=process.platform){let t=e==="win32"?"ctp_gateway.exe":"ctp_gateway";return e==="linux"?"/opt/vibek-gateway/current/ctp_gateway":e==="darwin"?"/usr/local/lib/vibek-gateway/current/ctp_gateway":dn(process.env.ProgramData??"C:\\ProgramData","Vibek","gateway","current",t)}var un=Yt.find((e)=>an(e))??(ot?no():Yt[Yt.length-1]);var te=ue(),Wt=process.env.VIBEK_PROCESS_ROLE??"",at=Wt==="webui"||Wt==="hooks"?{name:"",source:"none"}:Xr(te),g=at.name?te.account:void 0,mn=g?.ctp.production_mode??!0,hn=g?.ctp.password??"",io=hn.trim().length>0,Jt=jt(g?.market?.record_instruments??[]),oo=nt(g?.market?.bar_hooks??[],Jt),P=le(te),pn=I(P,"hooks"),me=at.name?en(at.name,te):I(P,"accounts",".unresolved"),so=I(me,"vibek_bars.db"),ao=I(me,"contracts.jsonl"),co=I(me,"commissions.jsonl"),gn=ot?nn(Pe(P),{requireExecutable:!0}):null,uo=gn?Kt(Pe(P),gn):un;function st(e){let t=/^([01]\d|2[0-3]):([0-5]\d)$/.exec(e);return{minutes:Number(t[1])*60+Number(t[2]),label:e}}var re={processRole:Wt,account:at.name,packageRoot:ot,broker:g?.identity.broker_id??"",user:g?.identity.user??"",pass:g?io?hn:process.env.VIBEK_PASS||"":"",addr:g?.ctp.market_front??"",traderAddr:g?.ctp.trader_front??"",ctpFronts:g?.ctp.front_candidates?.join(",")??"",ctpProductionMode:mn,authCode:g?.ctp.auth_code??"",appId:g?.ctp.app_id??"",logLevel:g?.logging?.level??(mn?"info":"debug"),daemonPort:te.server?.daemon_port??3111,daemonHttpUrl:`http://127.0.0.1:${te.server?.daemon_port??3111}`,webui:{port:te.server?.webui_port??3211},gatewayPath:uo,timerHooks:(g?.timer_hooks??[]).map((e)=>({name:e.name,cron:e.cron,timezone:e.timezone,enabled:e.enabled??!0})),market:{instruments:Jt.map((e)=>e.instrumentID),declarations:Jt,barHooks:oo,tickTimeMode:g?.market?.tick_time_mode??"realtime",maxFutureTickMs:g?.market?.max_future_tick_ms??5000,testFlush:process.env.VIBEK_TEST_FLUSH!==void 0},trading:{maxOrderVolume:g?.trading?.max_order_volume??10,maxPosition:g?.trading?.max_position??20,maxActiveOpenOrders:g?.trading?.max_active_open_orders??5,dailyLimits:{totalOrderLimit:g?.trading?.daily_limits?.total_order_limit??20000,totalCancelLimit:g?.trading?.daily_limits?.total_cancel_limit??1e4,totalTradeLimit:g?.trading?.daily_limits?.total_trade_limit??1e4,instrumentOrderLimit:g?.trading?.daily_limits?.instrument_order_limit??2000,instrumentCancelLimit:g?.trading?.daily_limits?.instrument_cancel_limit??1000,instrumentTradeLimit:g?.trading?.daily_limits?.instrument_trade_limit??1000},duplicateOrders:{duplicateOrderLimit:g?.trading?.duplicate_orders?.duplicate_order_limit??10}},schedule:{enabled:g?.schedule?.enabled??!0,dayStart:st(g?.schedule?.day_start??"08:45"),dayStop:st(g?.schedule?.day_stop??"15:15"),nightStart:st(g?.schedule?.night_start??"20:45"),nightStop:st(g?.schedule?.night_stop??"02:35"),maxStartAttempts:g?.schedule?.max_start_attempts??4,retryBaseMs:g?.schedule?.retry_base_ms??30000,tickMs:g?.schedule?.tick_ms??30000}},pe={config:F(),root:P,accounts:I(P,"accounts"),ai:I(P,"ai"),aiWorkspace:ln(I(P,"ai-workspace"))||!ln(I(P,"tui-workspace"))?I(P,"ai-workspace"):I(P,"tui-workspace"),hooks:pn,hooksSocket:I(P,"run","hooks.sock"),hookHostsDb:I(pn,"hosts.sqlite"),data:me,logs:I(me,"logs"),gatewayWorkdir:I(me,"gateway"),db:g?.market?.db_path?W(g.market.db_path):so,contracts:g?.reference_data?.contracts_path?.trim()?W(g.reference_data.contracts_path):ao,commissions:g?.reference_data?.commissions_path?.trim()?W(g.reference_data.commissions_path):co};var dt=30000;var od=Number.MAX_VALUE/2;import{randomUUID as No}from"crypto";var ct=/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/,lo=/^[A-Z][A-Z0-9]*\.[A-Z]+$/,ut=/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;function O(e){return typeof e==="object"&&e!==null&&!Array.isArray(e)}function mo(e,t){let r=new Set(t);return Object.keys(e).every((n)=>r.has(n))}function y(e,t){return mo(e,t)&&t.every((r)=>Object.hasOwn(e,r))}function q(e,t=4096){return typeof e==="string"&&e.length>0&&e.length<=t}function ge(e){return typeof e==="string"&&lo.test(e)&&/\d/.test(e.split(".")[0]??"")}function lt(e){if(typeof e!=="string"||!/^\d{8}$/.test(e))return!1;let t=Number(e.slice(0,4)),r=Number(e.slice(4,6)),n=Number(e.slice(6,8)),i=new Date(Date.UTC(t,r-1,n));return i.getUTCFullYear()===t&&i.getUTCMonth()===r-1&&i.getUTCDate()===n}function Qt(e,t,r=new Set){if(e===null||typeof e==="string"||typeof e==="boolean")return e;if(typeof e==="number"){if(!Number.isFinite(e))throw Error(`${t} contains a non-finite number`);return e}if(typeof e!=="object")throw Error(`${t} is not JSON`);if(r.has(e))throw Error(`${t} contains a cycle`);r.add(e);try{if(Array.isArray(e)){let i=[];for(let o=0;o<e.length;o++){if(!Object.hasOwn(e,o))throw Error(`${t}[${o}] is not JSON`);i.push(Qt(e[o],`${t}[${o}]`,r))}return i}let n=Object.getPrototypeOf(e);if(n!==Object.prototype&&n!==null)throw Error(`${t} is not a plain object`);return Object.fromEntries(Object.entries(e).map(([i,o])=>[i,Qt(o,`${t}.${i}`,r)]))}finally{r.delete(e)}}function fn(e,t){if(!O(e))throw Error(`${t} is not JSON`);let r=Qt(e,t);if(!O(r))throw Error(`${t} is not JSON`);return r}function yn(e,t){if(!O(t))throw Error(`invalid payload for ${e}`);if(e==="daemon.ready"){if(y(t,[]))return{}}else if(e==="daemon.stopping"){if(y(t,["signal"])&&(t.signal==="API"||t.signal==="SIGINT"||t.signal==="SIGTERM"))return{signal:t.signal}}else if(e==="daemon.service.faulted"){if(y(t,["service","reason"])&&(t.service==="market"||t.service==="trading")&&typeof t.reason==="string"&&t.reason.length>0&&t.reason.length<=4096)return{service:t.service,reason:t.reason}}else if(y(t,["service"])&&(t.service==="market"||t.service==="trading"))return{service:t.service};throw Error(`invalid payload for ${e}`)}var bn=["1min","3min","5min","10min","15min","30min","1h","2h","4h","1d"];function Zt(e){return typeof e==="string"&&bn.includes(e)}function wn(e,t){let r=["vtSymbol","timeframe","barTime","open","high","low","close","volume","tradingDay"];if(typeof t!=="object"||t===null||Array.isArray(t))throw Error(`invalid payload for ${e}`);let n=t,i=y(n,r)||y(n,[...r,"boundaryReason"]),o=[n.open,n.high,n.low,n.close,n.volume];if(!i||!ge(n.vtSymbol)||!Zt(n.timeframe)||!Number.isSafeInteger(n.barTime)||Number(n.barTime)<0||!o.every((s)=>typeof s==="number"&&Number.isFinite(s))||Number(n.volume)<0||n.tradingDay!==null&&!lt(n.tradingDay)||n.boundaryReason!==void 0&&n.boundaryReason!=="break"&&n.boundaryReason!=="sessionEnd"&&n.boundaryReason!=="disconnect")throw Error(`invalid payload for ${e}`);return{vtSymbol:n.vtSymbol,timeframe:n.timeframe,barTime:n.barTime,open:n.open,high:n.high,low:n.low,close:n.close,volume:n.volume,tradingDay:n.tradingDay,...n.boundaryReason===void 0?{}:{boundaryReason:n.boundaryReason}}}var Sn=["strategy","manual"],vn=["max_order_volume","max_position","max_active_open_orders","trading_day_unresolved","trading_day_mismatch","daily_total_order_limit","daily_total_cancel_limit","daily_total_trade_limit","daily_instrument_order_limit","daily_instrument_cancel_limit","daily_instrument_trade_limit","duplicate_order_limit"];function kn(e,t){if(e!=="risk.order_rejected"||!O(t)||!y(t,["source","riskCode","mode","vtSymbol","tradingDay","direction","offset","price","volume","reason"].concat(Object.hasOwn(t,"strategyId")?["strategyId"]:[]))||!Sn.includes(t.source)||!vn.includes(t.riskCode)||t.mode!=="paper"&&t.mode!=="live"||!ge(t.vtSymbol)||t.tradingDay!==null&&!lt(t.tradingDay)||t.direction!=="long"&&t.direction!=="short"||!["open","close","closeToday","closeYesterday"].includes(String(t.offset))||typeof t.price!=="number"||!Number.isFinite(t.price)||t.price<=0||!Number.isSafeInteger(t.volume)||Number(t.volume)<=0||!q(t.reason)||Object.hasOwn(t,"strategyId")&&(typeof t.strategyId!=="string"||!ct.test(t.strategyId)))throw Error(`invalid payload for ${e}`);return{source:t.source,riskCode:t.riskCode,mode:t.mode,vtSymbol:t.vtSymbol,tradingDay:t.tradingDay,direction:t.direction,offset:t.offset,price:t.price,volume:t.volume,reason:t.reason,...typeof t.strategyId==="string"?{strategyId:t.strategyId}:{}}}var Tn=["window","windowId"];function po(e){return(e.window==="day"||e.window==="night")&&q(e.windowId,128)&&e.windowId.endsWith(`/${e.window}`)}function En(e,t){if(!O(t)||!po(t))throw Error(`invalid payload for ${e}`);let r={window:t.window,windowId:t.windowId};if(e==="schedule.window.opened"||e==="schedule.window.closed"){if(y(t,Tn))return r}else if(y(t,[...Tn,"attempts","reason"])&&Number.isSafeInteger(t.attempts)&&Number(t.attempts)>0&&q(t.reason))return{...r,attempts:t.attempts,reason:t.reason};throw Error(`invalid payload for ${e}`)}var he=["strategyId","mode","vtSymbol"];function go(e){return typeof e.strategyId==="string"&&ct.test(e.strategyId)&&(e.mode==="paper"||e.mode==="live")&&ge(e.vtSymbol)}function ho(e){return{strategyId:e.strategyId,mode:e.mode,vtSymbol:e.vtSymbol}}function In(e,t){if(!O(t)||!go(t))throw Error(`invalid payload for ${e}`);let r=ho(t);switch(e){case"strategy.loaded":if(!y(t,[...he,"replaced"])||typeof t.replaced!=="boolean")break;return{...r,replaced:t.replaced};case"strategy.unloaded":case"strategy.enabled":case"strategy.disabled":case"strategy.started":case"strategy.stopped":if(!y(t,he))break;return r;case"strategy.start_failed":if(!y(t,[...he,"reason"])||!q(t.reason))break;return{...r,reason:t.reason};case"strategy.orders_retained":if(!y(t,[...he,"activeOrders","reason"])||!Number.isSafeInteger(t.activeOrders)||Number(t.activeOrders)<=0||!q(t.reason))break;return{...r,activeOrders:t.activeOrders,reason:t.reason};case"strategy.faulted":if(!y(t,[...he,"callback","reason"])||!q(t.callback,128)||!q(t.reason))break;return{...r,callback:t.callback,reason:t.reason};case"strategy.event":{if(!y(t,[...he,"name","data"])||typeof t.name!=="string"||!ut.test(t.name))break;let n=fn(t.data,"strategy.event.data");return{...r,name:t.name,data:n}}}throw Error(`invalid payload for ${e}`)}function Dn(e,t){if(!O(t)||!y(t,["name"])||typeof t.name!=="string"||!ut.test(t.name))throw Error(`invalid payload for ${e}`);return{name:t.name}}var Xt=65536;function er(e,t){if(e.startsWith("risk."))return kn(e,t);if(e.startsWith("strategy."))return In(e,t);if(e.startsWith("timer."))return Dn(e,t);if(e.startsWith("schedule."))return En(e,t);if(e.startsWith("market."))return wn(e,t);return yn(e,t)}function fe(e){let t=new TextEncoder().encode(JSON.stringify(e));if(t.byteLength>Xt)throw Error(`hook frame exceeds ${Xt} bytes`);let r=new Uint8Array(4+t.byteLength);return new DataView(r.buffer).setUint32(0,t.byteLength,!1),r.set(t,4),r}import{Database as So}from"bun:sqlite";function Rn(e){e.run("PRAGMA journal_mode=WAL"),e.run(`
|
|
5
|
+
CREATE TABLE IF NOT EXISTS bars (
|
|
6
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
7
|
+
code TEXT NOT NULL,
|
|
8
|
+
timeframe TEXT NOT NULL,
|
|
9
|
+
bar_time INTEGER NOT NULL, -- epoch \u79D2
|
|
10
|
+
open REAL NOT NULL,
|
|
11
|
+
high REAL NOT NULL,
|
|
12
|
+
low REAL NOT NULL,
|
|
13
|
+
close REAL NOT NULL,
|
|
14
|
+
volume INTEGER NOT NULL DEFAULT 0,
|
|
15
|
+
trading_day TEXT CHECK(
|
|
16
|
+
trading_day IS NULL OR (length(trading_day) = 8 AND trading_day NOT GLOB '*[^0-9]*')
|
|
17
|
+
),
|
|
18
|
+
boundary_reason TEXT CHECK(boundary_reason IN ('break', 'sessionEnd', 'disconnect')),
|
|
19
|
+
created_at INTEGER NOT NULL DEFAULT 0,
|
|
20
|
+
UNIQUE(code, timeframe, bar_time)
|
|
21
|
+
)
|
|
22
|
+
`),e.run(`
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_bars_code_tf_time
|
|
24
|
+
ON bars(code, timeframe, bar_time)
|
|
25
|
+
`)}function ne(e){return{code:e.code,timeframe:e.timeframe,barTime:e.bar_time,open:e.open,high:e.high,low:e.low,close:e.close,volume:e.volume,...e.trading_day?{tradingDay:e.trading_day}:{},...e.boundary_reason?{boundaryReason:e.boundary_reason}:{}}}var xn=pe.db;function nr(e=!1,t){return new _n(t??xn,e)}class _n{db;barCallbacks=[];constructor(e,t=!1){if(this.db=new So(e,{readonly:t,create:!t}),!t)Rn(this.db)}async getHistoricalBars(e,t,r){return this.db.query(`SELECT code, timeframe, bar_time, open, high, low, close, volume, trading_day, boundary_reason, created_at
|
|
26
|
+
FROM bars WHERE code = ? AND timeframe = ? ORDER BY bar_time ASC LIMIT ?`).all(e,t,r).map(ne)}async getLatestBars(e,t,r){return this.db.query(`SELECT code, timeframe, bar_time, open, high, low, close, volume, trading_day, boundary_reason, created_at
|
|
27
|
+
FROM (SELECT code, timeframe, bar_time, open, high, low, close, volume, trading_day, boundary_reason, created_at
|
|
28
|
+
FROM bars WHERE code = ? AND timeframe = ?
|
|
29
|
+
ORDER BY bar_time DESC LIMIT ?)
|
|
30
|
+
ORDER BY bar_time ASC`).all(e,t,r).map(ne)}async getBarsBefore(e,t,r,n){return this.db.query(`SELECT code, timeframe, bar_time, open, high, low, close, volume, trading_day, boundary_reason, created_at
|
|
31
|
+
FROM bars WHERE code = ? AND timeframe = ? AND bar_time < ?
|
|
32
|
+
ORDER BY bar_time ASC LIMIT ?`).all(e,t,r,n).map(ne)}async getBarsInRange(e,t,r,n){return this.db.query(`SELECT code, timeframe, bar_time, open, high, low, close, volume, trading_day, boundary_reason, created_at
|
|
33
|
+
FROM bars WHERE code = ? AND timeframe = ? AND bar_time >= ? AND bar_time <= ?
|
|
34
|
+
ORDER BY bar_time ASC`).all(e,t,r,n).map(ne)}getInstruments(e){return this.db.query(`SELECT code, timeframe, COUNT(*) AS barCount,
|
|
35
|
+
MIN(bar_time) AS firstBarTime, MAX(bar_time) AS lastBarTime
|
|
36
|
+
FROM bars ${e===void 0?"":"WHERE timeframe = ?"}
|
|
37
|
+
GROUP BY code, timeframe ORDER BY code, timeframe`).all(...e===void 0?[]:[e])}getInstrumentSummary(e,t,r){let n=(r===void 0?this.db.query(`SELECT COUNT(*) AS barCount, MIN(bar_time) AS firstBarTime, MAX(bar_time) AS lastBarTime
|
|
38
|
+
FROM bars WHERE code = ? AND timeframe = ?`).get(e,t):this.db.query(`SELECT COUNT(*) AS barCount, MIN(bar_time) AS firstBarTime, MAX(bar_time) AS lastBarTime
|
|
39
|
+
FROM bars WHERE code = ? AND timeframe = ? AND bar_time <= ?`).get(e,t,r))??null;if(!n||n.barCount===0||n.firstBarTime===null||n.lastBarTime===null)return null;return{code:e,timeframe:t,...n,firstBarTime:n.firstBarTime,lastBarTime:n.lastBarTime}}getBarAt(e,t,r){let n=this.db.query(`SELECT code, timeframe, bar_time, open, high, low, close, volume, trading_day, boundary_reason, created_at
|
|
40
|
+
FROM bars WHERE code = ? AND timeframe = ? AND bar_time = ?`).get(e,t,r);return n?ne(n):null}*iterateBars(e,t){let r=this.db.prepare(`SELECT code, timeframe, bar_time, open, high, low, close, volume, trading_day, boundary_reason, created_at
|
|
41
|
+
FROM bars WHERE code = ? AND timeframe = ? ORDER BY bar_time ASC`);try{for(let n of r.iterate(e,t))yield ne(n)}finally{r.finalize()}}*iterateBarsRange(e,t,r,n){let i=this.db.prepare(`SELECT code, timeframe, bar_time, open, high, low, close, volume, trading_day, boundary_reason, created_at
|
|
42
|
+
FROM bars WHERE code = ? AND timeframe = ? AND bar_time > ? AND bar_time <= ? ORDER BY bar_time ASC`);try{for(let o of i.iterate(e,t,r,n))yield ne(o)}finally{i.finalize()}}getAllCodes(){return this.db.query("SELECT DISTINCT code FROM bars ORDER BY code").all().map((t)=>t.code)}insertBars(e){let t=Math.floor(Date.now()/1000),r=this.db.prepare(`INSERT OR IGNORE INTO bars (code, timeframe, bar_time, open, high, low, close, volume, trading_day, boundary_reason, created_at)
|
|
43
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),n=0;try{for(let i of e)if(r.run(i.code,i.timeframe,i.barTime,i.open,i.high,i.low,i.close,i.volume,i.tradingDay??null,i.boundaryReason??null,t).changes>0)n++;return n}finally{r.finalize()}}onBar(e){this.barCallbacks.push(e)}emitBar(e){for(let t of this.barCallbacks)t(e)}destroy(){this.barCallbacks=[],this.db.close()}}import{existsSync as vo,mkdirSync as ko,readFileSync as To,renameSync as Eo,unlinkSync as Cn,writeFileSync as Io}from"fs";import{randomUUID as Do}from"crypto";import{join as ir}from"path";var Oo=new Set(["strategyId","vtSymbol","mode","bundleHash","savedAt","pos","variables","inventory","tradingDay"]);class $n extends Error{details;code="strategy_state_incompatible";constructor(e){super(`\u7B56\u7565 ${e.oldState.strategyId}@${e.oldState.mode} \u72B6\u6001 schema \u4E0D\u517C\u5BB9\uFF0C\u672A\u52A0\u8F7D\u65B0\u7B56\u7565\u3002
|
|
44
|
+
`+`\u72B6\u6001\u6587\u4EF6: ${e.statePath}
|
|
45
|
+
`+`\u65E7 schema: ${JSON.stringify(e.oldSchema)}
|
|
46
|
+
`+`\u65B0\u7B56\u7565\u671F\u671B schema: ${JSON.stringify(e.newSchema)}
|
|
47
|
+
`+`\u82E5\u8981\u653E\u5F03\u65E7\u72B6\u6001\uFF0C\u8BF7\u6267\u884C vibek strategy unload ${e.oldState.strategyId}@${e.oldState.mode} \u540E\u91CD\u65B0 load\uFF1B`+"unload \u4F1A\u6C38\u4E45\u5220\u9664\u8BE5\u6A21\u5F0F\u7684\u72B6\u6001\u3001\u6301\u4ED3\u548C\u53D8\u91CF\u3002\u82E5\u5F53\u524D\u6CA1\u6709\u5DF2\u52A0\u8F7D\u5B9E\u4F8B\uFF0C\u8BF7\u6309\u4E0A\u8FF0\u8DEF\u5F84\u624B\u52A8\u5220\u9664\u65E7 state \u6587\u4EF6\u3002");this.details=e;this.name="StrategyStateCompatibilityError"}}function or(e){return e!==null&&typeof e==="object"&&!Array.isArray(e)}function mt(e,t,r){if(!Number.isInteger(e)||e<0)throw Error(`${r}: ${t} \u975E\u6CD5`);return e}function Pn(e,t){if(!or(e))throw Error(`${t}: \u7B56\u7565\u72B6\u6001\u5FC5\u987B\u662F\u5BF9\u8C61`);if(Object.keys(e).some((i)=>!Oo.has(i)))throw Error(`${t}: \u7B56\u7565\u72B6\u6001\u5305\u542B\u672A\u77E5\u5B57\u6BB5`);if(typeof e.strategyId!=="string"||typeof e.vtSymbol!=="string"||e.mode!=="paper"&&e.mode!=="live")throw Error(`${t}: \u7B56\u7565\u72B6\u6001\u8EAB\u4EFD\u975E\u6CD5`);if(typeof e.bundleHash!=="string"||!/^[a-f0-9]{64}$/.test(e.bundleHash)||typeof e.savedAt!=="string"||!or(e.variables)||typeof e.tradingDay!=="string"||!or(e.inventory))throw Error(`${t}: \u7B56\u7565\u72B6\u6001\u5185\u5BB9\u975E\u6CD5`);let r={longToday:mt(e.inventory.longToday,"longToday",t),longYesterday:mt(e.inventory.longYesterday,"longYesterday",t),shortToday:mt(e.inventory.shortToday,"shortToday",t),shortYesterday:mt(e.inventory.shortYesterday,"shortYesterday",t)};if(!Number.isInteger(e.pos))throw Error(`${t}: pos \u975E\u6CD5`);if(r.longToday+r.longYesterday-r.shortToday-r.shortYesterday!==e.pos)throw Error(`${t}: pos \u4E0E inventory \u4E0D\u4E00\u81F4`);return{strategyId:e.strategyId,vtSymbol:e.vtSymbol,mode:e.mode,bundleHash:e.bundleHash,savedAt:e.savedAt,pos:e.pos,variables:e.variables,inventory:r,tradingDay:e.tradingDay}}class $e{root;constructor(e=pe.data){this.root=ir(e,"strategy-state")}read(e,t){let r=this.path(e,t);if(!vo(r))return null;return Pn(JSON.parse(To(r,"utf8")),r)}assertCompatible(e,t,r,n){let i=this.read(e,t);if(!i)return;if(i.strategyId!==e||i.mode!==t)throw Error(`${this.path(e,t)}: \u7B56\u7565 ${e} \u72B6\u6001\u8EAB\u4EFD\u4E0D\u5339\u914D`);if(i.vtSymbol!==r)throw Error(`${this.path(e,t)}: \u7B56\u7565 ${e} \u7684\u72B6\u6001\u5C5E\u4E8E ${i.vtSymbol}\uFF0C\u4E0D\u80FD\u6062\u590D\u5230 ${r}`);let o=Object.keys(i.variables).sort(),s=[...n].sort(),a=new Set(o),d=new Set(s),c=s.filter((l)=>!a.has(l)),u=o.filter((l)=>!d.has(l));if(c.length===0&&u.length===0)return;throw new $n({statePath:this.path(e,t),oldState:i,oldSchema:{variables:o},newSchema:{variables:s},addedVariables:c,removedVariables:u})}write(e){let t=this.path(e.strategyId,e.mode);ko(ir(this.root,e.strategyId),{recursive:!0});let r={savedAt:new Date().toISOString(),...e};Pn(r,t);let n=`${JSON.stringify(r,null,2)}
|
|
48
|
+
`;if(Buffer.byteLength(n)>262144)throw Error(`\u7B56\u7565 ${e.strategyId} variables \u8D85\u8FC7 256KB`);let i=`${t}.${process.pid}-${Do()}.tmp`;try{Io(i,n,{encoding:"utf8",mode:384,flag:"wx"}),Eo(i,t)}finally{try{Cn(i)}catch{}}}remove(e,t){let r=this.path(e,t);try{return Cn(r),!0}catch(n){if(n.code==="ENOENT")return!1;throw n}}path(e,t){if(!we.test(e))throw Error(`\u7B56\u7565 ID \u975E\u6CD5: ${e}`);if(t!=="paper"&&t!=="live")throw Error(`\u7B56\u7565 mode \u975E\u6CD5: ${t}`);return ir(this.root,e,`${t}.json`)}}import{randomUUID as Ro}from"crypto";import{existsSync as xo,mkdirSync as _o,readFileSync as Co,renameSync as Po,unlinkSync as $o,writeFileSync as Mo}from"fs";import{dirname as Ao,join as Bo}from"path";var pt=(e,t)=>{let r=setTimeout(e,t);return()=>clearTimeout(r)};var Mn=4194304,gt=/^\d{8}$/;function ar(){return{orders:0,cancels:0,trades:0}}function Me(e=""){return{tradingDay:e,total:ar(),instruments:new Map,duplicateAttempts:new Map,duplicateRejected:0,orderIds:new Set,cancelledOrderIds:new Set,tradeIds:new Set}}function Ae(e){return e!==null&&typeof e==="object"&&!Array.isArray(e)}function Be(e,t){if(!Number.isSafeInteger(e)||e<0)throw Error(`${t} \u5FC5\u987B\u662F\u975E\u8D1F\u6574\u6570`);return e}function An(e,t){if(!Ae(e))throw Error(`${t} \u5FC5\u987B\u662F JSON \u5BF9\u8C61`);return{orders:Be(e.orders,`${t}.orders`),cancels:Be(e.cancels,`${t}.cancels`),trades:Be(e.trades,`${t}.trades`)}}function Bn(e,t){if(!Ae(e))throw Error(`${t} \u5FC5\u987B\u662F JSON \u5BF9\u8C61`);let r=typeof e.tradingDay==="string"?e.tradingDay:"";if(r&&!gt.test(r))throw Error(`${t}.tradingDay \u975E\u6CD5`);if(!Ae(e.instruments))throw Error(`${t}.instruments \u5FC5\u987B\u662F JSON \u5BF9\u8C61`);let n={};for(let[o,s]of Object.entries(e.instruments)){if(!o||o.length>128)throw Error(`${t}.instruments key \u975E\u6CD5`);n[o]=An(s,`${t}.instruments.${o}`)}if(!Ae(e.duplicateAttempts))throw Error(`${t}.duplicateAttempts \u5FC5\u987B\u662F JSON \u5BF9\u8C61`);let i={};for(let[o,s]of Object.entries(e.duplicateAttempts))i[o]=Be(s,`${t}.duplicateAttempts`);return{tradingDay:r,total:An(e.total,`${t}.total`),instruments:n,duplicateAttempts:i,duplicateRejected:Be(e.duplicateRejected,`${t}.duplicateRejected`)}}function Hn(e,t){if(!Ae(e)||Object.keys(e).some((r)=>!["savedAt","paper","live"].includes(r))||typeof e.savedAt!=="string")throw Error(`${t}: \u98CE\u63A7\u72B6\u6001\u7ED3\u6784\u975E\u6CD5`);return{savedAt:e.savedAt,paper:Bn(e.paper,`${t}.paper`),live:Bn(e.live,`${t}.live`)}}function Ln(e){return{tradingDay:e.tradingDay,total:{...e.total},instruments:new Map(Object.entries(e.instruments).map(([t,r])=>[t,{...r}])),duplicateAttempts:new Map(Object.entries(e.duplicateAttempts)),duplicateRejected:e.duplicateRejected,orderIds:new Set,cancelledOrderIds:new Set,tradeIds:new Set}}function Nn(e){return{tradingDay:e.tradingDay,total:{...e.total},instruments:Object.fromEntries([...e.instruments.entries()].sort(([t],[r])=>t.localeCompare(r)).map(([t,r])=>[t,{...r}])),duplicateAttempts:Object.fromEntries([...e.duplicateAttempts.entries()].sort(([t],[r])=>t.localeCompare(r))),duplicateRejected:e.duplicateRejected}}function Ho(){return{dailyLimits:{...re.trading.dailyLimits},duplicateOrderLimit:re.trading.duplicateOrders.duplicateOrderLimit}}function Fn(e){return JSON.stringify([e.vtSymbol.toUpperCase(),e.direction,e.offset,e.price,e.volume])}function sr(e,t){e.orders=Math.max(e.orders,t.orders),e.cancels=Math.max(e.cancels,t.cancels),e.trades=Math.max(e.trades,t.trades)}function Lo(e){let t=ar();for(let r of e)t.orders+=r.orders,t.cancels+=r.cancels,t.trades+=r.trades;return t}class dr{path;constructor(e=pe.data){this.path=Bo(e,"risk-state.json")}read(){if(!xo(this.path))return null;let e=Co(this.path,"utf8");if(Buffer.byteLength(e)>Mn)throw Error(`${this.path}: \u98CE\u63A7\u72B6\u6001\u8D85\u8FC7 4MB`);try{return Hn(JSON.parse(e),this.path)}catch(t){throw Error(`${this.path}: \u98CE\u63A7\u72B6\u6001\u65E0\u6548: ${t instanceof Error?t.message:String(t)}`)}}write(e){let t={savedAt:new Date().toISOString(),...e};Hn(t,this.path);let r=`${JSON.stringify(t,null,2)}
|
|
49
|
+
`;if(Buffer.byteLength(r)>Mn)throw Error(`${this.path}: \u98CE\u63A7\u72B6\u6001\u8D85\u8FC7 4MB`);_o(Ao(this.path),{recursive:!0});let n=`${this.path}.${process.pid}-${Ro()}.tmp`;try{Mo(n,r,{encoding:"utf8",mode:384,flag:"wx"}),Po(n,this.path)}finally{try{$o(n)}catch{}}}}class He{settings;paper=Me();live=Me();stateStore;persistDelayMs;schedule;persistTimer=null;dirty=!1;constructor(e={}){this.settings=e.settings??Ho(),this.stateStore=e.stateStore??null,this.persistDelayMs=e.persistDelayMs??250,this.schedule=e.schedule??pt;let t=this.stateStore?.read();if(t)this.paper=Ln(t.paper),this.live=Ln(t.live)}setTradingDay(e,t){if(!gt.test(t))return;let r=this.mode(e);if(r.tradingDay===t)return;if(r.tradingDay&&t<r.tradingDay)return;this.replaceMode(e,Me(t)),this.markDirty()}checkOpen(e){return this.checkOpenDetailed(e)?.message??null}checkOpenDetailed(e,t={}){if(e.offset!=="open")return null;let r=t.recordAttempt!==!1,n;if(r)this.setTradingDay(e.mode,e.tradingDay),n=this.mode(e.mode);else{let a=this.mode(e.mode);n=gt.test(e.tradingDay)&&(!a.tradingDay||e.tradingDay>a.tradingDay)?Me(e.tradingDay):a}if(!n.tradingDay)return{code:"trading_day_unresolved",message:"\u98CE\u63A7 TradingDay \u5C1A\u672A\u786E\u5B9A"};if(n.tradingDay!==e.tradingDay)return{code:"trading_day_mismatch",message:`\u98CE\u63A7 TradingDay \u4E0D\u4E00\u81F4 ${n.tradingDay}/${e.tradingDay||"(\u7A7A)"}`};let i=this.dailyFailure(n,e.vtSymbol.toUpperCase());if(i)return i;let o=Fn(e),s=(n.duplicateAttempts.get(o)??0)+1;if(s>=this.settings.duplicateOrderLimit){if(r)n.duplicateAttempts.set(o,s),n.duplicateRejected++,this.markDirty();return{code:"duplicate_order_limit",message:`\u76F8\u540C\u5F00\u4ED3\u8BF7\u6C42\u5F53\u65E5\u7B2C ${s} \u6B21\uFF0C\u8FBE\u5230\u91CD\u590D\u62A5\u5355\u9608\u503C ${this.settings.duplicateOrderLimit}`}}if(r)n.duplicateAttempts.set(o,s),this.markDirty();return null}recordOrder(e,t,r=""){if(r)this.setTradingDay(e,r);let n=this.mode(e);if(!n.tradingDay||r&&n.tradingDay!==r)return;let i=t.vtSymbol.toUpperCase(),o=!1;if(!n.orderIds.has(t.vtOrderId))n.orderIds.add(t.vtOrderId),this.increment(n,i,"orders"),o=!0;if(t.status==="cancelled"&&!n.cancelledOrderIds.has(t.vtOrderId))n.cancelledOrderIds.add(t.vtOrderId),this.increment(n,i,"cancels"),o=!0;if(o)this.markDirty()}recordTrade(e,t){this.setTradingDay(e,t.tradingDay);let r=this.mode(e);if(!r.tradingDay||r.tradingDay!==t.tradingDay||r.tradeIds.has(t.vtTradeId))return;r.tradeIds.add(t.vtTradeId),this.increment(r,t.vtSymbol.toUpperCase(),"trades"),this.markDirty()}reconcileLive(e){if(!gt.test(e.tradingDay))throw Error(`Live \u98CE\u63A7 TradingDay \u975E\u6CD5: ${e.tradingDay}`);if(this.setTradingDay("live",e.tradingDay),this.live.tradingDay!==e.tradingDay)throw Error(`Live \u98CE\u63A7 TradingDay \u56DE\u9000: ${this.live.tradingDay}/${e.tradingDay}`);let t=this.live,r=Me(e.tradingDay);for(let n of e.orders)this.addSnapshotOrder(r,n);for(let n of e.trades)this.addSnapshotTrade(r,n);t.orderIds=new Set([...t.orderIds,...r.orderIds]),t.cancelledOrderIds=new Set([...t.cancelledOrderIds,...r.cancelledOrderIds]),t.tradeIds=new Set([...t.tradeIds,...r.tradeIds]),sr(t.total,r.total);for(let[n,i]of r.instruments){let o=this.instrumentCounts(t,n);sr(o,i)}sr(t.total,Lo(t.instruments.values()));for(let[n,i]of r.duplicateAttempts)t.duplicateAttempts.set(n,Math.max(t.duplicateAttempts.get(n)??0,i));this.markDirty()}snapshot(){return{limits:{dailyLimits:{...this.settings.dailyLimits},duplicateOrderLimit:this.settings.duplicateOrderLimit},paper:this.modeSnapshot(this.paper),live:this.modeSnapshot(this.live)}}tradingDay(e){return this.mode(e).tradingDay}flush(){if(this.persistTimer?.(),this.persistTimer=null,!this.dirty||!this.stateStore)return;this.stateStore.write({paper:Nn(this.paper),live:Nn(this.live)}),this.dirty=!1}close({persist:e=!0}={}){if(e)this.flush();else this.persistTimer?.(),this.persistTimer=null}dailyFailure(e,t){let r=this.settings.dailyLimits,n=this.instrumentCounts(e,t),o=[[e.total.orders,r.totalOrderLimit,"\u5F53\u65E5\u5168\u5C40\u59D4\u6258","daily_total_order_limit"],[e.total.cancels,r.totalCancelLimit,"\u5F53\u65E5\u5168\u5C40\u64A4\u5355","daily_total_cancel_limit"],[e.total.trades,r.totalTradeLimit,"\u5F53\u65E5\u5168\u5C40\u6210\u4EA4","daily_total_trade_limit"],[n.orders,r.instrumentOrderLimit,`${t} \u5F53\u65E5\u59D4\u6258`,"daily_instrument_order_limit"],[n.cancels,r.instrumentCancelLimit,`${t} \u5F53\u65E5\u64A4\u5355`,"daily_instrument_cancel_limit"],[n.trades,r.instrumentTradeLimit,`${t} \u5F53\u65E5\u6210\u4EA4`,"daily_instrument_trade_limit"]].find(([s,a])=>s>=a);return o?{code:o[3],message:`${o[2]}\u8FBE\u5230\u4E0A\u9650 ${o[0]}/${o[1]}`}:null}addSnapshotOrder(e,t){if(t.identity.tradingDay!==e.tradingDay||e.orderIds.has(t.vtOrderId))return;if(e.orderIds.add(t.vtOrderId),this.increment(e,t.vtSymbol.toUpperCase(),"orders"),t.status==="cancelled")e.cancelledOrderIds.add(t.vtOrderId),this.increment(e,t.vtSymbol.toUpperCase(),"cancels");if(t.offset==="open"){let r=Fn({vtSymbol:t.vtSymbol,direction:t.direction,offset:t.offset,price:t.price,volume:t.volume});e.duplicateAttempts.set(r,(e.duplicateAttempts.get(r)??0)+1)}}addSnapshotTrade(e,t){if(t.tradingDay!==e.tradingDay||e.tradeIds.has(t.vtTradeId))return;e.tradeIds.add(t.vtTradeId),this.increment(e,t.vtSymbol.toUpperCase(),"trades")}increment(e,t,r){e.total[r]++,this.instrumentCounts(e,t)[r]++}instrumentCounts(e,t){let r=e.instruments.get(t);if(!r)r=ar(),e.instruments.set(t,r);return r}mode(e){return e==="paper"?this.paper:this.live}replaceMode(e,t){if(e==="paper")this.paper=t;else this.live=t}modeSnapshot(e){let t=0,r=0;for(let n of e.duplicateAttempts.values()){if(n>=2)t++;r=Math.max(r,n)}return{tradingDay:e.tradingDay,total:{...e.total},instruments:Object.fromEntries([...e.instruments.entries()].sort(([n],[i])=>n.localeCompare(i)).map(([n,i])=>[n,{...i}])),duplicateRequests:{trackedKeys:e.duplicateAttempts.size,repeatedKeys:t,maxAttempts:r,rejected:e.duplicateRejected}}}markDirty(){if(this.dirty=!0,!this.stateStore||this.persistTimer)return;this.persistTimer=this.schedule(()=>{this.persistTimer=null,this.flush()},this.persistDelayMs)}}var qn=1e5,Fo=1e5,qo=500;function cr(){return{longToday:0,longYesterday:0,shortToday:0,shortYesterday:0}}function B(e,t){return`${t}:${e}`}function Vn(e){return e!==null&&typeof e==="object"&&!Array.isArray(e)}function lr(e,t,r){if(e===null||typeof e==="string"||typeof e==="boolean")return;if(typeof e==="number"){if(!Number.isFinite(e))throw Error(`${t} \u5305\u542B\u975E\u6709\u9650\u6570\u5B57`);return}if(typeof e!=="object")throw Error(`${t} \u5305\u542B\u4E0D\u53EF\u5E8F\u5217\u5316\u7684 ${typeof e}`);if(r.has(e))throw Error(`${t} \u5305\u542B\u5FAA\u73AF\u5F15\u7528`);if(r.add(e),Array.isArray(e))for(let n=0;n<e.length;n++)lr(e[n],`${t}[${n}]`,r);else{let n=Object.getPrototypeOf(e);if(n!==Object.prototype&&n!==null)throw Error(`${t} \u5FC5\u987B\u662F\u666E\u901A\u5BF9\u8C61\u6216\u6570\u7EC4`);for(let[i,o]of Object.entries(e))lr(o,`${t}.${i}`,r)}r.delete(e)}function Un(e,t){return lr(e,t,new Set),JSON.parse(JSON.stringify(e))}function zn(e){return`${e.code}\x00${e.timeframe}\x00${e.barTime}`}function J(e){return e.longToday+e.longYesterday-e.shortToday-e.shortYesterday}function ur(e){if(!Vn(e))throw Error("\u7F3A\u5C11\u7B56\u7565\u5B9E\u4F8B\u914D\u7F6E");let{id:t,vtSymbol:r,setting:n}=e,i=Object.keys(e).find((o)=>o!=="id"&&o!=="vtSymbol"&&o!=="setting");if(i)throw Error(`\u7B56\u7565\u5B9E\u4F8B\u914D\u7F6E\u4E0D\u652F\u6301\u5B57\u6BB5 "${i}"`);if(typeof t!=="string"||!we.test(t))throw Error("\u7B56\u7565 id \u5FC5\u987B\u4E3A 1-64 \u4F4D\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u70B9\u3001\u4E0B\u5212\u7EBF\u6216\u8FDE\u5B57\u7B26\uFF0C\u5E76\u4EE5\u5B57\u6BCD\u6216\u6570\u5B57\u5F00\u5934");if(typeof r!=="string")throw Error(`\u7B56\u7565 ${t} \u7F3A\u5C11 vtSymbol`);if(!Vn(n))throw Error(`\u7B56\u7565 ${t} \u7684 setting \u5FC5\u987B\u662F\u5BF9\u8C61`);return{id:t,vtSymbol:K(r).vtSymbol,setting:Un(n,`\u7B56\u7565 ${t} setting`)}}function Vo(){let e=nr(!0);return{getBars:async(t,r)=>{let n=xr(r.timeframe,r.count),i=await e.getLatestBars(t,"1min",n),o=ke(i,r.timeframe,r.count);if(o.selectedCount<r.count)throw Error(`\u5408\u7EA6 ${t} \u9884\u70ED\u6570\u636E\u4E0D\u8DB3\uFF1A\u9700\u8981 ${r.count} \u6839\u95ED\u5408 ${r.timeframe} Bar\uFF0C\u4EC5\u6709 ${o.availableCount} \u6839`);return o.bars},destroy:()=>e.destroy()}}class mr{strategies=new Map;instanceMap=new Map;orderOwners=new Map;terminalPaperOrders=new Set;liveOrders=new Map;liveTradeIds=new Set;pending=new Map;deps;cleanups=[];eventQueue=[];dispatching=!1;initializing=null;liveTradingDay="";timerCursorMs=null;eventTimeMs=null;tradeClaimContext=null;constructor(e={}){if(this.deps={createWarmupProvider:Vo,stateStore:new $e,live:null,paperExecution:null,recordTrade:null,orderRisk:new He,executionLimits:{maxOrderVolume:re.trading.maxOrderVolume,maxPosition:re.trading.maxPosition,maxActiveOpenOrders:re.trading.maxActiveOpenOrders},log:(t)=>console.log(t),now:Date.now,schedule:pt,createPaperSessionId:No,observe:()=>{},emitHook:null,...e},this.deps.live)this.cleanups.push(this.deps.live.onOrder((t)=>this.enqueue(()=>{this.deps.orderRisk.recordOrder("live",t,t.identity.tradingDay),this.handleLiveOrder(t)})),this.deps.live.onTrade((t)=>this.enqueue(()=>{this.deps.orderRisk.recordTrade("live",t),this.handleLiveTrade(t)})),this.deps.live.onOrderError((t)=>this.enqueue(()=>this.handleLiveError(t))),this.deps.live.onReconciled((t)=>this.handleLiveReconciled(t)),this.deps.live.onFault((t)=>this.enqueue(()=>this.handleLiveFault(t))))}prepare(e){if(e.mode!=="paper"&&e.mode!=="live")throw Error("\u7B56\u7565 mode \u5FC5\u987B\u662F paper \u6216 live");let t=ur(e.instance),r=K(t.vtSymbol),n=Se(e.definition,t.id,t.setting),i={id:t.id,name:n.ctor.name||t.id,ctor:n.ctor,instance:n.instance,runtime:n.runtime,vtSymbol:r.vtSymbol,instrumentID:r.instrumentID,setting:n.effectiveSetting,enabled:e.enabled??e.mode==="paper",mode:e.mode,sourcePath:e.sourcePath,hash:e.hash,artifact:e.artifact,loadedAt:e.loadedAt,inventory:cr(),orders:new Map,inited:!1,trading:!1,fault:null,expectedLivePos:null,expectedLiveInventory:null,tradingDay:"",stateTimer:null,warmupRequests:[],paperSessionId:this.deps.createPaperSessionId(),paperOrderCounter:0,paperTradeCounter:0,seenTrades:new Set,tradeVolumeByOrder:new Map,eventHandlers:{timer:new Set,trades:new Set}};return n.runtime.bindContext({ctaEngine:this,strategyName:t.id,vtSymbol:r.vtSymbol,mode:e.mode,setting:n.effectiveSetting}),this.assertLiveInstrumentOwner(i),this.instanceMap.set(n.instance,i),i}addRestored(e){let t=ur(e.instance),r=B(t.id,e.mode);if(this.strategies.has(r))throw Error(`\u7B56\u7565\u5B9E\u4F8B\u91CD\u590D: ${t.id} (${e.mode})`);let n=this.prepare(e);return this.strategies.set(r,n),n}assertStateCompatible(e){this.deps.stateStore.assertCompatible(e.id,e.mode,e.vtSymbol,e.ctor.variables??[])}async warmup(){for(let e of this.strategies.values())if(!e.inited)await this.warmupOne(e)}canInitialize(){return this.deps.live?.phase==="running"}async prepareMetadata(){for(let e of this.strategies.values())await this.prepareContractMetadata(e)}async preparePendingMetadata(e,t){await this.prepareContractMetadata(this.requirePending(e,t).running)}beginReplace(e){let t=ur(e.instance),r=B(t.id,e.mode),n=this.strategies.get(r);if(n?.trading)throw Error(`\u7B56\u7565 ${n.id} \u8FD0\u884C\u4E2D\uFF0C\u8BF7\u5148\u505C\u6B62\u518D\u66F4\u65B0`);if(n)this.assertOrdersSettled(n.id,n.mode);if(n&&n.vtSymbol!==t.vtSymbol)throw Error(`\u7B56\u7565 ${n.id} \u5DF2\u7ED1\u5B9A ${n.vtSymbol}\uFF0C\u66F4\u6362\u5408\u7EA6\u5FC5\u987B\u4F7F\u7528\u65B0\u7684\u5B9E\u4F8B ID`);let i=this.prepare(e);if(this.pending.has(r))throw this.instanceMap.delete(i.instance),Error(`\u7B56\u7565 ${i.id} (${i.mode}) \u5DF2\u5728\u52A0\u8F7D\u4E2D`);return this.pending.set(r,{running:i,events:[],replayedBars:new Set,overflowed:!1,warmed:!1}),i}async warmupPending(e,t,{restoreState:r=!0}={}){let n=this.requirePending(e,t);if(await this.warmupOne(n.running,n.replayedBars,!1,r),n.warmed=!0,n.overflowed)throw Error(`\u7B56\u7565 ${e} \u521D\u59CB\u5316\u671F\u95F4\u7F13\u5B58\u884C\u60C5\u8D85\u8FC7 ${qn} \u6761`)}async commitPending(e,t){let r=this.requirePending(e,t),n=B(e,r.running.mode);if(!r.warmed)throw Error(`\u7B56\u7565 ${e} \u5C1A\u672A\u5B8C\u6210\u521D\u59CB\u5316`);if(r.overflowed)throw Error(`\u7B56\u7565 ${e} \u521D\u59CB\u5316\u671F\u95F4\u7F13\u5B58\u6EA2\u51FA`);if(this.drainPending(r),r.running.fault)throw Error(`\u7B56\u7565 ${e} \u521D\u59CB\u5316\u671F\u95F4\u5F02\u5E38: ${r.running.fault}`);this.saveState(r.running);let i=this.strategies.get(n);if(this.strategies.set(n,r.running),this.pending.delete(n),i)await this.dispose(i,!1);return r.running}async commitPendingUninitialized(e,t){let r=this.requirePending(e,t),n=B(e,r.running.mode);if(r.warmed)throw Error(`\u7B56\u7565 ${e} \u5DF2\u521D\u59CB\u5316\uFF0C\u4E0D\u80FD\u6309\u7B49\u5F85\u72B6\u6001\u63D0\u4EA4`);r.events.length=0;let i=this.strategies.get(n);if(this.strategies.set(n,r.running),this.pending.delete(n),i)await this.dispose(i,!1);return r.running}async abortPending(e,t){let r=this.findPending(e,t);if(!r)return;let n=B(e,r.running.mode);this.pending.delete(n),await this.dispose(r.running,!1)}async start(e,t){let r=this.requireRunning(e,t);if(r.trading)return r;try{if(!r.inited||r.fault)throw Error(`\u7B56\u7565 ${e} \u672A\u5B8C\u6210\u521D\u59CB\u5316${r.fault?`: ${r.fault}`:""}`);if(await this.prepareContractMetadata(r),r.mode==="live")await this.prepareLive(r);try{this.invokeStrategySync(r,"onStart",()=>r.instance.onStart())}catch(n){throw this.failStrategy(r,"onStart",n),n}r.trading=!0,this.syncFrameworkState(r);try{this.saveState(r)}catch(n){throw this.failStrategy(r,"state",n),n}return this.writeLog(r.instance,`started mode=${r.mode}`),this.publishHook("strategy.started",this.strategyIdentity(r)),r}catch(n){throw this.publishHook("strategy.start_failed",{...this.strategyIdentity(r),reason:n instanceof Error?n.message:String(n)}),n}}async startAll(){for(let e of this.strategies.values()){if(!e.enabled)continue;try{await this.start(e.id,e.mode)}catch(t){let r=t instanceof Error?t.message:String(t);console.error(`[cta] ${e.id} \u542F\u52A8\u5931\u8D25: ${r}`)}}}async stop(e,{cancelOrders:t=!1},r){let n=this.requireRunning(e,r);if(n.trading){n.trading=!1,this.syncFrameworkState(n);try{this.invokeStrategySync(n,"onStop",()=>n.instance.onStop())}catch(i){this.failStrategy(n,"onStop",i)}if(this.trySaveState(n),this.writeLog(n.instance,"stopped"),!n.fault)this.publishHook("strategy.stopped",this.strategyIdentity(n))}if(this.syncFrameworkState(n),t)this.cancelAllDirect(n);else{let i=[...n.orders.values()].filter(w).length;if(i>0){let o=this.retainedOrdersMessage(i);this.writeLog(n.instance,o),this.publishHook("strategy.orders_retained",{...this.strategyIdentity(n),activeOrders:i,reason:o})}}return n}async stopAll(e={}){for(let t of this.strategies.values())await this.stop(t.id,e,t.mode)}cancelStrategyOrder(e,t,r){let n=this.requireRunning(e,r),i=n.orders.get(t);if(!i)throw Error(`\u59D4\u6258\u4E0D\u5C5E\u4E8E\u7B56\u7565 ${e}: ${t}`);if(!w(i))return;if(n.mode==="paper")this.cancelOrder(n.instance,t);else{if(!this.deps.live)throw Error("\u5B9E\u76D8\u901A\u9053\u4E0D\u53EF\u7528");this.deps.live.cancelOrder(t)}}cancelPaperOrder(e){if(this.terminalPaperOrders.has(e))return{vtOrderId:e,mode:"paper",result:"terminal"};let t=this.orderOwners.get(e);if(!t||t.mode!=="paper")return null;let r=t.orders.get(e);if(!r)return null;if(!w(r))return{vtOrderId:e,mode:"paper",result:"terminal"};return this.cancelStrategyOrder(t.id,e,t.mode),{vtOrderId:e,mode:"paper",result:"requested"}}async remove(e,t,{clearState:r=!1}={}){let n=this.findRunning(e,t);if(!n)return null;if(await this.stop(n.id,{},n.mode),this.assertOrdersSettled(n.id,n.mode),this.strategies.delete(B(n.id,n.mode)),await this.dispose(n,!r),r)this.deps.stateStore.remove(n.id,n.mode);return n}assertOrdersSettled(e,t){let r=this.requireRunning(e,t);if(r.orders.size>0)throw Error(`\u7B56\u7565 ${e} \u4ECD\u6709 ${r.orders.size} \u7B14\u59D4\u6258\u6216\u6210\u4EA4\u56DE\u62A5\u672A\u5B8C\u6210\uFF0C\u8BF7\u7B49\u5F85\u56DE\u62A5\u6536\u9F50\u540E\u91CD\u8BD5`)}async assertResettable(e,t){let r=this.requireRunning(e,t);if(r.trading||r.enabled)throw Error(`\u7B56\u7565 ${r.id}@${r.mode} \u5C1A\u672A\u505C\u6B62\uFF0C\u8BF7\u5148\u6267\u884C vibek strategy stop ${r.id}@${r.mode}`);if(this.assertOrdersSettled(r.id,r.mode),r.mode!=="live")return r;let n=this.deps.live;if(!n||n.phase!=="running")throw Error("TD \u672A\u8FD0\u884C\uFF0C\u65E0\u6CD5\u91CD\u7F6E Live \u7B56\u7565\u72B6\u6001");let o=(await n.queryOrders(r.instrumentID)).filter(U);if(o.length>0)throw Error(`\u5238\u5546\u5B58\u5728 ${o.length} \u7B14\u6D3B\u52A8\u59D4\u6258\uFF0C\u8BF7\u5148\u4EBA\u5DE5\u5904\u7406`);let s=await n.queryPositions(r.instrumentID);if(s.phase!=="running")throw Error("\u67E5\u8BE2\u5B9E\u76D8\u6301\u4ED3\u5931\u8D25");let a=this.inventoryFromBroker(s.positions,r.instrumentID),d=this.sidePositions(a);if(d.long!==0||d.short!==0)throw Error(`\u5408\u7EA6 ${r.instrumentID} \u4ECD\u6709\u5238\u5546\u6301\u4ED3 \u591A${d.long}/\u7A7A${d.short}\uFF0C\u4E0D\u80FD\u91CD\u7F6E\u672C\u5730\u72B6\u6001`);return r}async close({persist:e=!0}={}){if(e)await this.stopAll({cancelOrders:!0});let t=[...this.strategies.values()],r=[...this.pending.values()].map((n)=>n.running);this.strategies.clear(),this.pending.clear();for(let n of[...t,...r])await this.dispose(n,e);for(let n of this.cleanups.splice(0))n();this.deps.orderRisk.close({persist:e}),this.liveOrders.clear(),this.terminalPaperOrders.clear(),this.liveTradeIds.clear(),this.tradeClaimContext=null,this.timerCursorMs=null,this.eventTimeMs=null}has(e,t){return this.strategies.has(B(e,t))}resolve(e,t){return this.requireRunning(e,t)}setEnabled(e,t,r){let n=this.requireRunning(e,r);if(n.enabled===t)return n;return n.enabled=t,this.publishHook(t?"strategy.enabled":"strategy.disabled",this.strategyIdentity(n)),n}instruments(){return[...new Set([...this.strategies.values()].map((e)=>e.instrumentID))]}instrumentDeclarations(){return[...this.strategies.values()].map((e)=>this.declaration(e))}instrumentDeclarationsWithPending(e,t){let r=this.requirePending(e,t).running;return[...this.strategies.values()].map((n)=>this.declaration(n)).concat(this.declaration(r))}instrumentsWithPending(e,t){let r=this.requirePending(e,t).running;return[...new Set([...this.strategies.values()].map((n)=>n.instrumentID).concat(r.instrumentID))]}handleBar(e){for(let t of this.strategies.values())if(t.instrumentID===e.code&&t.inited)this.enqueue(()=>this.callStrategy(t,"onBar",()=>t.instance.onBar(e)));this.queuePending({kind:"bar",value:e},e.code)}handleTick(e){this.dispatchMarketEvent(e,[])}handleMarketEvent(e,t){this.dispatchMarketEvent(e,t)}handlePaperExternalTrade(e,t){if(e.type!=="order"||e.vtOrderId!==t.vtOrderId||e.vtSymbol!==t.vtSymbol||e.direction!==t.direction||e.offset!==t.offset||e.status!=="allTraded"||e.traded!==e.volume||t.volume!==e.volume)throw Error("\u5916\u90E8 Paper \u8BA2\u5355\u4E0E\u6210\u4EA4\u4E0D\u4E00\u81F4");let r={order:{...e},origin:"external",tradingDay:t.tradingDay,tradedVolume:t.volume,claimable:e.offset==="open",ownerId:null};this.enqueue(()=>{this.dispatchTrades("paper",{...t},{...e},"external",{observed:r,firstTrade:!0,allowPaper:!0})})}handleTimer(e){if(!Number.isFinite(e)||e<0)throw Error("Timer \u65F6\u95F4\u5FC5\u987B\u662F\u975E\u8D1F\u6709\u9650\u6570");let t=Math.floor(e/1000)*1000;if(this.timerCursorMs===null){this.timerCursorMs=t;return}if(t<this.timerCursorMs)throw Error("Timer \u65F6\u95F4\u4E0D\u80FD\u56DE\u9000");for(let r=this.timerCursorMs+1000;r<=t;r+=1000)this.dispatchTimer(r);this.timerCursorMs=t}setTimerClock(e){if(!Number.isFinite(e)||e<0)throw Error("Timer \u65F6\u95F4\u5FC5\u987B\u662F\u975E\u8D1F\u6709\u9650\u6570");this.timerCursorMs=Math.floor(e/1000)*1000}dispatchMarketEvent(e,t){let r=e.tradingDay??e.actionDay??"";this.deps.orderRisk.setTradingDay("paper",r);let n=new Map;for(let i of t){let o=n.get(i.code)??[];o.push(i),n.set(i.code,o)}for(let i of this.strategies.values()){if(!i.inited)continue;let o=i.instrumentID===e.code,s=n.get(i.instrumentID)??[];if(!o&&s.length===0)continue;this.enqueue(()=>{if(o&&i.mode==="paper")this.rollPaperDay(i,r),this.matchPaperOrders(i,e);for(let a of s){if(i.fault)break;this.callStrategy(i,"onBar",()=>i.instance.onBar(a))}if(o)this.callStrategy(i,"onTick",()=>i.instance.onTick(e))})}for(let i of t)this.queuePending({kind:"bar",value:i},i.code);this.queuePending({kind:"tick",value:e},e.code)}snapshot(e={}){return[...this.strategies.values()].map((t)=>{let r=[...t.orders.values()].filter(w).map((n)=>({...n,type:"order"}));return{id:t.id,name:t.name,sourcePath:t.sourcePath,hash:t.hash,loadedAt:t.loadedAt,mode:t.mode,vtSymbol:t.vtSymbol,instrumentID:t.instrumentID,setting:t.setting,enabled:t.enabled,inited:t.inited,trading:t.trading,pos:J(t.inventory),inventory:{...t.inventory},activeOrders:r.length,activeOrderDetails:r,warning:!t.trading&&r.length>0?this.retainedOrdersMessage(r.length):null,fault:t.fault,...e.variables?this.variablesSnapshot(t):{}}})}variablesSnapshot(e){try{return{variables:this.serializeVariables(e),variablesError:null}}catch(t){return{variables:null,variablesError:t instanceof Error?t.message:String(t)}}}riskSnapshot(){return this.deps.orderRisk.snapshot()}sendOrder(e,t,r,n,i){let o=this.requireInstance(e);if(!o.trading||o.fault)return[];if(!Number.isFinite(n)||n<=0||!Number.isInteger(i)||i<=0){let l=`price/volume \u975E\u6CD5 (${n}, ${i})`;return this.writeLog(e,`\u62D2\u7EDD\u59D4\u6258\uFF1A${l}`),this.emitOrderError(o,"invalidRequest",t,r,n,i,l),[]}if(i>this.deps.executionLimits.maxOrderVolume){let l=`\u6570\u91CF ${i} \u8D85\u8FC7\u5355\u7B14\u4E0A\u9650 ${this.deps.executionLimits.maxOrderVolume}`;return this.writeLog(e,`\u62D2\u7EDD\u59D4\u6258\uFF1A${l}`),this.emitRiskRejection(o,"max_order_volume",t,r,n,i,l),this.emitOrderError(o,"maxOrderVolume",t,r,n,i,l),[]}let s=this.resolveOrderParts(o,t,r,n,i);if(s.length===0)return[];if(r==="open"){if([...o.orders.values()].filter((f)=>f.offset==="open"&&w(f)).length>=this.deps.executionLimits.maxActiveOpenOrders){let f=`\u6D3B\u52A8\u5F00\u4ED3\u59D4\u6258\u8FBE\u5230\u4E0A\u9650 ${this.deps.executionLimits.maxActiveOpenOrders}`;return this.writeLog(e,`\u62D2\u7EDD\u5F00\u4ED3\uFF1A${f}`),this.emitRiskRejection(o,"max_active_open_orders",t,r,n,i,f),this.emitOrderError(o,"maxActiveOpenOrders",t,r,n,i,f),[]}}let a=this.roundPrice(o,n);if(!Number.isFinite(a)||a<=0){let l=`price/volume \u975E\u6CD5 (\u820D\u5165\u540E\u4EF7\u683C ${a}\uFF0C\u539F\u59CB ${n})`;return this.writeLog(e,`\u62D2\u7EDD\u59D4\u6258\uFF1A${l}`),this.emitOrderError(o,"invalidRequest",t,r,n,i,l),[]}let d=o.mode==="live"?this.liveTradingDay||o.tradingDay||this.deps.orderRisk.tradingDay("live"):o.tradingDay||this.deps.orderRisk.tradingDay("paper"),c=this.deps.orderRisk.checkOpenDetailed({mode:o.mode,tradingDay:d,vtSymbol:o.vtSymbol,direction:t,offset:r,price:a,volume:i});if(c)return this.writeLog(e,`\u62D2\u7EDD\u5F00\u4ED3\uFF1A${c.message}`),this.emitRiskRejection(o,c.code,t,r,a,i,c.message),this.emitOrderError(o,"riskRejected",t,r,n,i,c.message),[];let u=[];for(let l of s){let f=this.submitPhysical(o,t,l.offset,a,l.volume);if(f)u.push(f)}return u}cancelOrder(e,t){let r=this.requireInstance(e),n=r.orders.get(t);if(!n||!w(n))return;if(r.mode==="paper")this.deps.paperExecution?.onCancelRequested?.(Object.freeze({...n})),n.status="cancelled",n.statusMessage="paper cancelled",this.queueOrder(r,n);else try{this.deps.live?.cancelOrder(t)}catch(i){this.handleLiveError({vtOrderId:t,orderRef:t.slice(4),instrumentID:r.instrumentID,operation:"cancel",message:i instanceof Error?i.message:String(i)})}}cancelAll(e){this.cancelAllDirect(this.requireInstance(e))}liquidatePaper(e,t,r){let n=this.requireRunning(e,"paper");if(n.mode!=="paper")throw Error(`\u7B56\u7565 ${e} \u4E0D\u662F Paper \u6A21\u5F0F`);this.deps.orderRisk.setTradingDay("paper",t.tradingDay??t.actionDay??n.tradingDay);let i=n.trading;n.trading=!1,this.syncFrameworkState(n),this.cancelAllDirect(n);let o=[{direction:"short",offset:"closeYesterday",volume:n.inventory.longYesterday},{direction:"short",offset:"closeToday",volume:n.inventory.longToday},{direction:"long",offset:"closeYesterday",volume:n.inventory.shortYesterday},{direction:"long",offset:"closeToday",volume:n.inventory.shortToday}],s=[];for(let a of o){if(a.volume<=0)continue;let d=a.direction==="long"?t.askPrice1??t.lastPrice:t.bidPrice1??t.lastPrice,c={vtOrderId:`PAPERLIQ.${n.id}.${n.paperSessionId}.${++n.paperOrderCounter}`,vtSymbol:n.vtSymbol,direction:a.direction,offset:a.offset,price:d,volume:a.volume,traded:a.volume,status:"allTraded",statusMessage:r,datetime:t.datetime??this.deps.now()},u=this.paperFill(c,t,a.volume,"marginLiquidation");c.price=u.price,n.orders.set(c.vtOrderId,c),this.orderOwners.set(c.vtOrderId,n),this.emitOrder(n,c);let l={vtTradeId:`PAPERLIQTRADE.${n.id}.${n.paperSessionId}.${++n.paperTradeCounter}`,vtOrderId:c.vtOrderId,vtSymbol:n.vtSymbol,direction:a.direction,offset:a.offset,price:u.price,volume:a.volume,datetime:t.datetime??this.deps.now(),tradingDay:t.tradingDay??t.actionDay??""};this.deps.paperExecution?.onTrade?.(l,u,"marginLiquidation"),this.applyTrade(n,l),s.push(l)}if(i)try{this.invokeStrategySync(n,"onStop",()=>n.instance.onStop())}catch(a){this.failStrategy(n,"onStop",a)}return this.cancelAllDirect(n),this.syncFrameworkState(n),this.scheduleState(n,!0),this.writeLog(n.instance,`\u5F3A\u5236\u5E73\u4ED3\u5E76\u505C\u6B62: ${r}`),s}loadBars(e,t,r){let n=this.requireInitializing(e,"loadBars");if(!t||typeof t!=="object"||Array.isArray(t))throw Error("loadBars request \u5FC5\u987B\u662F\u5BF9\u8C61");if(!Z(t.timeframe))throw Error(`loadBars timeframe \u4E0D\u53D7\u652F\u6301: ${String(t.timeframe)}`);if(!Number.isInteger(t.count)||t.count<=0)throw Error("loadBars count \u5FC5\u987B\u4E3A\u6B63\u6574\u6570");if(n.warmupRequests.length>0)throw Error("\u6BCF\u4E2A\u7B56\u7565\u53EA\u80FD\u767B\u8BB0\u4E00\u4E2A loadBars \u8BF7\u6C42");n.warmupRequests.push({timeframe:t.timeframe,count:t.count,callback:r})}registerEvent(e,t,r){let n=this.requireInitializing(e,"registerEvent");if(t!=="timer"&&t!=="trades")throw Error(`\u4E0D\u652F\u6301\u7684\u7B56\u7565\u4E8B\u4EF6: ${String(t)}`);if(typeof r!=="function")throw Error("\u7B56\u7565\u4E8B\u4EF6 handler \u5FC5\u987B\u662F\u51FD\u6570");let i=n.eventHandlers[t];i.add(r);let o=!0;return()=>{if(!o)return;o=!1,i.delete(r)}}claimOrder(e,t){let r=this.requireInstance(e),n=this.tradeClaimContext;if(r.mode!=="live"&&!(r.mode==="paper"&&n?.allowPaper))return{ok:!1,reason:"\u53EA\u6709 Live \u7B56\u7565\u6216\u4EA4\u4E92\u56DE\u6D4B\u53EF\u4EE5\u8BA4\u9886\u5916\u90E8\u8BA2\u5355"};if(!r.inited||!r.trading||r.fault)return{ok:!1,reason:"\u7B56\u7565\u672A\u5904\u4E8E\u4EA4\u6613\u72B6\u6001"};if(!n||n.running!==r||n.trade.vtOrderId!==t)return{ok:!1,reason:"claimOrder \u53EA\u80FD\u5728\u5F53\u524D\u8BA2\u5355\u7684 trades \u4E8B\u4EF6\u4E2D\u8C03\u7528"};let{observed:i,trade:o}=n;if(i.ownerId!==null||this.orderOwners.has(t))return{ok:!1,reason:"\u8BA2\u5355\u5DF2\u7ECF\u5F52\u5C5E\u7B56\u7565"};let s=i.order;if(!s)return{ok:!1,reason:"\u6210\u4EA4\u5173\u8054\u7684\u8BA2\u5355\u4FE1\u606F\u5C1A\u672A\u5B8C\u6574"};if(!n.firstTrade)return{ok:!1,reason:"\u53EA\u80FD\u8BA4\u9886\u5916\u90E8\u8BA2\u5355\u7684\u9996\u7B14\u65B0\u6210\u4EA4"};if(i.origin!=="external"||s.offset!=="open"||o.offset!=="open")return{ok:!1,reason:"\u53EA\u80FD\u8BA4\u9886\u5916\u90E8\u5F00\u4ED3\u8BA2\u5355"};if(s.vtSymbol!==r.vtSymbol||o.vtSymbol!==r.vtSymbol)return{ok:!1,reason:"\u8BA2\u5355\u5408\u7EA6\u4E0E\u7B56\u7565\u4E0D\u4E00\u81F4"};if(s.direction!==o.direction||o.volume>s.volume||s.status==="rejected")return{ok:!1,reason:"\u8BA2\u5355\u4E0E\u6210\u4EA4\u4FE1\u606F\u4E0D\u4E00\u81F4"};if(r.orders.size>0||Object.values(r.inventory).some((d)=>d!==0))return{ok:!1,reason:"\u7B56\u7565\u5DF2\u6709\u6301\u4ED3\u6216\u672A\u5B8C\u6210\u7684\u8BA2\u5355"};if(r.mode==="live")this.rollLiveDay(r,o.tradingDay);else this.rollPaperDay(r,o.tradingDay);let a={...s,traded:Math.max(s.traded,o.volume)};return i.ownerId=r.id,r.orders.set(t,a),this.orderOwners.set(t,r),this.queueOrder(r,a),this.applyTrade(r,{...o},!0,!1),this.writeLog(r.instance,`claimed external order ${t} on trade ${o.vtTradeId}`),{ok:!0}}writeLog(e,t){this.deps.log(`[cta:${e.strategyName}:${e.mode}] ${t}`)}emitHook(e,t,r){let n=this.requireInstance(e),i=this.eventTimeMs??this.deps.now(),o=er("strategy.event",{...this.strategyIdentity(n),name:t,data:r});fe({type:"hook",id:"00000000-0000-4000-8000-000000000000",event:"strategy.event",account:"a".repeat(64),occurredAt:i,payload:o});try{this.deps.emitHook?.("strategy.event",o,i)}catch(s){this.writeLog(e,`Hook \u6295\u9012\u5931\u8D25 name=${o.name}: ${s instanceof Error?s.message:String(s)}`)}}getCurrentTime(e){return this.requireInstance(e),this.eventTimeMs??this.deps.now()}getContract(e){let t=this.requireInstance(e),r=t.vtSymbol.split(".")[1],n=this.deps.live?.getInstrument(t.instrumentID);if(!n)throw Error(`\u5408\u7EA6 ${t.vtSymbol} Contract \u5C1A\u672A\u51C6\u5907`);if(n.instrumentID.toUpperCase()!==t.instrumentID)throw Error(`\u5408\u7EA6\u4EE3\u7801\u4E0D\u4E00\u81F4\uFF1A\u7B56\u7565 ${t.instrumentID}\uFF0C\u5143\u6570\u636E ${n.instrumentID}`);if(n.exchangeID.toUpperCase()!==r)throw Error(`\u5408\u7EA6\u4EA4\u6613\u6240\u4E0D\u4E00\u81F4\uFF1A\u7B56\u7565 ${r}\uFF0C\u5143\u6570\u636E ${n.exchangeID}`);if(!Number.isFinite(n.priceTick)||n.priceTick<=0||!Number.isSafeInteger(n.volumeMultiple)||n.volumeMultiple<=0||!Number.isSafeInteger(n.minLimitOrderVolume)||n.minLimitOrderVolume<=0||!Number.isSafeInteger(n.maxLimitOrderVolume)||n.maxLimitOrderVolume<n.minLimitOrderVolume||!Number.isFinite(n.longMarginRatio)||n.longMarginRatio<0||!Number.isFinite(n.shortMarginRatio)||n.shortMarginRatio<0)throw Error(`\u5408\u7EA6\u89C4\u683C\u975E\u6CD5: ${t.vtSymbol}`);return Object.freeze({vtSymbol:`${n.instrumentID}.${n.exchangeID}`.toUpperCase(),instrumentID:n.instrumentID.toUpperCase(),exchangeID:n.exchangeID.toUpperCase(),productID:n.productID.toUpperCase(),name:n.instrumentName,priceTick:n.priceTick,size:n.volumeMultiple,minOrderVolume:n.minLimitOrderVolume,maxOrderVolume:n.maxLimitOrderVolume,longMarginRatio:n.longMarginRatio,shortMarginRatio:n.shortMarginRatio,isTrading:n.isTrading===1})}getCommission(e){let t=this.requireInstance(e),r=t.vtSymbol.split(".")[1],n=this.deps.live?.getCommission(t.instrumentID,r);if(!n)throw Error(`\u5408\u7EA6 ${t.vtSymbol} Commission \u5C1A\u672A\u51C6\u5907`);let i=this.getContract(e);if(n.instrumentID.toUpperCase()!==t.instrumentID&&n.instrumentID.toUpperCase()!==i.productID||n.exchangeID&&n.exchangeID.toUpperCase()!==r)throw Error(`\u624B\u7EED\u8D39\u5408\u7EA6\u4E0D\u4E00\u81F4: ${t.vtSymbol}`);if([n.openRatioByMoney,n.openRatioByVolume,n.closeRatioByMoney,n.closeRatioByVolume,n.closeTodayRatioByMoney,n.closeTodayRatioByVolume].some((o)=>!Number.isFinite(o)||o<0))throw Error(`\u624B\u7EED\u8D39\u7387\u975E\u6CD5: ${t.vtSymbol}`);return Object.freeze({open:Object.freeze({ratioByMoney:n.openRatioByMoney,perLot:n.openRatioByVolume}),close:Object.freeze({ratioByMoney:n.closeRatioByMoney,perLot:n.closeRatioByVolume}),closeToday:Object.freeze({ratioByMoney:n.closeTodayRatioByMoney,perLot:n.closeTodayRatioByVolume})})}getPriceTick(e){return this.getContract(e).priceTick}getSize(e){return this.getContract(e).size}async warmupOne(e,t,r=!0,n=!0){e.warmupRequests=[],this.clearEventHandlers(e),e.inited=!1,e.trading=!1,this.syncFrameworkState(e),await this.prepareContractMetadata(e),this.initializing=e;try{this.invokeStrategySync(e,"onInit",()=>e.instance.onInit())}catch(i){throw Error(`\u7B56\u7565 ${e.id} onInit \u5931\u8D25: ${i instanceof Error?i.message:String(i)}`)}finally{this.initializing=null}if(e.warmupRequests.length>0){let i=this.deps.createWarmupProvider();try{for(let o of e.warmupRequests){let s={timeframe:o.timeframe,count:o.count},a=await i.getBars(e.instrumentID,s),d=Et(a,o.timeframe);if(d<o.count)throw Error(`\u5408\u7EA6 ${e.instrumentID} \u9884\u70ED\u6570\u636E\u4E0D\u5B8C\u6574\uFF1A\u9700\u8981 ${o.count} \u6839\u95ED\u5408 ${o.timeframe} Bar\uFF0C\u5B9E\u9645 ${d} \u6839`);for(let c of a)t?.add(zn(c)),this.invokeStrategySync(e,"loadBars callback",()=>o.callback(c));this.writeLog(e.instance,`loadBars ${d} ${o.timeframe} bars / ${a.length} source 1min bars`)}}catch(o){throw Error(`\u7B56\u7565 ${e.id} \u521D\u59CB\u5316\u6570\u636E\u5931\u8D25: ${o instanceof Error?o.message:String(o)}`)}finally{i.destroy()}}if(n)this.restoreState(e);if(this.serializeVariables(e),e.inited=!0,this.syncFrameworkState(e),r)this.saveState(e)}restoreState(e){let t=this.deps.stateStore.read(e.id,e.mode);if(this.observe(e,{type:"state",operation:"read",value:t?{pos:t.pos,inventory:{...t.inventory},variables:structuredClone(t.variables),tradingDay:t.tradingDay}:null}),!t)return;if(t.strategyId!==e.id||t.mode!==e.mode)throw Error(`\u7B56\u7565 ${e.id} \u72B6\u6001\u8EAB\u4EFD\u4E0D\u5339\u914D`);if(t.vtSymbol!==e.vtSymbol)throw Error(`\u7B56\u7565 ${e.id} \u7684\u72B6\u6001\u5C5E\u4E8E ${t.vtSymbol}\uFF0C\u4E0D\u80FD\u6062\u590D\u5230 ${e.vtSymbol}\uFF1B\u8BF7\u4F7F\u7528\u65B0\u7684\u5B9E\u4F8B ID`);if(e.runtime.restoreVariables(t.variables),e.expectedLivePos=t.pos,e.tradingDay=t.tradingDay,e.mode==="paper")e.inventory=t.inventory,this.syncFrameworkState(e);else e.inventory=t.inventory,e.expectedLiveInventory={...e.inventory},this.syncFrameworkState(e)}async prepareLive(e){let t=this.deps.live;if(!t||t.phase!=="running")throw Error("TD \u672A\u8FD0\u884C\uFF0C\u65E0\u6CD5\u542F\u52A8 Live \u7B56\u7565");let r=e.vtSymbol.split(".")[1],n=await t.prepareInstrument(e.instrumentID,r);if(n.exchangeID.toUpperCase()!==r)throw Error(`\u5408\u7EA6\u4EA4\u6613\u6240\u4E0D\u4E00\u81F4\uFF1A\u7B56\u7565 ${r}\uFF0CCTP ${n.exchangeID}`);let i=await t.queryOrders(e.instrumentID);for(let u of i)this.observeLiveOrder(this.orderDataFromBroker(u),u.identity.tradingDay,!1);let o=i.filter(U);if(o.length>0)throw Error(`\u5238\u5546\u5B58\u5728 ${o.length} \u7B14\u6D3B\u52A8\u59D4\u6258\uFF0C\u8BF7\u5148\u4EBA\u5DE5\u5904\u7406`);let s=await t.queryPositions(e.instrumentID);if(s.phase!=="running")throw Error("\u67E5\u8BE2\u5B9E\u76D8\u6301\u4ED3\u5931\u8D25");let a=this.inventoryFromBroker(s.positions,e.instrumentID),d=J(a),c=this.sidePositions(a);if(e.expectedLivePos===null&&(c.long!==0||c.short!==0))throw Error(`\u5408\u7EA6 ${e.instrumentID} \u5DF2\u6709\u5238\u5546\u6301\u4ED3 \u591A${c.long}/\u7A7A${c.short}\uFF0C\u65B0\u7B56\u7565\u62D2\u7EDD\u8BA4\u9886`);if(e.expectedLivePos!==null&&e.expectedLivePos!==d)throw Error(`\u6301\u4ED3\u4E0D\u4E00\u81F4\uFF1A\u672C\u5730 ${e.expectedLivePos}\uFF0C\u5238\u5546 ${d}`);if(e.expectedLiveInventory){let u=this.sidePositions(e.expectedLiveInventory);if(u.long!==c.long||u.short!==c.short)throw Error(`\u6301\u4ED3\u4E0D\u4E00\u81F4\uFF1A\u672C\u5730\u591A${u.long}/\u7A7A${u.short}\uFF0C\u5238\u5546\u591A${c.long}/\u7A7A${c.short}`)}e.inventory=a,e.expectedLivePos=d,e.expectedLiveInventory={...a},e.tradingDay=this.liveTradingDay||s.positions.find((u)=>u.instrumentID===e.instrumentID)?.tradingDay||e.tradingDay,this.syncFrameworkState(e)}async prepareContractMetadata(e){let t=this.deps.live;if(!t||t.phase!=="running")throw Error(`TD \u672A\u8FD0\u884C\uFF0C\u65E0\u6CD5\u521D\u59CB\u5316\u7B56\u7565 ${e.id}`);let r=e.vtSymbol.split(".")[1],n=await t.prepareInstrument(e.instrumentID,r);if(n.exchangeID.toUpperCase()!==r)throw Error(`\u5408\u7EA6\u4EA4\u6613\u6240\u4E0D\u4E00\u81F4\uFF1A\u7B56\u7565 ${r}\uFF0CCTP ${n.exchangeID}`);if(tt(n.exchangeID,n.productID),!this.getContract(e.instance).isTrading)throw Error(`\u5408\u7EA6\u5F53\u524D\u4E0D\u53EF\u4EA4\u6613: ${e.vtSymbol}`);await t.prepareCommission(e.instrumentID,r)}declaration(e){let t=e.vtSymbol.split(".")[1];return{instrumentID:e.instrumentID,exchangeID:t,vtSymbol:e.vtSymbol}}inventoryFromBroker(e,t){let r=cr();for(let n of e){if(n.instrumentID.toUpperCase()!==t.toUpperCase())continue;if(n.hedgeFlag!=="1"&&n.volume!==0)throw Error(`\u6682\u4E0D\u652F\u6301\u975E\u6295\u673A\u6301\u4ED3: ${n.instrumentID}/${n.hedgeFlag}`);if(!Number.isInteger(n.todayVolume)||n.todayVolume<0||!Number.isInteger(n.yesterdayVolume)||n.yesterdayVolume<0||n.todayVolume+n.yesterdayVolume!==n.volume)throw Error(`\u5238\u5546\u6301\u4ED3\u6570\u636E\u975E\u6CD5: ${n.instrumentID}`);if(n.direction==="long")r.longToday+=n.todayVolume,r.longYesterday+=n.yesterdayVolume;else r.shortToday+=n.todayVolume,r.shortYesterday+=n.yesterdayVolume}return r}sidePositions(e){return{long:e.longToday+e.longYesterday,short:e.shortToday+e.shortYesterday}}resolveOrderParts(e,t,r,n,i){if(r==="open"){let c=t==="long"?e.inventory.longToday+e.inventory.longYesterday:e.inventory.shortToday+e.inventory.shortYesterday,u=[...e.orders.values()].filter((l)=>l.offset==="open"&&l.direction===t).reduce((l,f)=>l+this.unsettledOrderVolume(e,f),0);if(c+u+i>this.deps.executionLimits.maxPosition){let l=`\u9884\u8BA1\u6301\u4ED3 ${c+u+i} \u8D85\u8FC7\u4E0A\u9650 ${this.deps.executionLimits.maxPosition}`;return this.writeLog(e.instance,`\u62D2\u7EDD\u5F00\u4ED3\uFF1A${l}`),this.emitRiskRejection(e,"max_position",t,r,n,i,l),this.emitOrderError(e,"maxPosition",t,r,n,i,l),[]}return[{offset:r,volume:i}]}let o=this.availableClose(e,t);if(r==="closeToday"){if(i>o.today)return this.rejectInsufficientClose(e,t,r,n,i,o);return[{offset:r,volume:i}]}if(r==="closeYesterday"){if(i>o.yesterday)return this.rejectInsufficientClose(e,t,r,n,i,o);return[{offset:r,volume:i}]}if(i>o.today+o.yesterday)return this.rejectInsufficientClose(e,t,r,n,i,o);if(!Pr(e.vtSymbol))return[{offset:"close",volume:i}];let s=[],a=Math.min(i,o.yesterday);if(a>0)s.push({offset:"closeYesterday",volume:a});let d=i-a;if(d>0)s.push({offset:"closeToday",volume:d});return s}rejectInsufficientClose(e,t,r,n,i,o){let s=`\u8BF7\u6C42 ${i}\uFF0C\u53EF\u7528\u4ECA ${o.today}/\u6628 ${o.yesterday}`;return this.writeLog(e.instance,`\u62D2\u7EDD\u5E73\u4ED3\uFF1A${s}`),this.emitOrderError(e,"insufficientPosition",t,r,n,i,s),[]}availableClose(e,t){let r=t==="short"?e.inventory.longToday:e.inventory.shortToday,n=t==="short"?e.inventory.longYesterday:e.inventory.shortYesterday,i=0;for(let s of e.orders.values()){if(s.direction!==t||s.offset==="open")continue;let a=this.unsettledOrderVolume(e,s);if(a<=0)continue;if(s.offset==="closeToday")r-=a;else if(s.offset==="closeYesterday")n-=a;else i+=a}r=Math.max(0,r),n=Math.max(0,n);let o=Math.min(n,i);return n-=o,r=Math.max(0,r-(i-o)),{today:r,yesterday:n}}unsettledOrderVolume(e,t){let r=e.tradeVolumeByOrder.get(t.vtOrderId)??0,n=w(t)?t.volume:t.traded;return Math.max(0,n-r)}submitPhysical(e,t,r,n,i){let o;if(e.mode==="paper")o=`PAPER.${e.id}.${e.paperSessionId}.${++e.paperOrderCounter}`;else{let a=this.deps.live;if(!a)return this.emitOrderError(e,"submitFailed",t,r,n,i,"\u5B9E\u76D8\u901A\u9053\u4E0D\u53EF\u7528"),null;try{o=a.submitOrder({strategyId:e.id,instrumentID:e.instrumentID,direction:t==="long"?"buy":"sell",offsetFlag:r,price:n,volume:i}).vtOrderId}catch(d){let c=d instanceof Error?d.message:String(d);return this.writeLog(e.instance,`\u5B9E\u76D8\u62A5\u5355\u5931\u8D25: ${c}`),this.emitOrderError(e,"submitFailed",t,r,n,i,c),null}}let s={vtOrderId:o,vtSymbol:e.vtSymbol,direction:t,offset:r,price:n,volume:i,traded:0,status:"submitting",statusMessage:"",datetime:this.eventTimeMs??this.deps.now()};if(e.mode==="paper"){let a=this.deps.paperExecution?.acceptOrder?.(s)??{ok:!0};if(!a.ok)return this.writeLog(e.instance,`\u62D2\u7EDD\u59D4\u6258\uFF1A${a.reason}`),this.emitOrderError(e,"paperRejected",t,r,n,i,a.reason),null}if(e.orders.set(o,s),this.orderOwners.set(o,e),e.mode==="live"){let a=this.observeLiveOrder(s,this.liveTradingDay||e.tradingDay,!1);a.origin="strategy",a.ownerId=e.id}if(this.deps.orderRisk.recordOrder(e.mode,s,e.tradingDay),e.mode==="paper")this.deps.paperExecution?.onOrderSubmitted?.(Object.freeze({...s}));if(this.queueOrder(e,s),e.mode==="paper")this.enqueue(()=>{if(s.status!=="submitting")return;s.status="notTraded",this.emitOrder(e,s)});return this.scheduleState(e),o}matchPaperOrders(e,t){for(let r of[...e.orders.values()]){if(r.status!=="notTraded")continue;let n=r.direction==="long",i=n?t.askPrice1:t.bidPrice1,o=n?t.askVolume1:t.bidVolume1;if(i===void 0||!Number.isFinite(i)||i<=0)continue;if(o!==void 0&&(!Number.isFinite(o)||o<=0))continue;if(n?r.price<i:r.price>i)continue;let s=r.volume-r.traded,a=this.paperFill(r,t,s,"strategy");r.traded=r.volume,r.status="allTraded",r.statusMessage="paper matched",this.emitOrder(e,r);let d={vtTradeId:`PAPERTRADE.${e.id}.${e.paperSessionId}.${++e.paperTradeCounter}`,vtOrderId:r.vtOrderId,vtSymbol:e.vtSymbol,direction:r.direction,offset:r.offset,price:a.price,volume:s,datetime:t.datetime??this.deps.now(),tradingDay:t.tradingDay??t.actionDay??""};this.deps.paperExecution?.onTrade?.(d,a,"strategy"),this.applyTrade(e,d)}}handleLiveOrder(e){let t=e.vtOrderId;this.observeLiveOrder(this.orderDataFromBroker(e),e.identity.tradingDay);let r=this.orderOwners.get(t);if(!r||r.mode!=="live")return;let n=r.orders.get(t);if(!n)return;n.price=e.price,n.volume=e.volume,n.traded=e.traded,n.status=e.status,n.statusMessage=e.statusMessage,this.emitOrder(r,n),this.scheduleState(r,!0)}observeLiveOrder(e,t,r=!0){let n=this.liveOrders.get(e.vtOrderId);if(!n)n={order:null,origin:"external",tradingDay:t,tradedVolume:0,claimable:r,ownerId:null},this.liveOrders.set(e.vtOrderId,n);return n.order={...e,type:"order"},n.claimable&&=r,n}orderDataFromBroker(e){return{type:"order",vtOrderId:e.vtOrderId,vtSymbol:e.vtSymbol,direction:e.direction,offset:e.offset,price:e.price,volume:e.volume,traded:e.traded,status:e.status,statusMessage:e.statusMessage,datetime:e.datetime}}handleLiveTrade(e){if(!this.rememberTradeId(this.liveTradeIds,e.vtTradeId))return;let t=e.vtOrderId,r=this.orderOwners.get(t),n=this.liveOrders.get(t);if(!n)n={order:null,origin:"external",tradingDay:e.tradingDay,tradedVolume:0,claimable:!1,ownerId:null},this.liveOrders.set(t,n);let i=n.claimable&&n.tradedVolume===0&&this.deps.live?.phase==="running"&&(!this.liveTradingDay||e.tradingDay===this.liveTradingDay);n.tradedVolume+=e.volume,n.claimable=!1;let o={vtTradeId:e.vtTradeId,vtOrderId:t,vtSymbol:e.vtSymbol,direction:e.direction,offset:e.offset,price:e.price,volume:e.volume,datetime:e.datetime,tradingDay:e.tradingDay};if(r?.mode==="live")this.rollLiveDay(r,e.tradingDay),this.applyTrade(r,o);this.dispatchTrades("live",o,n.order,n.origin,{observed:n,firstTrade:i,allowPaper:!1})}handleLiveError(e){let t=this.orderOwners.get(e.vtOrderId);if(!t)return;let r=t.orders.get(e.vtOrderId);if(!r||!w(r))return;if(e.operation==="cancel"){r.statusMessage=`\u64A4\u5355\u5931\u8D25: ${e.message}`,this.emitOrder(t,r),this.scheduleState(t,!0);return}r.status="rejected",r.statusMessage=e.message,this.emitOrder(t,r),this.scheduleState(t,!0)}handleLiveReconciled(e){this.deps.orderRisk.reconcileLive(e),this.liveTradingDay=e.tradingDay;for(let[o,s]of this.liveOrders)if(s.tradingDay<e.tradingDay&&!this.orderOwners.has(o))this.liveOrders.delete(o);for(let o of e.orders)this.observeLiveOrder(this.orderDataFromBroker(o),o.identity.tradingDay,!1);for(let o of e.trades){if(!this.rememberTradeId(this.liveTradeIds,o.vtTradeId))continue;let s=this.liveOrders.get(o.vtOrderId);if(s)s.tradedVolume+=o.volume}if(e.reason!=="reconnect")return;let t=[...this.strategies.values()].filter((o)=>o.mode==="live"&&o.trading);if(t.length===0)return;let r=new Map;for(let o of t){let s=r.get(o.instrumentID)??[];s.push(o),r.set(o.instrumentID,s)}let n=new Map(e.orders.map((o)=>[o.vtOrderId,o]));for(let o of e.orders){if(!U(o)||!r.has(o.instrumentID))continue;let s=this.orderOwners.get(o.vtOrderId);if(!s||!s.trading)throw Error(`\u6062\u590D\u53D1\u73B0\u672A\u5F52\u5C5E\u7684\u6D3B\u52A8\u59D4\u6258: ${o.vtOrderId}`)}for(let o of t)for(let s of o.orders.values())if(w(s)&&!n.has(s.vtOrderId))throw Error(`\u672C\u5730\u6D3B\u52A8\u59D4\u6258\u672A\u51FA\u73B0\u5728\u5238\u5546\u5FEB\u7167: ${s.vtOrderId}`);let i=new Map;for(let o of t)i.set(o,this.inventoryOnTradingDay(o,e.tradingDay));for(let[o,s]of r){let a=this.inventoryFromBroker(e.positions,o),d=s.reduce((c,u)=>{let l=i.get(u);return{longToday:c.longToday+l.longToday,longYesterday:c.longYesterday+l.longYesterday,shortToday:c.shortToday+l.shortToday,shortYesterday:c.shortYesterday+l.shortYesterday}},cr());if(!this.sameInventory(a,d))throw Error(`\u6062\u590D\u6301\u4ED3\u4E0D\u4E00\u81F4 ${o}: \u672C\u5730\u4ECA/\u6628 \u591A${d.longToday}/${d.longYesterday}`+` \u7A7A${d.shortToday}/${d.shortYesterday}\uFF0C\u5238\u5546\u4ECA/\u6628 \u591A${a.longToday}/${a.longYesterday}`+` \u7A7A${a.shortToday}/${a.shortYesterday}`)}for(let[o,s]of i)o.inventory=s,o.expectedLivePos=J(s),o.expectedLiveInventory={...s},o.tradingDay=e.tradingDay,this.syncFrameworkState(o),this.scheduleState(o,!0)}inventoryOnTradingDay(e,t){if(e.tradingDay&&e.tradingDay>t)throw Error(`\u6062\u590D TradingDay \u56DE\u9000: ${e.tradingDay}/${t}`);let r={...e.inventory};if(e.tradingDay&&e.tradingDay!==t)r.longYesterday+=r.longToday,r.longToday=0,r.shortYesterday+=r.shortToday,r.shortToday=0;return r}sameInventory(e,t){return e.longToday===t.longToday&&e.longYesterday===t.longYesterday&&e.shortToday===t.shortToday&&e.shortYesterday===t.shortYesterday}handleLiveFault(e){for(let t of this.strategies.values())if(t.mode==="live"&&t.trading)this.failStrategy(t,"TD",Error(e))}applyTrade(e,t,r=!1,n=!0){let i=t.vtTradeId;if(!this.rememberTradeId(e.seenTrades,i))return;if(this.deps.recordTrade)try{this.deps.recordTrade({strategyId:e.id,name:e.name,mode:e.mode,vtSymbol:e.vtSymbol,trade:{...t},inventory:{...e.inventory},multiplier:this.deps.live?.getInstrument(e.instrumentID)?.volumeMultiple??null})}catch(u){this.writeLog(e.instance,`\u6210\u4EA4\u8BB0\u5F55\u5931\u8D25: ${u instanceof Error?u.message:String(u)}`)}if(this.deps.orderRisk.recordTrade(e.mode,t),this.applyInventory(e.inventory,t.direction,t.offset,t.volume),this.syncFrameworkState(e),e.expectedLivePos=J(e.inventory),e.mode==="live")e.expectedLiveInventory={...e.inventory};let o=(e.tradeVolumeByOrder.get(t.vtOrderId)??0)+t.volume;e.tradeVolumeByOrder.set(t.vtOrderId,o);let s=e.orders.get(t.vtOrderId);if(s)s.traded=Math.max(s.traded,Math.min(s.volume,o));let a={...t},d=s?{...s}:null,c=()=>{if(this.callStrategy(e,"onTrade",()=>(this.observe(e,{type:"trade",value:{...t}}),e.instance.onTrade({...t}))),s)this.pruneTerminalOrder(e,s);if(this.scheduleState(e,!0),e.mode==="paper"&&n)this.dispatchTrades("paper",a,d,"strategy")};if(r)this.scheduleState(e,!0),this.enqueue(c);else c()}applyInventory(e,t,r,n){if(r==="open"){if(t==="long")e.longToday+=n;else e.shortToday+=n;return}let i=t==="short"?"longToday":"shortToday",o=t==="short"?"longYesterday":"shortYesterday";if(r==="closeToday"){e[i]=Math.max(0,e[i]-n);return}if(r==="closeYesterday"){e[o]=Math.max(0,e[o]-n);return}let s=Math.min(e[o],n);e[o]-=s,e[i]=Math.max(0,e[i]-(n-s))}queueOrder(e,t){let r={...t},n=e.tradingDay;this.enqueue(()=>this.emitOrder(e,t,r,n))}emitOrder(e,t,r={...t},n=e.tradingDay){if(this.deps.orderRisk.recordOrder(e.mode,r,n),e.inited)this.callStrategy(e,"onOrder",()=>(this.observe(e,{type:"order",value:{...r,type:"order"}}),e.instance.onOrder({...r,type:"order"})));if(!w(r))this.pruneTerminalOrder(e,t)}emitOrderError(e,t,r,n,i,o,s){let a={type:"error",stage:"submit",code:t,vtSymbol:e.vtSymbol,direction:r,offset:n,price:i,volume:o,message:s,datetime:this.eventTimeMs??this.deps.now()};this.enqueue(()=>this.callStrategy(e,"onOrder",()=>(this.observe(e,{type:"order",value:{...a}}),e.instance.onOrder(a))))}pruneTerminalOrder(e,t){if(e.orders.get(t.vtOrderId)!==t)return;if(w(t))return;if((e.tradeVolumeByOrder.get(t.vtOrderId)??0)<t.traded)return;if(e.mode==="paper")this.deps.paperExecution?.onOrderClosed?.(t),this.terminalPaperOrders.add(t.vtOrderId);e.orders.delete(t.vtOrderId),e.tradeVolumeByOrder.delete(t.vtOrderId),this.orderOwners.delete(t.vtOrderId)}rememberTradeId(e,t){if(e.has(t))return!1;if(e.add(t),e.size>Fo){let r=e.values().next().value;if(r!==void 0)e.delete(r)}return!0}dispatchTimer(e){this.enqueue(()=>{for(let t of this.strategies.values())this.dispatchEvent(t,Object.freeze({type:"timer",now:e}))})}dispatchTrades(e,t,r,n,i){let o=Object.freeze({type:"trades",now:this.eventTimeMs??this.deps.now(),trade:Object.freeze({...t}),order:r?Object.freeze({...r,origin:n}):null});for(let s of this.strategies.values()){if(s.mode!==e)continue;let a=this.tradeClaimContext;this.tradeClaimContext=i?{...i,running:s,trade:o.trade}:null;try{this.dispatchEvent(s,o)}finally{this.tradeClaimContext=a}}}dispatchEvent(e,t){if(!e.inited||e.fault)return;let r=e.eventHandlers[t.type];for(let n of[...r]){if(e.fault)break;if(!r.has(n))continue;this.invokeAtTime(t.now,()=>this.callStrategy(e,`${t.type} event`,()=>n(t)))}}clearEventHandlers(e){for(let t of Object.values(e.eventHandlers))t.clear()}invokeAtTime(e,t){let r=this.eventTimeMs;this.eventTimeMs=e;try{return t()}finally{this.eventTimeMs=r}}rollPaperDay(e,t){if(!t||t===e.tradingDay)return;if(e.tradingDay){for(let r of e.orders.values()){if(!w(r))continue;r.status="cancelled",r.statusMessage="paper trading day changed",this.queueOrder(e,r)}e.inventory.longYesterday+=e.inventory.longToday,e.inventory.longToday=0,e.inventory.shortYesterday+=e.inventory.shortToday,e.inventory.shortToday=0}e.tradingDay=t,this.scheduleState(e,!0)}rollLiveDay(e,t){if(!t||t===e.tradingDay)return;let r=this.inventoryOnTradingDay(e,t);e.inventory=r,e.expectedLiveInventory={...r},e.expectedLivePos=J(r),e.tradingDay=t,this.syncFrameworkState(e)}roundPrice(e,t){let r=this.getPriceTick(e.instance);return Number((Math.round(t/r)*r).toFixed(10))}paperFill(e,t,r,n){let i=(e.direction==="long"?t.askPrice1:t.bidPrice1)??e.price,o=this.deps.paperExecution?.fillOrder?.(e,t,r,n)??{referencePrice:i,price:i};if(!Number.isFinite(o.referencePrice)||o.referencePrice<=0||!Number.isFinite(o.price)||o.price<=0)throw Error(`Paper \u6210\u4EA4\u4EF7\u975E\u6CD5: ${o.referencePrice}/${o.price}`);return o}cancelAllDirect(e){for(let t of e.orders.values()){if(!w(t))continue;if(e.mode==="paper")this.cancelOrder(e.instance,t.vtOrderId);else try{this.deps.live?.cancelOrder(t.vtOrderId)}catch(r){console.error(`[cta] ${e.id} \u64A4\u5355\u5931\u8D25: ${r instanceof Error?r.message:String(r)}`)}}}retainedOrdersMessage(e){return`\u7B56\u7565\u5DF2\u505C\u6B62\uFF0C\u4ECD\u6709 ${e} \u7B14\u59D4\u6258\u672A\u64A4\u9500\uFF0C\u53EF\u80FD\u7EE7\u7EED\u6210\u4EA4\uFF0C\u8BF7\u624B\u52A8\u64A4\u5355\u3002`}scheduleState(e,t=!1){if(t){e.stateTimer?.(),e.stateTimer=null,this.trySaveState(e);return}if(e.stateTimer)return;e.stateTimer=this.deps.schedule(()=>{e.stateTimer=null,this.trySaveState(e)},qo)}saveState(e){let t=this.serializeVariables(e);this.deps.stateStore.write({strategyId:e.id,vtSymbol:e.vtSymbol,mode:e.mode,bundleHash:e.hash,pos:J(e.inventory),variables:t,inventory:e.inventory,tradingDay:e.tradingDay}),this.observe(e,{type:"state",operation:"write",value:{pos:J(e.inventory),inventory:{...e.inventory},variables:t,tradingDay:e.tradingDay}})}serializeVariables(e){return Un(e.runtime.getStateVariables(),`\u7B56\u7565 ${e.id} variables`)}trySaveState(e){try{this.saveState(e)}catch(t){if(console.error(`[cta] ${e.id} \u72B6\u6001\u4FDD\u5B58\u5931\u8D25: ${t instanceof Error?t.message:String(t)}`),e.trading)this.failStrategy(e,"state",t)}}callStrategy(e,t,r){if(e.fault)return;try{this.invokeStrategySync(e,t,r),this.scheduleState(e)}catch(n){this.failStrategy(e,t,n)}}failStrategy(e,t,r){let n=r instanceof Error?r.message:String(r);if(e.fault)return;e.fault=`${t}: ${n}`,this.observe(e,{type:"fault",callback:t,message:n}),e.trading=!1,e.inited=!1,this.clearEventHandlers(e),this.syncFrameworkState(e),this.cancelAllDirect(e),console.error(`[cta] ${e.id} ${t} \u5F02\u5E38\uFF0C\u7B56\u7565\u5DF2\u505C\u6B62: ${n}`),this.publishHook("strategy.faulted",{...this.strategyIdentity(e),callback:t,reason:n})}emitRiskRejection(e,t,r,n,i,o,s){let a=this.deps.orderRisk.tradingDay(e.mode)||e.tradingDay||null;this.publishHook("risk.order_rejected",{source:"strategy",riskCode:t,mode:e.mode,vtSymbol:e.vtSymbol,tradingDay:a,direction:r,offset:n,price:i,volume:o,reason:s,strategyId:e.id})}strategyIdentity(e){return{strategyId:e.id,mode:e.mode,vtSymbol:e.vtSymbol}}publishHook(e,t){try{this.deps.emitHook?.(e,t)}catch(r){console.error(`[hooks] \u4E8B\u4EF6\u6295\u9012\u5931\u8D25 event=${e}: ${r instanceof Error?r.message:String(r)}`)}}enqueue(e){let t=this.eventTimeMs;if(this.eventQueue.push(t===null?e:()=>this.invokeAtTime(t,e)),this.dispatching)return;this.dispatching=!0;try{while(this.eventQueue.length>0)this.eventQueue.shift()()}finally{this.dispatching=!1}}queuePending(e,t){for(let r of this.pending.values()){if(r.running.instrumentID!==t)continue;if(r.events.length>=qn){r.overflowed=!0;continue}r.events.push(e)}}drainPending(e){while(e.events.length>0){let t=e.events.splice(0);for(let r of t){if(r.kind==="bar"){if(e.replayedBars.has(zn(r.value)))continue;this.callStrategy(e.running,"onBar",()=>e.running.instance.onBar(r.value))}else this.callStrategy(e.running,"onTick",()=>e.running.instance.onTick(r.value));if(e.running.fault)return}}}assertLiveInstrumentOwner(e){if(e.mode!=="live")return;let t=[...this.strategies.values()].find((r)=>B(r.id,r.mode)!==B(e.id,e.mode)&&r.mode==="live"&&r.instrumentID===e.instrumentID);if(t)throw Error(`Live \u5408\u7EA6 ${e.instrumentID} \u5DF2\u7531\u7B56\u7565 ${t.id} \u5360\u7528`)}requireInitializing(e,t){let r=this.requireInstance(e);if(this.initializing!==r)throw Error(`${t} \u53EA\u80FD\u5728 onInit() \u4E2D\u8C03\u7528`);return r}requireInstance(e){let t=this.instanceMap.get(e);if(!t)throw Error("\u672A\u77E5\u7B56\u7565\u5B9E\u4F8B");return t}findRunning(e,t){return this.strategies.get(B(e,t))??null}requireRunning(e,t){let r=this.findRunning(e,t);if(!r)throw Error(`\u7B56\u7565\u5B9E\u4F8B\u4E0D\u5B58\u5728: ${e}@${t}`);return r}findPending(e,t){return this.pending.get(B(e,t))??null}requirePending(e,t){let r=this.findPending(e,t);if(!r)throw Error(`\u7B56\u7565 ${e}@${t} \u6CA1\u6709\u5F85\u63D0\u4EA4\u53D8\u66F4`);return r}async dispose(e,t=!0){if(e.stateTimer?.(),e.stateTimer=null,t&&e.inited)this.trySaveState(e);this.instanceMap.delete(e.instance),this.clearEventHandlers(e);for(let r of e.orders.keys())this.orderOwners.delete(r)}invokeSync(e,t){let r=t();if(r&&typeof r.then==="function")throw Promise.resolve(r).catch(()=>{return}),Error(`${e} \u4E0D\u5141\u8BB8\u8FD4\u56DE Promise`)}invokeStrategySync(e,t,r){this.syncFrameworkState(e),this.observe(e,{type:"callback",callback:t,stage:"begin"});try{this.invokeSync(t,r)}finally{this.syncFrameworkState(e),this.observe(e,{type:"callback",callback:t,stage:"end"})}}observe(e,t){this.deps.observe({...t,strategyId:e.id,now:this.eventTimeMs??this.deps.now()})}syncFrameworkState(e){e.runtime.setRuntimeState({inited:e.inited,trading:e.trading,pos:J(e.inventory)})}}var jn=20,Ko=1000,Q="vibek-test",Yo={maxOrderVolume:1000,maxPosition:1e4,maxActiveOpenOrders:1000},Gn={dailyLimits:{totalOrderLimit:1e5,totalCancelLimit:1e5,totalTradeLimit:1e5,instrumentOrderLimit:1e5,instrumentCancelLimit:1e5,instrumentTradeLimit:1e5},duplicateOrderLimit:1e5};class ie{definition;fixture;events=[];orderEvents=[];tradeEvents=[];logs=[];hooks=[];submissions=[];cancelRequests=[];broker;callbackCounts={onInit:0,onStart:0,onStop:0,onTick:0,onBar:0};dataDir;stateStore;clock;traceRecorder;faults=[];aggregator=new qe({timeframes:["1min"]});barHistory=[];warmupSupplied=!1;paperSessionSequence=0;mode;setting;initialState;brokerState;executionLimits;riskSettings;paperExecution;runtime;running;live;gatewayControl;eventCounter=0;nextEventTimeMs;initialized=!1;initialStateWritten=!1;disposed=!1;firstSdkOrderViolation;constructor(e,t,r={}){this.definition=e;this.fixture=t;if(this.mode=r.mode??"live",r.execution!==void 0&&r.execution!=="scripted"&&r.execution!=="ctp")throw Error("Unknown test execution model");if(r.execution==="ctp"&&this.mode!=="live")throw Error("CTP execution requires live mode");if(r.execution==="ctp"&&r.brokerState)throw Error("CTP execution uses gatewayState");if(r.brokerState&&this.mode!=="live")throw Error("Broker state requires live mode");if(r.paperExecution&&this.mode!=="paper")throw Error("Paper execution hooks require paper mode");if(r.execution!=="ctp"&&(r.gatewayState||r.gatewaySession))throw Error("Gateway settings require CTP execution");this.setting=r.setting===void 0?ce(t.setting,"setting"):ce(r.setting,"setting"),this.initialState=r.initialState,this.brokerState=r.brokerState===void 0?void 0:structuredClone(r.brokerState),this.executionLimits=Wo(r.executionLimits),this.riskSettings=Jo(r.riskLimits),this.paperExecution=r.paperExecution,this.clock=new Ht(Ke(t)),this.traceRecorder=new Lt(()=>this.clock.now);let{paperExecution:n,...i}=r;if(this.traceRecorder.record("context",{fixture:{vtSymbol:t.vtSymbol,instrument:{priceTick:t.priceTick,size:t.size,name:t.name,minOrderVolume:t.minOrderVolume,maxOrderVolume:t.maxOrderVolume,longMarginRatio:t.longMarginRatio,shortMarginRatio:t.shortMarginRatio,isTrading:t.isTrading},...t.commission?{commission:t.commission}:{},setting:t.setting,seedPrice:t.seedPrice,tradingDay:t.tradingDay},options:i,...n?{customPaperExecution:!0}:{}}),r.execution==="ctp")this.live=new xe(t,this.clock,this.traceRecorder,r,{submissions:this.submissions,cancelRequests:this.cancelRequests});this.nextEventTimeMs=this.clock.now;let o={holdReports:()=>this.requireLive().holdReports(),resumeReports:()=>this.requireLive().resumeReports(),pendingReports:()=>this.requireLive().pendingReports(),deliverReport:(s)=>this.requireLive().deliverReport(s),repeatReport:(s)=>this.requireLive().repeatReport(s),snapshot:()=>this.requireLive().snapshot(),failNextSubmit:(s)=>this.requireLive().failNextSubmit(s),rejectSubmit:(s,a)=>this.requireLive().rejectSubmit(s,a),acknowledge:(s)=>this.requireLive().acknowledge(s),partialFill:(s,a,d)=>this.requireLive().partialFill(s,a,d),fill:(s,a)=>this.requireLive().fill(s,a),reject:(s,a)=>this.requireLive().reject(s,a),cancel:(s,a)=>this.requireLive().cancel(s,a),failNextCancel:(s)=>this.requireLive().failNextCancel(s),rejectCancel:(s,a)=>this.requireLive().rejectCancel(s,a),emitExternalOrder:(s)=>this.requireLive().emitExternalOrder(s),emitOrder:(s)=>this.requireLive().emitOrder(s),emitTrade:(s)=>this.requireLive().emitTrade(s),disconnect:()=>this.requireLive().disconnect(),reconcile:(s)=>this.requireLive().reconcile(s),fault:(s)=>this.requireLive().fault(s)};this.broker=this.observeCommands(o,"broker",["snapshot","pendingReports"]),this.dataDir=zo(Go(jo(),"vibek-strategy-test-runtime-")),this.stateStore=new $e(this.dataDir)}initialize(e={}){return this.traceRecorder.input({type:"initialize",...e},()=>this.initializeRuntime(e))}async initializeRuntime(e){if(this.disposed)throw Error("\u6D4B\u8BD5 Runtime \u5DF2\u91CA\u653E");if(this.initialized)throw Error("\u6D4B\u8BD5 Runtime \u5DF2\u521D\u59CB\u5316");if(e.warmupBars){let t=e.warmupBars.map((r,n)=>H(this.fixture,r,n));Ie(t,this.clock.now,this.fixture.instrumentID),this.barHistory.push(...t),this.warmupSupplied=!0}this.seedInitialState(),await this.createRuntime(),await this.runtime.warmup(),this.runtime.setTimerClock(this.clock.now),this.initialized=!0}async start(){this.requireInitialized(),await this.traceRecorder.input({type:"start"},()=>this.runtime.start(Q,this.mode))}async stop(){this.requireInitialized(),await this.traceRecorder.input({type:"stop"},()=>this.runtime.stop(Q,{},this.mode))}cancelOrder(e){this.requireInitialized(),this.traceRecorder.input({type:"cancelOrder",vtOrderId:e},()=>{this.runtime.cancelStrategyOrder(Q,e,this.mode)})}async restart(){this.requireInitialized(),await this.traceRecorder.input({type:"restart"},async()=>{await this.runtime.stop(Q,{},this.mode),await this.rebuildRuntime()})}async crashAndRestart(){this.requireInitialized(),await this.traceRecorder.input({type:"crashAndRestart"},()=>this.rebuildRuntime(!1))}setTime(e){if(this.disposed)throw Error("\u6D4B\u8BD5 Runtime \u5DF2\u91CA\u653E");let t=T(e,"\u6D4B\u8BD5\u65F6\u95F4");this.traceRecorder.input({type:"setTime",datetime:t},()=>{if(this.initialized)this.advanceClock(t);else this.clock.now=t}),this.nextEventTimeMs=t}advanceTime(e){if(this.disposed)throw Error("\u6D4B\u8BD5 Runtime \u5DF2\u91CA\u653E");let t=T(e,"\u63A8\u8FDB\u65F6\u95F4"),r=this.clock.now+t;if(!Number.isSafeInteger(r))throw Error("\u63A8\u8FDB\u540E\u7684\u6D4B\u8BD5\u65F6\u95F4\u8D85\u8FC7\u5B89\u5168\u6574\u6570\u8303\u56F4");this.traceRecorder.input({type:"advanceTime",milliseconds:e},()=>this.advanceClock(r)),this.nextEventTimeMs=r}pushBar(e,t={}){this.requireInitialized();let r=t.datetime??(e.barTime===void 0?Math.max(this.nextEventTimeMs,((this.barHistory.at(-1)?.barTime??-120)+120)*1000):(e.barTime+60)*1000),n=H(this.fixture,e,this.eventCounter,Math.floor(r/60000)*60-60);this.validateClosedBars([n],r),this.traceRecorder.input({type:"bar",value:n,datetime:r},()=>{this.recordEventTime(r),this.eventCounter++,this.aggregator.completeThrough(r),this.dispatchBars(this.mergeClosedBars(this.takeCompletedBars(),[n]))})}pushTick(e){this.pushMarketEvent(e,[])}pushMarketEvent(e,t){this.requireInitialized();let r=De(this.fixture,e,this.eventCounter,this.nextEventTimeMs);if(r.code!==this.fixture.instrumentID)throw Error("\u6D4B\u8BD5 Tick \u5408\u7EA6\u4E0E fixture \u4E0D\u5339\u914D");let n=t.map((o,s)=>H(this.fixture,o,s)),i=r.receivedAt??r.datetime;if(r.datetime>i||i-r.datetime>dt)throw Error("\u6D4B\u8BD5 Tick \u4EA4\u4ED8\u65F6\u95F4\u5FC5\u987B\u4F4D\u4E8E\u884C\u60C5\u65F6\u95F4\u4E4B\u540E\u7684 30 \u79D2\u4EE5\u5185");this.validateClosedBars(n,i),this.traceRecorder.input({type:"market",tick:r,closedBars:n},()=>{this.recordEventTime(i),this.eventCounter++,this.aggregator.pushTick(r);let o=this.mergeClosedBars(this.takeCompletedBars(),n);this.rememberBars(o);for(let s of o)this.traceRecorder.record("bar",s);this.runtime.handleMarketEvent(r,o)})}activeOrders(){if(this.requireInitialized(),!this.running)throw Error("\u6D4B\u8BD5 Runtime \u7684\u7B56\u7565\u5B9E\u4F8B\u4E0D\u5B58\u5728");return[...this.running.orders.values()].filter(w).map((e)=>({...e}))}snapshot(){this.requireInitialized();let e=this.runtime.snapshot().find((t)=>t.id===Q);if(!e||!this.running)throw Error("\u6D4B\u8BD5 Runtime \u7684\u7B56\u7565\u5B9E\u4F8B\u4E0D\u5B58\u5728");return{inited:e.inited,trading:e.trading,pos:e.pos,activeOrders:e.activeOrders,fault:e.fault,variables:ce(this.running.runtime.getStateVariables(),"variables"),inventory:{...this.running.inventory},now:this.clock.now}}get trace(){return this.traceRecorder.events}get gateway(){if(this.disposed)throw Error("\u6D4B\u8BD5 Runtime \u5DF2\u91CA\u653E");if(!(this.live instanceof xe))throw Error('Gateway controls require execution="ctp"');return this.gatewayControl??=this.observeCommands(this.live.gateway,"gateway",["pendingRequests"])}expectFault(e){let t=this.faults.find((r)=>!r.verified&&r.message===e);if(!t)throw Error(`\u672A\u53D1\u751F\u5F85\u9A8C\u8BC1\u7684 fault: ${e}`);t.verified=!0,this.traceRecorder.record("input",{type:"expectFault",message:e})}settleActiveOrders(){if(this.mode==="live"){if(!(this.live instanceof Oe))throw Error("CTP orders require explicit gateway reports");this.live.settleActiveOrders()}else for(let e of this.activeOrders())this.cancelOrder(e.vtOrderId)}assertHealthy(e){let t=this.snapshot();if(t.fault)throw Error(`${e}\u540E\u7B56\u7565 fault: ${t.fault}`);if(this.firstSdkOrderViolation)throw Error(`${e}\u89E6\u53D1 SDK \u975E\u6CD5\u4E0B\u5355: ${this.firstSdkOrderViolation}`)}async dispose(){if(!this.disposed){this.disposed=!0;try{await this.runtime?.close()}finally{if(this.live instanceof xe)this.live.close();this.traceRecorder.close(),Uo(this.dataDir,{recursive:!0,force:!0})}}let e=this.faults.filter((t)=>!t.verified);if(e.length)throw Error(`\u672A\u9A8C\u8BC1\u7684\u7B56\u7565 fault: ${e.map((t)=>t.message).join("; ")}`)}async createRuntime(){let e=this.stateStore.read(Q,this.mode),t=e?.inventory??de(),r={...this.fixture,tradingDay:e?.tradingDay||this.fixture.tradingDay};this.live??=new Oe(r,t,{order:0,trade:0},()=>this.clock.now,{submissions:this.submissions,cancelRequests:this.cancelRequests,record:(i,o)=>this.traceRecorder.record(i,o)},this.brokerState);let n=new He({settings:this.riskSettings,stateStore:new dr(this.dataDir),schedule:(i,o)=>this.clock.schedule(this.traceRecorder.bind(i),o)});n.setTradingDay("paper",r.tradingDay),this.runtime=new mr({createWarmupProvider:()=>new Bt(r,this.barHistory,this.clock.now,!this.warmupSupplied),stateStore:this.stateStore,live:this.live,paperExecution:{...this.paperExecution,onOrderSubmitted:(i)=>{let o={vtOrderId:i.vtOrderId,direction:i.direction,offset:i.offset,price:i.price,volume:i.volume};this.submissions.push(o),this.traceRecorder.record("submission",o)},onCancelRequested:(i)=>{this.cancelRequests.push(i.vtOrderId),this.traceRecorder.record("cancel",{vtOrderId:i.vtOrderId})}},orderRisk:n,executionLimits:this.executionLimits,log:(i)=>this.recordLog(i),now:()=>this.clock.now,schedule:(i,o)=>this.clock.schedule(this.traceRecorder.bind(i),o),createPaperSessionId:()=>String(++this.paperSessionSequence),observe:(i)=>this.observe(i),emitHook:(i,o,s)=>{if(i!=="strategy.event")return;let a=structuredClone({...o,occurredAt:s??this.clock.now});this.hooks.push(a),this.events.push({type:"hook",value:a,now:a.occurredAt}),this.traceRecorder.record("hook",a)}}),await this.live.connect(),this.running=this.runtime.addRestored({sourcePath:"strategy.ts",hash:"a".repeat(64),artifact:"strategy-test",loadedAt:new Date(0).toISOString(),mode:this.mode,definition:this.definition,instance:{id:Q,vtSymbol:this.fixture.vtSymbol,setting:this.setting}})}observe(e){if(this.traceRecorder.record(e.type,e),e.type==="callback"&&e.stage==="begin"&&Object.hasOwn(this.callbackCounts,e.callback))this.callbackCounts[e.callback]++;else if(e.type==="order"){let t=Zo(e.value);if(t.type==="error"&&(t.code==="invalidRequest"||t.code==="insufficientPosition"))this.firstSdkOrderViolation??=`${t.code}: ${t.message}`;this.orderEvents.push(t),this.events.push({type:"order",value:t,now:e.now})}else if(e.type==="trade")this.tradeEvents.push({...e.value}),this.events.push({type:"trade",value:{...e.value},now:e.now});else if(e.type==="fault")this.faults.push({message:`${e.callback}: ${e.message}`,verified:!1})}recordLog(e){let t=Qo(this.logs,e);this.traceRecorder.record("log",{message:t})}async rebuildRuntime(e=!0){await this.runtime.close({persist:e}),this.runtime=void 0,this.running=void 0,this.aggregator.reset(),this.initialized=!1,await this.createRuntime(),await this.runtime.warmup(),this.runtime.setTimerClock(this.clock.now),this.initialized=!0}recordEventTime(e){let t=e+60000;if(!Number.isSafeInteger(t))throw Error("\u884C\u60C5\u65F6\u95F4\u8D85\u8FC7\u5B89\u5168\u6574\u6570\u8303\u56F4");if(e<this.clock.now)throw Error("\u884C\u60C5\u65F6\u95F4\u4E0D\u80FD\u56DE\u9000");this.advanceClock(e),this.nextEventTimeMs=t}advanceClock(e){this.clock.advanceTo(e,this.initialized?()=>{this.traceRecorder.record("timer",{now:this.clock.now}),this.runtime.handleTimer(this.clock.now),this.aggregator.completeThrough(this.clock.now-dt),this.dispatchBars(this.takeCompletedBars())}:void 0)}takeCompletedBars(){let e=[...this.aggregator.getCompletedBars()];return this.aggregator.clearCompletedBars(),e.filter((t)=>t.barTime>(this.barHistory.at(-1)?.barTime??-1))}validateClosedBars(e,t){if(Ie(e,t,this.fixture.instrumentID),e.some((n)=>n.barTime<=(this.barHistory.at(-1)?.barTime??-1)))throw Error("\u95ED\u5408 Bar \u65F6\u95F4\u5FC5\u987B\u665A\u4E8E\u5DF2\u56DE\u653E\u5386\u53F2");let r=this.aggregator.getCurrentBar("1min");if(r&&t>=(r.barTime+60)*1000+dt&&e.some((n)=>n.barTime===r.barTime))throw Error("\u663E\u5F0F Bar \u7684\u4EA4\u4ED8\u665A\u4E8E\u81EA\u52A8\u95ED\u5408\u671F\u9650\uFF0C\u4E0D\u80FD\u66FF\u6362\u5DF2\u5230\u671F\u7684 Bar")}mergeClosedBars(e,t){let r=new Map(e.map((n)=>[n.barTime,n]));for(let n of t)r.set(n.barTime,n);return[...r.values()].sort((n,i)=>n.barTime-i.barTime)}rememberBars(e){this.barHistory.push(...e.map((t)=>({...t})))}dispatchBars(e){this.rememberBars(e);for(let t of e)this.traceRecorder.record("bar",t),this.runtime.handleBar(t)}seedInitialState(){if(this.initialStateWritten||!this.initialState)return;let e=We(this.initialState.inventory),t=this.initialState.variables===void 0?{}:ce(this.initialState.variables,"initialState.variables");this.stateStore.write({strategyId:Q,vtSymbol:this.fixture.vtSymbol,mode:this.mode,bundleHash:"a".repeat(64),pos:Mr(e),variables:t,inventory:e,tradingDay:this.fixture.tradingDay}),this.initialStateWritten=!0}requireInitialized(){if(this.disposed)throw Error("\u6D4B\u8BD5 Runtime \u5DF2\u91CA\u653E");if(!this.initialized||!this.runtime)throw Error("\u6D4B\u8BD5 Runtime \u5C1A\u672A initialize()")}requireLive(){if(this.requireInitialized(),this.mode!=="live"||!(this.live instanceof Oe))throw Error('broker \u63A7\u5236\u53EA\u9002\u7528\u4E8E mode="live"\u3001execution="scripted" \u7684\u6D4B\u8BD5 Runtime');return this.live}observeCommands(e,t,r){return new Proxy(e,{get:(n,i)=>{let o=Reflect.get(n,i);if(typeof o!=="function"||!Object.hasOwn(n,i)||r.includes(String(i)))return o;return(...s)=>this.traceRecorder.input({type:t,method:i,args:s},()=>Reflect.apply(o,n,s))}})}}function Wo(e){let t={...Yo,...e};return{maxOrderVolume:x(t.maxOrderVolume,"executionLimits.maxOrderVolume"),maxPosition:x(t.maxPosition,"executionLimits.maxPosition"),maxActiveOpenOrders:x(t.maxActiveOpenOrders,"executionLimits.maxActiveOpenOrders")}}function Jo(e){let t={...Gn.dailyLimits,...e?.dailyLimits};for(let r of Object.keys(t))x(t[r],`riskLimits.dailyLimits.${r}`);return{dailyLimits:t,duplicateOrderLimit:x(e?.duplicateOrderLimit??Gn.duplicateOrderLimit,"riskLimits.duplicateOrderLimit")}}function Qo(e,t){let r=Xo(es(t),Ko);if(e.push(r),e.length>jn)e.splice(0,e.length-jn);return r}function Zo(e){return{...e}}function Xo(e,t){let r=Buffer.from(e);if(r.length<=t)return e;return`${r.subarray(0,t).toString("utf8")}...`}function es(e){return e.replace(/\bsk-[A-Za-z0-9_-]{6,}\b/g,"[redacted]").replace(/((?:api[_ -]?key|authorization|access[_ -]?token)\s*[:=]\s*)\S+/gi,"$1[redacted]")}async function pr(e,t,r){await e.initialize(),await e.start();for(let n of[0,1,-1,2,-2,0]){if(e.pushTick({lastPrice:Math.max(t.priceTick,t.seedPrice+n*t.priceTick),askVolume1:1,bidVolume1:1}),e.advanceTime(1500),r==="live")for(let i of e.activeOrders()){if(i.status==="submitting")e.broker.acknowledge(i.vtOrderId);if(!e.activeOrders().some((o)=>o.vtOrderId===i.vtOrderId))continue;if(i.volume-i.traded>1)e.broker.partialFill(i.vtOrderId,1);else e.broker.fill(i.vtOrderId)}e.advanceTime(60000),e.assertHealthy("\u786E\u5B9A\u6027\u56DE\u653E")}return await e.stop(),e.settleActiveOrders(),await e.restart(),await e.start(),e.pushTick({lastPrice:t.seedPrice}),e.advanceTime(1000),await e.stop(),e.settleActiveOrders(),e.assertHealthy("\u786E\u5B9A\u6027\u91CD\u542F"),{trace:e.trace,submissions:e.submissions,cancelRequests:e.cancelRequests,events:e.events,orderEvents:e.orderEvents,tradeEvents:e.tradeEvents,hooks:e.hooks,logs:e.logs,snapshot:e.snapshot()}}import{isDeepStrictEqual as Yn,inspect as Wn}from"util";class ft extends Error{step;scenario;trace;constructor(e,t,r,n){super(`Strategy scenario failed at step ${e}${t.seed===void 0?"":` (seed ${t.seed})`}: ${n instanceof Error?n.message:String(n)}
|
|
50
|
+
Replay: ${Jn(t)}`,{cause:n});this.step=e;this.scenario=t;this.trace=r;this.name="StrategyScenarioError"}}async function gr(e,t,r={}){if(yt(t),t.context?.customPaperExecution&&!r.paperExecution)throw Error("Custom Paper hooks must be supplied to replay this scenario");let n=new ie(e.strategy,Y(t.context?.fixture??e.fixture),{...t.context?.options,...r}),i={...t,context:n.trace[0].value},o=0,s=new Map;try{for(;o<t.steps.length;o++){let d=t.steps[o];if(d.at!==void 0)n.setTime(d.at);if(d.action.type==="await"){let c=s.get(d.action.task);if(!c)throw Error(`Scenario task does not exist: ${d.action.task}`);let u=await c;if(s.delete(d.action.task),d.action.error===void 0){if("error"in u)throw u.error}else if(!("error"in u)||(u.error instanceof Error?u.error.message:String(u.error))!==d.action.error)throw Error(`Scenario task did not reject with: ${d.action.error}`)}else if(d.task)s.set(d.task,Kn(n,d.action).then(()=>({}),(c)=>({error:c})));else await Kn(n,d.action)}let a={...t.seed===void 0?{}:{seed:t.seed},trace:n.trace,snapshot:n.snapshot(),submissions:structuredClone(n.submissions),cancelRequests:[...n.cancelRequests]};return await n.dispose(),a}catch(a){try{await n.dispose()}catch(d){if(d!==a)a=AggregateError([a,d],String(a))}throw new ft(o,i,n.trace,a)}}async function ts(e,t,r={}){let n=await gr(e,t,r),i=await gr(e,t,r);try{if(fr(n.trace,i.trace),!Yn(n.snapshot,i.snapshot))throw Error(`Strategy replay snapshot differs:
|
|
51
|
+
${Wn({expected:n.snapshot,actual:i.snapshot},{depth:8})}`)}catch(o){let s={...t,context:i.trace[0].value};throw new ft(t.steps.length,s,i.trace,o)}return n}function fr(e,t){for(let r=0;r<Math.max(e.length,t.length);r++)if(!Yn(e[r],t[r]))throw Error(`Strategy replay first differs at trace[${r}] (sequence ${r+1}):
|
|
52
|
+
${Wn({expected:e[r],actual:t[r]},{depth:8})}`)}function rs(e,t){let r=e.trace,n=new Map;for(let s of r)if(s.type==="completion"){let a=s.value;n.set(a.input,a)}let i=[];for(let s of r){if(s.type==="input")i.push({...n.has(s.sequence)?{task:`input-${s.sequence}`}:{},action:structuredClone(s.value)});if(s.type==="completion"){let a=s.value;i.push({action:{type:"await",task:`input-${a.input}`,...a.error===void 0?{}:{error:a.error}}})}}let o={...t===void 0?{}:{seed:t},context:structuredClone(r.find((s)=>s.type==="context")?.value),steps:i};return yt(o),o}function Jn(e){return yt(e),JSON.stringify(hr(e))}function ns(e){let t=ht(JSON.parse(e));return yt(t),t}async function Kn(e,t){switch(t.type){case"initialize":await e.initialize(Object.hasOwn(t,"warmupBars")?{warmupBars:t.warmupBars}:{});return;case"start":await e.start();return;case"stop":await e.stop();return;case"restart":await e.restart();return;case"crashAndRestart":await e.crashAndRestart();return;case"setTime":e.setTime(t.datetime);return;case"advanceTime":e.advanceTime(t.milliseconds);return;case"market":e.pushMarketEvent(t.tick,t.closedBars??[]);return;case"bar":e.pushBar(t.value,{datetime:t.datetime});return;case"cancelOrder":e.cancelOrder(Le(t.vtOrderId,e));return;case"expectFault":e.expectFault(t.message);return;case"broker":{let r=e.broker[t.method];if(!Object.hasOwn(e.broker,t.method)||typeof r!=="function"||t.method==="snapshot"||t.method==="pendingReports")throw Error("Invalid broker action");await Reflect.apply(r,e.broker,Le(t.args,e));return}case"gateway":{let r=e.gateway[t.method];if(!Object.hasOwn(e.gateway,t.method)||typeof r!=="function"||t.method==="pendingRequests")throw Error("Invalid gateway action");await Reflect.apply(r,e.gateway,Le(t.args,e));return}default:throw Error(`Unknown scenario action: ${t.type}`)}}function Le(e,t){if(Array.isArray(e))return e.map((r)=>Le(r,t));if(e&&typeof e==="object"){let r=e;if(Object.keys(r).length===1&&Object.hasOwn(r,"submission")){let n=r.submission;if(!Number.isSafeInteger(n)||n<0)throw Error("Invalid submission reference");let i=t.submissions[n];if(!i)throw Error(`Submission reference does not exist: ${n}`);return i.vtOrderId}if(Object.keys(r).length===1&&typeof r.request==="string"){let n=t.gateway.pendingRequests().find((i)=>i.method===r.request);if(!n)throw Error(`Pending request reference does not exist: ${r.request}`);return n.id}return Object.fromEntries(Object.entries(r).map(([n,i])=>[n,Le(i,t)]))}return e}function yt(e){if(!e||typeof e!=="object"||!Array.isArray(e.steps))throw Error("Scenario steps must be an array");if(e.seed!==void 0&&!Number.isSafeInteger(e.seed))throw Error("Scenario seed must be a safe integer");if(e.context!==void 0){if(!e.context||typeof e.context!=="object")throw Error("Invalid scenario context");if(Y(e.context.fixture),!e.context.options||typeof e.context.options!=="object"||Array.isArray(e.context.options)||Object.hasOwn(e.context.options,"paperExecution"))throw Error("Invalid portable scenario options");if(e.context.customPaperExecution!==void 0&&e.context.customPaperExecution!==!0)throw Error("Invalid custom Paper hook marker")}let t=-1,r=new Set,n=new Set;for(let i of e.steps){if(!i||!i.action||typeof i.action.type!=="string")throw Error("Invalid scenario step");if(i.task!==void 0){if(!i.task||r.has(i.task)||i.action.type==="await")throw Error("Scenario task names must be unique");r.add(i.task)}if(i.action.type==="await"){if(!r.has(i.action.task)||n.has(i.action.task))throw Error(`Unknown or already joined task: ${i.action.task}`);n.add(i.action.task)}if(i.at!==void 0){if(!Number.isSafeInteger(i.at)||i.at<t||i.at<0)throw Error("Scenario times must be nonnegative and ordered");t=i.at}}if(r.size!==n.size)throw Error("Every scenario task must have an await step")}var G="$vibekTesting";function hr(e,t=new Set){if(e===void 0)return{[G]:"undefined"};if(Object.is(e,-0))return{[G]:"number",value:"-0"};if(typeof e==="number"&&!Number.isFinite(e))return{[G]:"number",value:String(e)};if(e===null||typeof e==="string"||typeof e==="boolean"||typeof e==="number")return e;if(typeof e!=="object")throw Error(`Scenario cannot serialize ${typeof e}`);if(t.has(e))throw Error("Scenario contains a cycle");t.add(e);try{if(Array.isArray(e))return e.map((n)=>hr(n,t));if(Object.getPrototypeOf(e)!==Object.prototype&&Object.getPrototypeOf(e)!==null)throw Error("Scenario values must be plain objects");let r=Object.entries(e).map(([n,i])=>[n,hr(i,t)]);return Object.hasOwn(e,G)?{[G]:"object",entries:r}:Object.fromEntries(r)}finally{t.delete(e)}}function ht(e){if(!e||typeof e!=="object")return e;if(Array.isArray(e))return e.map(ht);let t=e;if(!Object.hasOwn(t,G))return Object.fromEntries(Object.entries(t).map(([r,n])=>[r,ht(n)]));if(t[G]==="undefined")return;if(t[G]==="number"&&["NaN","Infinity","-Infinity","-0"].includes(String(t.value)))return Number(t.value);if(t[G]==="object"&&Array.isArray(t.entries))return Object.fromEntries(t.entries.map((r)=>{if(!Array.isArray(r)||r.length!==2||typeof r[0]!=="string")throw Error("Invalid scenario object entry");return[r[0],ht(r[1])]}));throw Error("Invalid encoded scenario value")}var ye={definition:"SDK-001 strategy definition",paper:"SDK-101 paper lifecycle",live:"SDK-102 fake-live lifecycle",persistence:"SDK-103 state persistence and restart",replay:"SDK-104 deterministic replay"};function is(e){let{afterEach:t}=wt("bun:test"),r=Y(e.fixture),n=new Set,i=0,o=(s={})=>{let a=new ie(e.strategy,r,s);return n.add(a),a};return t(async()=>{let s=[...n];n.clear(),i=0;let d=(await Promise.allSettled(s.map((c)=>c.dispose()))).flatMap((c)=>c.status==="rejected"?[c.reason]:[]);if(d.length)throw AggregateError(d,"Strategy test cleanup failed")}),os(e.strategy,r,o),{createRuntime:o,bar:(s)=>H(r,s,i++),tick:(s)=>De(r,s,i++)}}function os(e,t,r){let{describe:n,expect:i,test:o}=wt("bun:test");n("SDK contract",()=>{o(ye.definition,()=>{let s=Se(e,"inspect",t.setting);i(s.ctor).toBe(e.strategy)}),o(ye.paper,async()=>{await Qn(r({mode:"paper"}),t.seedPrice)}),o(ye.live,async()=>{await Qn(r({mode:"live"}),t.seedPrice)}),o(ye.persistence,async()=>{let s=r({mode:"paper"});await s.initialize(),await s.start(),s.pushTick({lastPrice:t.seedPrice}),s.pushBar({close:t.seedPrice}),await s.stop(),s.settleActiveOrders();let a=s.snapshot();await s.restart();let d=s.snapshot();i(d.pos).toBe(a.pos),i(d.variables).toEqual(a.variables),s.assertHealthy("\u91CD\u542F")}),o(ye.replay,async()=>{for(let s of["paper","live"]){let a=await pr(r({mode:s}),t,s),d=await pr(r({mode:s}),t,s);fr(a.trace,d.trace),i(a).toEqual(d)}})})}async function Qn(e,t){let{expect:r}=wt("bun:test");await e.initialize(),r(e.callbackCounts.onInit).toBe(1),e.assertHealthy("\u521D\u59CB\u5316"),await e.start(),r(e.callbackCounts.onStart).toBe(1);let n={...e.callbackCounts};e.pushTick({lastPrice:t}),e.pushBar({close:t}),r(e.callbackCounts.onTick-n.onTick).toBe(1),r(e.callbackCounts.onBar-n.onBar).toBe(1),e.assertHealthy("\u884C\u60C5\u56DE\u8C03"),e.settleActiveOrders(),await e.stop(),r(e.callbackCounts.onStop).toBe(1),e.settleActiveOrders(),r(e.snapshot()).toMatchObject({trading:!1,activeOrders:0,fault:null})}function Cu(e,t={}){return new ie(e.strategy,Y(e.fixture),t)}export{ft as StrategyScenarioError,ts as assertStrategyReplay,rs as captureStrategyScenario,Cu as createStrategyTest,ns as parseStrategyScenario,gr as runStrategyScenario,Jn as serializeStrategyScenario,is as setupStrategyTests};
|