desic-okx-agent 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +353 -0
- package/README.md +194 -190
- package/dist/account/private-websocket.js +4 -4
- package/dist/account/private-websocket.js.map +1 -1
- package/dist/account/service.d.ts +12 -1
- package/dist/account/service.js +18 -0
- package/dist/account/service.js.map +1 -1
- package/dist/bars/rate-limiter.d.ts +18 -0
- package/dist/bars/rate-limiter.js +84 -0
- package/dist/bars/rate-limiter.js.map +1 -0
- package/dist/bars/schema.d.ts +36 -0
- package/dist/bars/schema.js +134 -0
- package/dist/bars/schema.js.map +1 -0
- package/dist/bars/service.d.ts +60 -0
- package/dist/bars/service.js +120 -0
- package/dist/bars/service.js.map +1 -0
- package/dist/bars/store.d.ts +105 -0
- package/dist/bars/store.js +415 -0
- package/dist/bars/store.js.map +1 -0
- package/dist/bars/timeframe.d.ts +40 -0
- package/dist/bars/timeframe.js +146 -0
- package/dist/bars/timeframe.js.map +1 -0
- package/dist/bars/types.d.ts +68 -0
- package/dist/bars/types.js +13 -0
- package/dist/bars/types.js.map +1 -0
- package/dist/cli/data-render.d.ts +37 -0
- package/dist/cli/data-render.js +143 -0
- package/dist/cli/data-render.js.map +1 -0
- package/dist/cli/doctor.js +7 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +758 -27
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/live-render.d.ts +24 -0
- package/dist/cli/live-render.js +85 -0
- package/dist/cli/live-render.js.map +1 -0
- package/dist/cli/range.d.ts +28 -0
- package/dist/cli/range.js +63 -0
- package/dist/cli/range.js.map +1 -0
- package/dist/cli/render.js +3 -0
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/strategy-render.d.ts +36 -0
- package/dist/cli/strategy-render.js +391 -0
- package/dist/cli/strategy-render.js.map +1 -0
- package/dist/cli/width.d.ts +18 -0
- package/dist/cli/width.js +71 -0
- package/dist/cli/width.js.map +1 -0
- package/dist/config/loader.js +1 -1
- package/dist/config/schema.d.ts +7 -0
- package/dist/config/schema.js +24 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/core/okx-client.d.ts +9 -1
- package/dist/core/okx-client.js +14 -5
- package/dist/core/okx-client.js.map +1 -1
- package/dist/i18n/locale.d.ts +24 -0
- package/dist/i18n/locale.js +65 -0
- package/dist/i18n/locale.js.map +1 -0
- package/dist/i18n/messages.d.ts +333 -0
- package/dist/i18n/messages.js +660 -0
- package/dist/i18n/messages.js.map +1 -0
- package/dist/live/account-snapshot.d.ts +30 -0
- package/dist/live/account-snapshot.js +130 -0
- package/dist/live/account-snapshot.js.map +1 -0
- package/dist/live/cutoff-queue.d.ts +42 -0
- package/dist/live/cutoff-queue.js +69 -0
- package/dist/live/cutoff-queue.js.map +1 -0
- package/dist/live/execution-key.d.ts +23 -0
- package/dist/live/execution-key.js +31 -0
- package/dist/live/execution-key.js.map +1 -0
- package/dist/live/failures.d.ts +37 -0
- package/dist/live/failures.js +57 -0
- package/dist/live/failures.js.map +1 -0
- package/dist/live/gates.d.ts +65 -0
- package/dist/live/gates.js +136 -0
- package/dist/live/gates.js.map +1 -0
- package/dist/live/loop.d.ts +56 -0
- package/dist/live/loop.js +197 -0
- package/dist/live/loop.js.map +1 -0
- package/dist/live/preconditions.d.ts +48 -0
- package/dist/live/preconditions.js +69 -0
- package/dist/live/preconditions.js.map +1 -0
- package/dist/live/reconcile.d.ts +46 -0
- package/dist/live/reconcile.js +104 -0
- package/dist/live/reconcile.js.map +1 -0
- package/dist/live/runner.d.ts +57 -0
- package/dist/live/runner.js +160 -0
- package/dist/live/runner.js.map +1 -0
- package/dist/live/schema.d.ts +18 -0
- package/dist/live/schema.js +91 -0
- package/dist/live/schema.js.map +1 -0
- package/dist/live/service.d.ts +144 -0
- package/dist/live/service.js +303 -0
- package/dist/live/service.js.map +1 -0
- package/dist/live/session.d.ts +85 -0
- package/dist/live/session.js +234 -0
- package/dist/live/session.js.map +1 -0
- package/dist/live/sizing.d.ts +62 -0
- package/dist/live/sizing.js +79 -0
- package/dist/live/sizing.js.map +1 -0
- package/dist/live/store.d.ts +123 -0
- package/dist/live/store.js +350 -0
- package/dist/live/store.js.map +1 -0
- package/dist/live/types.d.ts +82 -0
- package/dist/live/types.js +2 -0
- package/dist/live/types.js.map +1 -0
- package/dist/market/websocket.d.ts +16 -1
- package/dist/market/websocket.js +60 -5
- package/dist/market/websocket.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +15 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/network/connectivity.d.ts +9 -1
- package/dist/network/connectivity.js +28 -1
- package/dist/network/connectivity.js.map +1 -1
- package/dist/report/chart-script.d.ts +12 -0
- package/dist/report/chart-script.js +146 -0
- package/dist/report/chart-script.js.map +1 -0
- package/dist/report/compare-html.d.ts +8 -0
- package/dist/report/compare-html.js +254 -0
- package/dist/report/compare-html.js.map +1 -0
- package/dist/report/compare-script.d.ts +12 -0
- package/dist/report/compare-script.js +109 -0
- package/dist/report/compare-script.js.map +1 -0
- package/dist/report/compare.d.ts +61 -0
- package/dist/report/compare.js +205 -0
- package/dist/report/compare.js.map +1 -0
- package/dist/report/fetch.d.ts +20 -0
- package/dist/report/fetch.js +56 -0
- package/dist/report/fetch.js.map +1 -0
- package/dist/report/html.d.ts +54 -0
- package/dist/report/html.js +641 -0
- package/dist/report/html.js.map +1 -0
- package/dist/report/open.d.ts +42 -0
- package/dist/report/open.js +114 -0
- package/dist/report/open.js.map +1 -0
- package/dist/runtime/server.d.ts +16 -1
- package/dist/runtime/server.js +112 -9
- package/dist/runtime/server.js.map +1 -1
- package/dist/setup/installer.d.ts +1 -0
- package/dist/setup/installer.js +8 -0
- package/dist/setup/installer.js.map +1 -1
- package/dist/setup/wizard.d.ts +3 -0
- package/dist/setup/wizard.js +131 -6
- package/dist/setup/wizard.js.map +1 -1
- package/dist/strategy/constants.d.ts +23 -0
- package/dist/strategy/constants.js +24 -0
- package/dist/strategy/constants.js.map +1 -0
- package/dist/strategy/environment.d.ts +52 -0
- package/dist/strategy/environment.js +187 -0
- package/dist/strategy/environment.js.map +1 -0
- package/dist/strategy/instrument.d.ts +29 -0
- package/dist/strategy/instrument.js +39 -0
- package/dist/strategy/instrument.js.map +1 -0
- package/dist/strategy/optimize.d.ts +73 -0
- package/dist/strategy/optimize.js +113 -0
- package/dist/strategy/optimize.js.map +1 -0
- package/dist/strategy/parameter-space.d.ts +59 -0
- package/dist/strategy/parameter-space.js +221 -0
- package/dist/strategy/parameter-space.js.map +1 -0
- package/dist/strategy/python-bridge.d.ts +24 -0
- package/dist/strategy/python-bridge.js +114 -0
- package/dist/strategy/python-bridge.js.map +1 -0
- package/dist/strategy/schema.d.ts +9 -0
- package/dist/strategy/schema.js +91 -0
- package/dist/strategy/schema.js.map +1 -0
- package/dist/strategy/service.d.ts +138 -0
- package/dist/strategy/service.js +745 -0
- package/dist/strategy/service.js.map +1 -0
- package/dist/strategy/settings.d.ts +162 -0
- package/dist/strategy/settings.js +243 -0
- package/dist/strategy/settings.js.map +1 -0
- package/dist/strategy/store.d.ts +96 -0
- package/dist/strategy/store.js +367 -0
- package/dist/strategy/store.js.map +1 -0
- package/dist/strategy/templates.d.ts +11 -0
- package/dist/strategy/templates.js +134 -0
- package/dist/strategy/templates.js.map +1 -0
- package/dist/strategy/types.d.ts +111 -0
- package/dist/strategy/types.js +2 -0
- package/dist/strategy/types.js.map +1 -0
- package/dist/tools/catalog.d.ts +16 -0
- package/dist/tools/catalog.js +118 -17
- package/dist/tools/catalog.js.map +1 -1
- package/dist/trade/service.d.ts +12 -0
- package/dist/trade/service.js +24 -7
- package/dist/trade/service.js.map +1 -1
- package/dist/tui/app.d.ts +23 -0
- package/dist/tui/app.js +322 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/commands.d.ts +70 -0
- package/dist/tui/commands.js +313 -0
- package/dist/tui/commands.js.map +1 -0
- package/dist/tui/entries.d.ts +17 -0
- package/dist/tui/entries.js +24 -0
- package/dist/tui/entries.js.map +1 -0
- package/dist/tui/execute.d.ts +26 -0
- package/dist/tui/execute.js +664 -0
- package/dist/tui/execute.js.map +1 -0
- package/dist/tui/history.d.ts +17 -0
- package/dist/tui/history.js +48 -0
- package/dist/tui/history.js.map +1 -0
- package/dist/tui/index.d.ts +8 -0
- package/dist/tui/index.js +48 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/line-editor.d.ts +44 -0
- package/dist/tui/line-editor.js +98 -0
- package/dist/tui/line-editor.js.map +1 -0
- package/dist/tui/progress.d.ts +23 -0
- package/dist/tui/progress.js +46 -0
- package/dist/tui/progress.js.map +1 -0
- package/dist/tui/settings-editor.d.ts +18 -0
- package/dist/tui/settings-editor.js +115 -0
- package/dist/tui/settings-editor.js.map +1 -0
- package/docs/live-trading.md +455 -0
- package/docs/strategy-research.md +597 -0
- package/package.json +11 -1
- package/python/desic_strategy/__init__.py +34 -0
- package/python/desic_strategy/actions.py +158 -0
- package/python/desic_strategy/context.py +164 -0
- package/python/desic_strategy/engine.py +614 -0
- package/python/desic_strategy/indicators.py +159 -0
- package/python/desic_strategy/live.py +253 -0
- package/python/desic_strategy/policy.py +193 -0
- package/python/desic_strategy/portfolio.py +152 -0
- package/python/desic_strategy/report.py +319 -0
- package/python/desic_strategy/runner.py +574 -0
- package/python/desic_strategy/timeframe.py +150 -0
- package/python/main.py +18 -0
- package/skills/okx-live-trading/SKILL.md +117 -0
- package/skills/okx-live-trading/agents/openai.yaml +9 -0
- package/skills/okx-live-trading/references/lifecycle.md +128 -0
- package/skills/okx-strategy-research/SKILL.md +113 -0
- package/skills/okx-strategy-research/agents/openai.yaml +9 -0
- package/skills/okx-strategy-research/references/execution-semantics.md +107 -0
- package/skills/okx-strategy-research/references/field-traps.md +142 -0
- package/skills/okx-strategy-research/references/python-api.md +121 -0
- package/skills/okx-strategy-research/references/tools-and-data.md +192 -0
- package/skills/okx-trading/SKILL.md +11 -10
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { RuntimeError } from "../core/errors.js";
|
|
2
|
+
import { resultMeta } from "../core/types.js";
|
|
3
|
+
import { loadContractSpec } from "../strategy/instrument.js";
|
|
4
|
+
import { pythonStatus } from "../strategy/environment.js";
|
|
5
|
+
import { checkPreconditions } from "./preconditions.js";
|
|
6
|
+
import { reconcileSignals } from "./reconcile.js";
|
|
7
|
+
import { describeAutoDisable, evaluateStreak } from "./failures.js";
|
|
8
|
+
/** Consecutive failures after which a profile stops itself. */
|
|
9
|
+
export const ERROR_STREAK_LIMIT = 3;
|
|
10
|
+
/**
|
|
11
|
+
* Owns live profiles: their lifecycle, and what has to be true before one trades.
|
|
12
|
+
*
|
|
13
|
+
* Creating a profile never starts it. Enabling is the only step that can lead to an
|
|
14
|
+
* order, and it is the step that checks preconditions — so a profile can be written,
|
|
15
|
+
* inspected, and left alone safely.
|
|
16
|
+
*/
|
|
17
|
+
export class LiveService {
|
|
18
|
+
store;
|
|
19
|
+
strategyStore;
|
|
20
|
+
bars;
|
|
21
|
+
market;
|
|
22
|
+
accounts;
|
|
23
|
+
client;
|
|
24
|
+
trade;
|
|
25
|
+
errorStreakLimit;
|
|
26
|
+
constructor(store, strategyStore, bars, market, accounts, client,
|
|
27
|
+
/** Used to apply the profile's leverage at enable time. See `applyLeverage`. */
|
|
28
|
+
trade,
|
|
29
|
+
/** Consecutive systemic failures after which a profile stops itself. */
|
|
30
|
+
errorStreakLimit = ERROR_STREAK_LIMIT) {
|
|
31
|
+
this.store = store;
|
|
32
|
+
this.strategyStore = strategyStore;
|
|
33
|
+
this.bars = bars;
|
|
34
|
+
this.market = market;
|
|
35
|
+
this.accounts = accounts;
|
|
36
|
+
this.client = client;
|
|
37
|
+
this.trade = trade;
|
|
38
|
+
this.errorStreakLimit = errorStreakLimit;
|
|
39
|
+
}
|
|
40
|
+
onDisabled;
|
|
41
|
+
/**
|
|
42
|
+
* Marks profiles a dead worker left running.
|
|
43
|
+
*
|
|
44
|
+
* Called at startup. Their generation advances, so an order decided by the previous
|
|
45
|
+
* process cannot be submitted if it somehow still arrives.
|
|
46
|
+
*/
|
|
47
|
+
recoverInterrupted() {
|
|
48
|
+
return this.store.failInterruptedProfiles();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolves signals left unresolved by a previous process.
|
|
52
|
+
*
|
|
53
|
+
* Every one is checked against the exchange rather than assumed. Nothing is resubmitted:
|
|
54
|
+
* re-sending an order that may already exist is the one mistake worse than missing one,
|
|
55
|
+
* so anything that cannot be resolved is marked ambiguous and waits for a person.
|
|
56
|
+
*/
|
|
57
|
+
async reconcile() {
|
|
58
|
+
const outcome = await reconcileSignals({
|
|
59
|
+
client: this.client,
|
|
60
|
+
accounts: this.accounts,
|
|
61
|
+
store: this.store
|
|
62
|
+
});
|
|
63
|
+
return { data: outcome, meta: resultMeta({ source: "mixed" }) };
|
|
64
|
+
}
|
|
65
|
+
createProfile(input) {
|
|
66
|
+
validateBudgets(input);
|
|
67
|
+
// Deliberately does not check preconditions: a profile that cannot yet be enabled
|
|
68
|
+
// is still worth creating, and refusing here would mean writing the definition and
|
|
69
|
+
// the evidence in one step.
|
|
70
|
+
return { data: this.store.createProfile(input), meta: resultMeta({ source: "sqlite" }) };
|
|
71
|
+
}
|
|
72
|
+
listProfiles() {
|
|
73
|
+
return { data: this.store.listProfiles(), meta: resultMeta({ source: "sqlite" }) };
|
|
74
|
+
}
|
|
75
|
+
getProfile(id) {
|
|
76
|
+
return { data: this.store.requireProfile(id), meta: resultMeta({ source: "sqlite" }) };
|
|
77
|
+
}
|
|
78
|
+
signals(profileId, limit = 50) {
|
|
79
|
+
this.store.requireProfile(profileId);
|
|
80
|
+
return { data: this.store.listSignals(profileId, limit), meta: resultMeta({ source: "sqlite" }) };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Reports whether a profile could be enabled, without enabling it.
|
|
84
|
+
*
|
|
85
|
+
* Separate from `enable` so the list of blockers can be read before committing to
|
|
86
|
+
* anything — and so a caller that wants to show them does not have to trigger a
|
|
87
|
+
* failure to find out.
|
|
88
|
+
*/
|
|
89
|
+
async readiness(id) {
|
|
90
|
+
const profile = this.store.requireProfile(id);
|
|
91
|
+
const facts = await this.gatherFacts(profile);
|
|
92
|
+
return {
|
|
93
|
+
data: {
|
|
94
|
+
profile,
|
|
95
|
+
ready: facts.failures.length === 0,
|
|
96
|
+
failures: facts.failures,
|
|
97
|
+
backtestRunId: facts.backtestRunId
|
|
98
|
+
},
|
|
99
|
+
meta: resultMeta({ source: "mixed" })
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Enables a profile, refusing unless every precondition holds.
|
|
104
|
+
*
|
|
105
|
+
* The backtest that justified it is recorded on the profile, so a later reader can
|
|
106
|
+
* see which run was the evidence rather than having to trust that one existed.
|
|
107
|
+
*/
|
|
108
|
+
async enable(id) {
|
|
109
|
+
const profile = this.store.requireProfile(id);
|
|
110
|
+
const facts = await this.gatherFacts(profile);
|
|
111
|
+
if (facts.failures.length > 0) {
|
|
112
|
+
throw new RuntimeError("VALIDATION", `Cannot enable '${profile.name}': ${facts.failures.map((failure) => failure.message).join(" ")}`, false, { failures: facts.failures });
|
|
113
|
+
}
|
|
114
|
+
// Before the row is written, so a profile is never enabled at a leverage other than
|
|
115
|
+
// its own. Sizing divides the entry budget by `profile.leverage`; if the exchange
|
|
116
|
+
// holds a different figure, the margin actually committed differs by that ratio and
|
|
117
|
+
// the budget stops meaning what it says. Measured on demo: a profile set to 10x on
|
|
118
|
+
// an account left at 3x turned a 60 USDT entry budget into roughly 198 USDT of
|
|
119
|
+
// margin.
|
|
120
|
+
const applied = await this.applyLeverage(profile);
|
|
121
|
+
return {
|
|
122
|
+
data: {
|
|
123
|
+
profile: this.store.enableProfile(id, facts.backtestRunId),
|
|
124
|
+
backtestRunId: facts.backtestRunId,
|
|
125
|
+
leverage: applied
|
|
126
|
+
},
|
|
127
|
+
meta: resultMeta({ source: "mixed" })
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Writes the profile's leverage to the exchange, and refuses to enable if it cannot.
|
|
132
|
+
*
|
|
133
|
+
* This changes a setting the account shares across everything trading that instrument
|
|
134
|
+
* and margin mode, including positions opened by hand. That is the cost of the budgets
|
|
135
|
+
* meaning what they say, and it is why it happens at enable rather than silently on
|
|
136
|
+
* every bar.
|
|
137
|
+
*
|
|
138
|
+
* A failure here stops the enable. Continuing would leave the profile trading at a
|
|
139
|
+
* leverage nobody chose, which is the exact condition this call exists to remove.
|
|
140
|
+
*/
|
|
141
|
+
async applyLeverage(profile) {
|
|
142
|
+
const lever = String(profile.leverage);
|
|
143
|
+
try {
|
|
144
|
+
await this.trade.setLeverage({
|
|
145
|
+
account: profile.account,
|
|
146
|
+
// Deterministic, so a retried enable reuses the same execution record instead of
|
|
147
|
+
// writing a second one for the same intent.
|
|
148
|
+
executionKey: `live-leverage:${profile.id}:${lever}:${profile.marginMode}`,
|
|
149
|
+
instId: profile.instId,
|
|
150
|
+
lever,
|
|
151
|
+
mgnMode: profile.marginMode
|
|
152
|
+
});
|
|
153
|
+
return { requested: profile.leverage, applied: true };
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
157
|
+
throw new RuntimeError("VALIDATION", `Cannot enable '${profile.name}': setting ${profile.instId} to ${lever}x ${profile.marginMode} was refused (${detail}). Its entry budget is sized against ${lever}x, so trading at whatever the account currently holds would commit a different amount of margin than the budget states.`, false, { instId: profile.instId, leverage: profile.leverage, marginMode: profile.marginMode });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Stops a profile.
|
|
162
|
+
*
|
|
163
|
+
* Always allowed and never precondition-checked: whatever is wrong, stopping is the
|
|
164
|
+
* safe direction, and a stop that could fail would be worse than useless.
|
|
165
|
+
*/
|
|
166
|
+
disable(id, reason) {
|
|
167
|
+
this.store.requireProfile(id);
|
|
168
|
+
const profile = this.store.disableProfile(id, reason ?? null);
|
|
169
|
+
// The row is written first, so a stop takes effect even if releasing the session fails.
|
|
170
|
+
// Releasing promptly matters because a session owns a Python child process; waiting for
|
|
171
|
+
// the next bar would leave it running against a profile that has stopped.
|
|
172
|
+
void this.onDisabled?.(id);
|
|
173
|
+
return { data: profile, meta: resultMeta({ source: "sqlite" }) };
|
|
174
|
+
}
|
|
175
|
+
/** Registers the loop's session release, called after a profile is disabled. */
|
|
176
|
+
onProfileDisabled(release) {
|
|
177
|
+
this.onDisabled = release;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Records a failed evaluation and stops the profile if they keep coming.
|
|
181
|
+
*
|
|
182
|
+
* One failure never stops anything: the risk gates still refuse whatever is unsafe, and
|
|
183
|
+
* halting on a single dropped connection would make the whole thing unusable. A run of
|
|
184
|
+
* failures is different — it means something systemic, and continuing only accumulates
|
|
185
|
+
* errors against an exchange nobody is watching.
|
|
186
|
+
*
|
|
187
|
+
* Only failures that will not fix themselves count. A profile that could not reach OKX
|
|
188
|
+
* for one minute is in a different position from one whose strategy throws on every bar.
|
|
189
|
+
* Transient failures are still recorded, so the minute is never silently dropped; they
|
|
190
|
+
* just do not push toward a shutdown.
|
|
191
|
+
*/
|
|
192
|
+
recordFailure(id, error) {
|
|
193
|
+
const profile = this.store.requireProfile(id);
|
|
194
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
195
|
+
const decision = evaluateStreak({
|
|
196
|
+
error,
|
|
197
|
+
currentStreak: profile.errorStreak,
|
|
198
|
+
limit: this.errorStreakLimit
|
|
199
|
+
});
|
|
200
|
+
if (!decision.counted) {
|
|
201
|
+
// Kept as the profile's last error so a reader can see what happened, without
|
|
202
|
+
// advancing the streak.
|
|
203
|
+
this.store.setLastError(id, message);
|
|
204
|
+
return {
|
|
205
|
+
data: { kind: decision.kind, streak: decision.streak, disabled: false },
|
|
206
|
+
meta: resultMeta({ source: "sqlite" })
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const streak = this.store.recordError(id, message);
|
|
210
|
+
if (streak < this.errorStreakLimit) {
|
|
211
|
+
return { data: { kind: decision.kind, streak, disabled: false }, meta: resultMeta({ source: "sqlite" }) };
|
|
212
|
+
}
|
|
213
|
+
// Disabling also advances the generation, so an action this profile already decided
|
|
214
|
+
// cannot still be submitted.
|
|
215
|
+
this.store.disableProfile(id, describeAutoDisable(streak, message));
|
|
216
|
+
return { data: { kind: decision.kind, streak, disabled: true }, meta: resultMeta({ source: "sqlite" }) };
|
|
217
|
+
}
|
|
218
|
+
/** Clears the streak after an evaluation that worked. */
|
|
219
|
+
recordSuccess(id) {
|
|
220
|
+
// Only touched when there is something to clear: a healthy profile would otherwise
|
|
221
|
+
// write a row on every closed minute for no reason.
|
|
222
|
+
if (this.store.requireProfile(id).errorStreak > 0)
|
|
223
|
+
this.store.clearErrorStreak(id);
|
|
224
|
+
}
|
|
225
|
+
deleteProfile(id) {
|
|
226
|
+
const profile = this.store.requireProfile(id);
|
|
227
|
+
if (profile.enabled) {
|
|
228
|
+
throw new RuntimeError("VALIDATION", `'${profile.name}' is enabled. Stop it before deleting, so its signal history is not removed while it is still trading.`);
|
|
229
|
+
}
|
|
230
|
+
return { data: { deleted: this.store.deleteProfile(id) }, meta: resultMeta({ source: "sqlite" }) };
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Collects everything the preconditions need.
|
|
234
|
+
*
|
|
235
|
+
* Each lookup is guarded on its own: a profile blocked by a missing backtest should
|
|
236
|
+
* still learn that its account is also unverified, and one failing probe must not
|
|
237
|
+
* hide the rest of the list.
|
|
238
|
+
*/
|
|
239
|
+
async gatherFacts(profile) {
|
|
240
|
+
const backtest = this.strategyStore.latestCompletedRun(profile.strategyHash, profile.instId);
|
|
241
|
+
const python = await pythonStatus().catch(() => ({ ready: false, problems: ["Python status could not be read"] }));
|
|
242
|
+
let accountVerified = false;
|
|
243
|
+
let accountPermissions = null;
|
|
244
|
+
try {
|
|
245
|
+
const verified = await this.accounts.verify(profile.account);
|
|
246
|
+
accountVerified = true;
|
|
247
|
+
const permission = verified.data.perm;
|
|
248
|
+
accountPermissions = typeof permission === "string" && permission ? permission : null;
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
// Left unverified; the precondition reports it, and the exact exchange error is
|
|
252
|
+
// not more useful than "OKX did not accept these credentials".
|
|
253
|
+
}
|
|
254
|
+
const contractReadable = await loadContractSpec(this.market, profile.instId).then(() => true, () => false);
|
|
255
|
+
return {
|
|
256
|
+
backtestRunId: backtest?.id ?? null,
|
|
257
|
+
failures: checkPreconditions({
|
|
258
|
+
profile,
|
|
259
|
+
backtestRunId: backtest?.id ?? null,
|
|
260
|
+
pythonReady: python.ready,
|
|
261
|
+
pythonProblems: python.problems,
|
|
262
|
+
accountVerified,
|
|
263
|
+
accountPermissions,
|
|
264
|
+
contractReadable,
|
|
265
|
+
marketEnvironment: this.bars.environment === "demo" ? "demo" : "live"
|
|
266
|
+
})
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Rejects budgets that cannot describe a real position.
|
|
272
|
+
*
|
|
273
|
+
* Checked at creation because these are the numbers every risk gate measures against: a
|
|
274
|
+
* profile whose side budget is below its entry budget would refuse every order it ever
|
|
275
|
+
* decided, which reads as a broken strategy rather than a bad setting.
|
|
276
|
+
*/
|
|
277
|
+
function validateBudgets(input) {
|
|
278
|
+
// An agent must say why. A profile that arrived with no reasoning is one nobody can review
|
|
279
|
+
// later, and reviewing it later is the only check left once it is trading.
|
|
280
|
+
if (input.origin === "agent" && !input.rationale?.trim()) {
|
|
281
|
+
throw new RuntimeError("VALIDATION", "An agent-created profile requires a rationale: which backtest justified it, and why these budgets");
|
|
282
|
+
}
|
|
283
|
+
const positives = [
|
|
284
|
+
["entryBudgetUsdt", input.entryBudgetUsdt],
|
|
285
|
+
["sideBudgetUsdt", input.sideBudgetUsdt],
|
|
286
|
+
["dailyLossLimitUsdt", input.dailyLossLimitUsdt],
|
|
287
|
+
["leverage", input.leverage]
|
|
288
|
+
];
|
|
289
|
+
for (const [field, value] of positives) {
|
|
290
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
291
|
+
throw new RuntimeError("VALIDATION", `${field} must be greater than zero`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (input.sideBudgetUsdt < input.entryBudgetUsdt) {
|
|
295
|
+
throw new RuntimeError("VALIDATION", `sideBudgetUsdt (${input.sideBudgetUsdt}) is below entryBudgetUsdt (${input.entryBudgetUsdt}), so no entry could ever be placed`);
|
|
296
|
+
}
|
|
297
|
+
// Both default to allowed when omitted, matching the store. Reading `undefined` as
|
|
298
|
+
// "not allowed" would reject every profile that simply did not mention direction.
|
|
299
|
+
if (input.allowLong === false && input.allowShort === false) {
|
|
300
|
+
throw new RuntimeError("VALIDATION", "A profile with neither direction allowed can never trade");
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/live/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Bar } from "../bars/types.js";
|
|
2
|
+
export interface LiveSessionOptions {
|
|
3
|
+
interpreter: string;
|
|
4
|
+
engineRoot: string;
|
|
5
|
+
source: string;
|
|
6
|
+
instrument: Record<string, unknown>;
|
|
7
|
+
params: Record<string, unknown>;
|
|
8
|
+
intervals: string[];
|
|
9
|
+
/** Test seam. */
|
|
10
|
+
evaluationTimeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
/** A strategy's intent for one cutoff. Carries no contract count. */
|
|
13
|
+
export interface LiveDecision {
|
|
14
|
+
action: string;
|
|
15
|
+
reason: string;
|
|
16
|
+
asOfMs: number;
|
|
17
|
+
protection?: {
|
|
18
|
+
stopLossPrice?: number;
|
|
19
|
+
takeProfitPrice?: number;
|
|
20
|
+
};
|
|
21
|
+
execution?: {
|
|
22
|
+
orderType: string;
|
|
23
|
+
limitPrice?: number;
|
|
24
|
+
};
|
|
25
|
+
orderId?: string;
|
|
26
|
+
}
|
|
27
|
+
/** The account state a strategy sees, as reported by the exchange. */
|
|
28
|
+
export interface LivePortfolio {
|
|
29
|
+
cashUsdt: number;
|
|
30
|
+
equityUsdt: number;
|
|
31
|
+
usedMarginUsdt: number;
|
|
32
|
+
availableMarginUsdt: number;
|
|
33
|
+
positions: Array<Record<string, unknown>>;
|
|
34
|
+
openOrders: Array<Record<string, unknown>>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Owns one long-lived Python process speaking the `live` protocol.
|
|
38
|
+
*
|
|
39
|
+
* Requests are strictly serialised: one line out, one line back. The protocol has
|
|
40
|
+
* no request ids, so a second concurrent write would make it impossible to tell
|
|
41
|
+
* which reply belonged to which bar — and mismatching a decision to a minute is how
|
|
42
|
+
* a stale intent reaches the exchange.
|
|
43
|
+
*/
|
|
44
|
+
export declare class PythonLiveSession {
|
|
45
|
+
private readonly options;
|
|
46
|
+
private child?;
|
|
47
|
+
private buffer;
|
|
48
|
+
private pending?;
|
|
49
|
+
private queue;
|
|
50
|
+
private stderrText;
|
|
51
|
+
/**
|
|
52
|
+
* Replies still owed by the engine for requests that already timed out.
|
|
53
|
+
*
|
|
54
|
+
* The engine answers every request eventually, in order. A timeout abandons the
|
|
55
|
+
* caller but not the request, so that many arriving replies must be discarded
|
|
56
|
+
* before the stream lines up with whoever is waiting now.
|
|
57
|
+
*/
|
|
58
|
+
private owedReplies;
|
|
59
|
+
private exited?;
|
|
60
|
+
private readonly timeoutMs;
|
|
61
|
+
constructor(options: LiveSessionOptions);
|
|
62
|
+
get running(): boolean;
|
|
63
|
+
/** Starts the process and completes the handshake. */
|
|
64
|
+
start(): Promise<void>;
|
|
65
|
+
/** Feeds history without evaluating it. */
|
|
66
|
+
warm(bars: Bar[]): Promise<number>;
|
|
67
|
+
/**
|
|
68
|
+
* Evaluates one confirmed bar and returns the strategy's intent.
|
|
69
|
+
*
|
|
70
|
+
* `portfolio` must be the exchange's own view, read immediately before this call.
|
|
71
|
+
*/
|
|
72
|
+
evaluate(bar: Bar, portfolio: LivePortfolio): Promise<LiveDecision>;
|
|
73
|
+
/** Asks the process to exit, then makes sure it did. */
|
|
74
|
+
stop(): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Serialises one request/response exchange.
|
|
77
|
+
*
|
|
78
|
+
* Every call is chained onto the previous one, so two callers cannot interleave
|
|
79
|
+
* writes on a protocol that has no request ids.
|
|
80
|
+
*/
|
|
81
|
+
private request;
|
|
82
|
+
private onStdout;
|
|
83
|
+
private fail;
|
|
84
|
+
private describeExit;
|
|
85
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { RuntimeError } from "../core/errors.js";
|
|
4
|
+
/**
|
|
5
|
+
* How long one evaluation may take before it is treated as a strategy fault.
|
|
6
|
+
*
|
|
7
|
+
* A decision is only useful within the minute it was made for, so a strategy that
|
|
8
|
+
* stalls is skipped rather than waited on. The session survives the timeout: the
|
|
9
|
+
* warmed indicator state is still valid for the next minute.
|
|
10
|
+
*/
|
|
11
|
+
const EVALUATION_TIMEOUT_MS = 5_000;
|
|
12
|
+
/** Handshake and warm-up move more data, so they get a longer allowance. */
|
|
13
|
+
const SETUP_TIMEOUT_MS = 60_000;
|
|
14
|
+
/** One reply is a single JSON line; more than this means something is wrong. */
|
|
15
|
+
const MAX_LINE_BYTES = 4 * 1024 * 1024;
|
|
16
|
+
/**
|
|
17
|
+
* Owns one long-lived Python process speaking the `live` protocol.
|
|
18
|
+
*
|
|
19
|
+
* Requests are strictly serialised: one line out, one line back. The protocol has
|
|
20
|
+
* no request ids, so a second concurrent write would make it impossible to tell
|
|
21
|
+
* which reply belonged to which bar — and mismatching a decision to a minute is how
|
|
22
|
+
* a stale intent reaches the exchange.
|
|
23
|
+
*/
|
|
24
|
+
export class PythonLiveSession {
|
|
25
|
+
options;
|
|
26
|
+
child;
|
|
27
|
+
buffer = "";
|
|
28
|
+
pending;
|
|
29
|
+
queue = Promise.resolve();
|
|
30
|
+
stderrText = "";
|
|
31
|
+
/**
|
|
32
|
+
* Replies still owed by the engine for requests that already timed out.
|
|
33
|
+
*
|
|
34
|
+
* The engine answers every request eventually, in order. A timeout abandons the
|
|
35
|
+
* caller but not the request, so that many arriving replies must be discarded
|
|
36
|
+
* before the stream lines up with whoever is waiting now.
|
|
37
|
+
*/
|
|
38
|
+
owedReplies = 0;
|
|
39
|
+
exited;
|
|
40
|
+
timeoutMs;
|
|
41
|
+
constructor(options) {
|
|
42
|
+
this.options = options;
|
|
43
|
+
this.timeoutMs = options.evaluationTimeoutMs ?? EVALUATION_TIMEOUT_MS;
|
|
44
|
+
}
|
|
45
|
+
get running() {
|
|
46
|
+
return this.child !== undefined && this.exited === undefined;
|
|
47
|
+
}
|
|
48
|
+
/** Starts the process and completes the handshake. */
|
|
49
|
+
async start() {
|
|
50
|
+
if (this.child)
|
|
51
|
+
throw new RuntimeError("VALIDATION", "This session is already started");
|
|
52
|
+
const entry = path.join(this.options.engineRoot, "main.py");
|
|
53
|
+
// `-I` ignores PYTHONPATH and the user site directory, so a live strategy
|
|
54
|
+
// cannot be redirected by ambient configuration. `-u` keeps replies flowing
|
|
55
|
+
// instead of sitting in a pipe buffer.
|
|
56
|
+
const child = spawn(this.options.interpreter, ["-I", "-u", entry], {
|
|
57
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
58
|
+
cwd: this.options.engineRoot,
|
|
59
|
+
windowsHide: true
|
|
60
|
+
});
|
|
61
|
+
this.child = child;
|
|
62
|
+
child.stdout.setEncoding("utf8");
|
|
63
|
+
child.stdout.on("data", (chunk) => this.onStdout(chunk));
|
|
64
|
+
child.stderr.setEncoding("utf8");
|
|
65
|
+
child.stderr.on("data", (chunk) => {
|
|
66
|
+
// Kept for diagnostics; an unexpected exit is undiagnosable without it.
|
|
67
|
+
if (this.stderrText.length < 64 * 1024)
|
|
68
|
+
this.stderrText += chunk;
|
|
69
|
+
});
|
|
70
|
+
child.on("error", (error) => this.fail(new RuntimeError("CAPABILITY_UNAVAILABLE", `Could not start the strategy engine: ${error.message}`)));
|
|
71
|
+
child.on("close", (code, signal) => {
|
|
72
|
+
this.exited = { code, signal };
|
|
73
|
+
this.fail(new RuntimeError("INTERNAL", `The strategy engine exited (${this.describeExit()})`));
|
|
74
|
+
});
|
|
75
|
+
child.stdin.on("error", () => {
|
|
76
|
+
// The close handler reports the real cause; a broken pipe here is a symptom.
|
|
77
|
+
});
|
|
78
|
+
const ready = await this.request({
|
|
79
|
+
command: "live",
|
|
80
|
+
source: this.options.source,
|
|
81
|
+
instrument: this.options.instrument,
|
|
82
|
+
params: this.options.params,
|
|
83
|
+
intervals: this.options.intervals
|
|
84
|
+
}, SETUP_TIMEOUT_MS);
|
|
85
|
+
if (ready.ok !== true)
|
|
86
|
+
throw pythonError(ready);
|
|
87
|
+
}
|
|
88
|
+
/** Feeds history without evaluating it. */
|
|
89
|
+
async warm(bars) {
|
|
90
|
+
const response = await this.request({ kind: "warm", bars: bars.map(toPayload) }, SETUP_TIMEOUT_MS);
|
|
91
|
+
if (response.ok !== true)
|
|
92
|
+
throw pythonError(response);
|
|
93
|
+
return Number(response.barCount ?? 0);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Evaluates one confirmed bar and returns the strategy's intent.
|
|
97
|
+
*
|
|
98
|
+
* `portfolio` must be the exchange's own view, read immediately before this call.
|
|
99
|
+
*/
|
|
100
|
+
async evaluate(bar, portfolio) {
|
|
101
|
+
const response = await this.request({ kind: "bar", bar: toPayload(bar), portfolio }, this.timeoutMs);
|
|
102
|
+
if (response.ok !== true)
|
|
103
|
+
throw pythonError(response);
|
|
104
|
+
return {
|
|
105
|
+
action: String(response.action ?? ""),
|
|
106
|
+
reason: String(response.reason ?? ""),
|
|
107
|
+
asOfMs: Number(response.asOfMs ?? bar.closeTimeMs),
|
|
108
|
+
...(response.protection ? { protection: response.protection } : {}),
|
|
109
|
+
...(response.execution ? { execution: response.execution } : {}),
|
|
110
|
+
...(response.orderId ? { orderId: String(response.orderId) } : {})
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/** Asks the process to exit, then makes sure it did. */
|
|
114
|
+
async stop() {
|
|
115
|
+
const child = this.child;
|
|
116
|
+
if (!child || this.exited) {
|
|
117
|
+
this.child = undefined;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
await this.request({ command: "shutdown" }, 2_000);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// A session being stopped because it misbehaved will not answer; the kill
|
|
125
|
+
// below is the point.
|
|
126
|
+
}
|
|
127
|
+
if (this.exited === undefined)
|
|
128
|
+
child.kill("SIGKILL");
|
|
129
|
+
this.child = undefined;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Serialises one request/response exchange.
|
|
133
|
+
*
|
|
134
|
+
* Every call is chained onto the previous one, so two callers cannot interleave
|
|
135
|
+
* writes on a protocol that has no request ids.
|
|
136
|
+
*/
|
|
137
|
+
request(message, timeoutMs) {
|
|
138
|
+
const run = async () => {
|
|
139
|
+
const child = this.child;
|
|
140
|
+
if (!child || this.exited) {
|
|
141
|
+
throw new RuntimeError("INTERNAL", `The strategy engine is not running (${this.describeExit()})`);
|
|
142
|
+
}
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const timer = setTimeout(() => {
|
|
145
|
+
this.pending = undefined;
|
|
146
|
+
// The engine is still working on this request and will answer eventually.
|
|
147
|
+
// That reply now belongs to nobody, so it is counted here and dropped on
|
|
148
|
+
// arrival — handing it to the next caller would pair a decision with a
|
|
149
|
+
// different minute than the one it was computed for.
|
|
150
|
+
this.owedReplies += 1;
|
|
151
|
+
// The process is left alive: its indicator state is still valid, and the
|
|
152
|
+
// caller treats this minute as a skipped evaluation.
|
|
153
|
+
reject(new RuntimeError("INTERNAL", `The strategy took longer than ${timeoutMs}ms to decide`));
|
|
154
|
+
}, timeoutMs);
|
|
155
|
+
timer.unref();
|
|
156
|
+
this.pending = { resolve, reject, timer };
|
|
157
|
+
child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
// Failures must not poison the chain for later calls.
|
|
161
|
+
const chained = this.queue.then(run, run);
|
|
162
|
+
this.queue = chained.then(() => undefined, () => undefined);
|
|
163
|
+
return chained;
|
|
164
|
+
}
|
|
165
|
+
onStdout(chunk) {
|
|
166
|
+
this.buffer += chunk;
|
|
167
|
+
if (this.buffer.length > MAX_LINE_BYTES) {
|
|
168
|
+
this.buffer = "";
|
|
169
|
+
this.fail(new RuntimeError("INTERNAL", "The strategy engine produced an oversized reply"));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
for (;;) {
|
|
173
|
+
const newline = this.buffer.indexOf("\n");
|
|
174
|
+
if (newline === -1)
|
|
175
|
+
return;
|
|
176
|
+
const line = this.buffer.slice(0, newline).trim();
|
|
177
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
178
|
+
if (!line)
|
|
179
|
+
continue;
|
|
180
|
+
// A timed-out request is still owed a reply. Discarding it here — before
|
|
181
|
+
// looking at who is waiting — is what keeps the stream aligned: otherwise the
|
|
182
|
+
// late answer would satisfy the *next* evaluation, pairing a decision with a
|
|
183
|
+
// different minute than the one it was computed for.
|
|
184
|
+
if (this.owedReplies > 0) {
|
|
185
|
+
this.owedReplies -= 1;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const waiting = this.pending;
|
|
189
|
+
// A reply nobody is owed and nobody is waiting for means the stream
|
|
190
|
+
// desynchronised; dropping it is safer than delivering it.
|
|
191
|
+
if (!waiting)
|
|
192
|
+
continue;
|
|
193
|
+
this.pending = undefined;
|
|
194
|
+
clearTimeout(waiting.timer);
|
|
195
|
+
try {
|
|
196
|
+
waiting.resolve(JSON.parse(line));
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
waiting.reject(new RuntimeError("INTERNAL", `The strategy engine returned invalid JSON: ${line.slice(0, 200)}`));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
fail(error) {
|
|
204
|
+
const waiting = this.pending;
|
|
205
|
+
if (!waiting)
|
|
206
|
+
return;
|
|
207
|
+
this.pending = undefined;
|
|
208
|
+
clearTimeout(waiting.timer);
|
|
209
|
+
waiting.reject(error);
|
|
210
|
+
}
|
|
211
|
+
describeExit() {
|
|
212
|
+
if (!this.exited)
|
|
213
|
+
return "still running";
|
|
214
|
+
const detail = this.stderrText.trim().split("\n").at(-1);
|
|
215
|
+
const status = this.exited.signal ? `signal ${this.exited.signal}` : `exit code ${this.exited.code}`;
|
|
216
|
+
return detail ? `${status}: ${detail}` : status;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function toPayload(bar) {
|
|
220
|
+
return {
|
|
221
|
+
openTimeMs: bar.openTimeMs,
|
|
222
|
+
closeTimeMs: bar.closeTimeMs,
|
|
223
|
+
open: Number(bar.open),
|
|
224
|
+
high: Number(bar.high),
|
|
225
|
+
low: Number(bar.low),
|
|
226
|
+
close: Number(bar.close),
|
|
227
|
+
volume: Number(bar.volume)
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function pythonError(response) {
|
|
231
|
+
const error = (response.error ?? {});
|
|
232
|
+
return new RuntimeError(error.code === "policy_violation" ? "VALIDATION" : "INTERNAL", error.message ?? "The strategy engine reported an error", false, error.violations ? { violations: error.violations } : undefined);
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/live/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAuC,MAAM,oBAAoB,CAAC;AAChF,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD;;;;;;GAMG;AACH,MAAM,qBAAqB,GAAG,KAAK,CAAC;AACpC,4EAA4E;AAC5E,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAChC,gFAAgF;AAChF,MAAM,cAAc,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAiCvC;;;;;;;GAOG;AACH,MAAM,OAAO,iBAAiB;IAiBC;IAhBrB,KAAK,CAAkC;IACvC,MAAM,GAAG,EAAE,CAAC;IACZ,OAAO,CAAgH;IACvH,KAAK,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAC5C,UAAU,GAAG,EAAE,CAAC;IACxB;;;;;;OAMG;IACK,WAAW,GAAG,CAAC,CAAC;IAChB,MAAM,CAA0D;IACvD,SAAS,CAAS;IAEnC,YAA6B,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;QACtD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,mBAAmB,IAAI,qBAAqB,CAAC;IACxE,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC;IAC/D,CAAC;IAED,sDAAsD;IACtD,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,YAAY,CAAC,YAAY,EAAE,iCAAiC,CAAC,CAAC;QACxF,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QAC5D,0EAA0E;QAC1E,4EAA4E;QAC5E,uCAAuC;QACvC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE;YACjE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;YAC/B,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU;YAC5B,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QAEnB,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACjC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QACjE,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QACjC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YACxC,wEAAwE;YACxE,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,EAAE,GAAG,IAAI;gBAAE,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC;QACnE,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,wBAAwB,EAAE,wCAAwC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7I,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YACjC,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;YAC/B,IAAI,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,UAAU,EAAE,+BAA+B,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,CAAC;QACjG,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAC3B,6EAA6E;QAC/E,CAAC,CAAC,CAAC;QAEH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAC9B;YACE,OAAO,EAAE,MAAM;YACf,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;YAC3B,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU;YACnC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;YAC3B,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS;SAClC,EACD,gBAAgB,CACjB,CAAC;QACF,IAAI,KAAK,CAAC,EAAE,KAAK,IAAI;YAAE,MAAM,WAAW,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC;IAED,2CAA2C;IAC3C,KAAK,CAAC,IAAI,CAAC,IAAW;QACpB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;QACnG,IAAI,QAAQ,CAAC,EAAE,KAAK,IAAI;YAAE,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAC;QACtD,OAAO,MAAM,CAAC,QAAQ,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAC;IACxC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,QAAQ,CAAC,GAAQ,EAAE,SAAwB;QAC/C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACrG,IAAI,QAAQ,CAAC,EAAE,KAAK,IAAI;YAAE,MAAM,WAAW,CAAC,QAAQ,CAAC,CAAC;QACtD,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;YACrC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;YACrC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,IAAI,GAAG,CAAC,WAAW,CAAC;YAClD,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,QAAQ,CAAC,UAAwC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjG,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,SAAsC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7F,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACnE,CAAC;IACJ,CAAC;IAED,wDAAwD;IACxD,KAAK,CAAC,IAAI;QACR,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;YACvB,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,KAAK,CAAC,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACP,0EAA0E;YAC1E,sBAAsB;QACxB,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrD,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACK,OAAO,CAAC,OAAgB,EAAE,SAAiB;QACjD,MAAM,GAAG,GAAG,KAAK,IAAsC,EAAE;YACvD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;YACzB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAC1B,MAAM,IAAI,YAAY,CAAC,UAAU,EAAE,uCAAuC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;YACpG,CAAC;YACD,OAAO,IAAI,OAAO,CAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC9D,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;oBAC5B,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;oBACzB,0EAA0E;oBAC1E,yEAAyE;oBACzE,uEAAuE;oBACvE,qDAAqD;oBACrD,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC;oBACtB,yEAAyE;oBACzE,qDAAqD;oBACrD,MAAM,CAAC,IAAI,YAAY,CAAC,UAAU,EAAE,iCAAiC,SAAS,cAAc,CAAC,CAAC,CAAC;gBACjG,CAAC,EAAE,SAAS,CAAC,CAAC;gBACd,KAAK,CAAC,KAAK,EAAE,CAAC;gBACd,IAAI,CAAC,OAAO,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;gBAC1C,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACpD,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QACF,sDAAsD;QACtD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,IAAI,CACvB,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;QACF,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,QAAQ,CAAC,KAAa;QAC5B,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC;QACrB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;YACxC,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,UAAU,EAAE,iDAAiD,CAAC,CAAC,CAAC;YAC3F,OAAO;QACT,CAAC;QACD,SAAS,CAAC;YACR,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1C,IAAI,OAAO,KAAK,CAAC,CAAC;gBAAE,OAAO;YAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;YAClD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;YAC7C,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,yEAAyE;YACzE,8EAA8E;YAC9E,6EAA6E;YAC7E,qDAAqD;YACrD,IAAI,IAAI,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;gBACzB,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC;gBACtB,SAAS;YACX,CAAC;YACD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;YAC7B,oEAAoE;YACpE,2DAA2D;YAC3D,IAAI,CAAC,OAAO;gBAAE,SAAS;YACvB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;YACzB,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,IAAI,CAAC;gBACH,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAA4B,CAAC,CAAC;YAC/D,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,UAAU,EAAE,8CAA8C,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YACnH,CAAC;QACH,CAAC;IACH,CAAC;IAEO,IAAI,CAAC,KAAY;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAC7B,IAAI,CAAC,OAAO;YAAE,OAAO;QACrB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,eAAe,CAAC;QACzC,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACrG,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IAClD,CAAC;CACF;AAED,SAAS,SAAS,CAAC,GAAQ;IACzB,OAAO;QACL,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,WAAW,EAAE,GAAG,CAAC,WAAW;QAC5B,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QACtB,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QACtB,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;QACpB,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC;KAC3B,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,QAAiC;IACpD,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAA8D,CAAC;IAClG,OAAO,IAAI,YAAY,CACrB,KAAK,CAAC,IAAI,KAAK,kBAAkB,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,EAC7D,KAAK,CAAC,OAAO,IAAI,uCAAuC,EACxD,KAAK,EACL,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,SAAS,CAChE,CAAC;AACJ,CAAC"}
|