bandkit 1.0.2 → 1.0.3

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.md CHANGED
@@ -10,6 +10,7 @@ The package ships:
10
10
  - Live market widgets: `<OrderBookWidget />`, `<TradeTape />`
11
11
  - On-chain feed: `<ContractEventFeed />` (watches your deployed strategy contract's events)
12
12
  - Context badges: `<RegimeBadge />` (volatility regime), `<GasBadge />` (mainnet gas price)
13
+ - Decision proxy: `<BotDecisionPanel />` — live BUY/SELL/WAIT/STAND-DOWN preview from Binance + Coinbase data (preview only, not the on-chain bot)
13
14
  - A dark UI throughout
14
15
 
15
16
  It does **not** ship trading strategy logic, signal generation, risk management, or the off-chain trade-execution daemon — those are out of scope.
@@ -47,19 +48,23 @@ max-width 1100px, gap 20px, system font stack:
47
48
  │ │
48
49
  ├──────────────────────┬──────────────────────────────┤
49
50
  │ │ │
50
- │ <BandPanel /> │ <OrderBookWidget /> │ ← two-column split
51
+ │ <BandPanel /> │ <BotDecisionPanel /> │ ← two-column split
51
52
  │ (the bot UI: it │ │ on >=900px wide,
52
53
  │ has its own deploy,├──────────────────────────────┤ stacked below
53
- │ fund, start, stop, │ <TradeTape />
54
+ │ fund, start, stop, │ <OrderBookWidget />
54
55
  │ withdraw buttons. │ │
55
56
  │ Default title is ├──────────────────────────────┤
56
- │ "Get started") │ <ContractEventFeed
57
+ │ "Get started") │ <TradeTape />
58
+ │ │ │
59
+ │ ├──────────────────────────────┤
60
+ │ │ <ContractEventFeed │
57
61
  │ │ strategyContractAddress= │
58
62
  │ │ {deployedAddress} /> │
59
63
  └──────────────────────┴──────────────────────────────┘
60
64
 
61
65
  Components to import from bandkit:
62
66
  - BandPanel (the main staged UI)
67
+ - BotDecisionPanel (live BUY/SELL/WAIT decision preview — top of right column)
63
68
  - OrderBookWidget (live Binance ETH/USDT depth)
64
69
  - TradeTape (live recent trades)
65
70
  - ContractEventFeed (on-chain events from the deployed strategy)
@@ -224,6 +229,25 @@ type RegimeBadgeProps = {
224
229
  };
225
230
  ```
226
231
 
232
+ ### `<BotDecisionPanel />`
233
+
234
+ Live decision proxy. Combines Binance order book imbalance + recent trade flow + volatility regime + Coinbase cross-venue divergence into a `BUY` / `SELL` / `WAIT` / `STAND DOWN` decision with a 0–100 confidence score and human-readable reasons. **Preview only — does not execute trades.**
235
+
236
+ ```ts
237
+ type BotDecisionPanelProps = {
238
+ className?: string;
239
+ title?: string; // default: "Bot Decision (live)"
240
+ evaluationIntervalMs?: number; // default: 2000
241
+ flowWindowMs?: number; // default: 60000
242
+ historyLimit?: number; // default: 30
243
+ thresholds?: Partial<DecisionThresholds>; // override imbalance/flow/spread/divergence/depth cutoffs
244
+ enabled?: boolean;
245
+ // plus sub-options: orderBook, trades, coinbase, regime
246
+ };
247
+ ```
248
+
249
+ The pure decision function `computeBotDecision()` is also exported, so you can re-use the same logic in a Node script or backtest without React.
250
+
227
251
  ### `<GasBadge />`
228
252
 
229
253
  Current mainnet gas price, color-coded by tier (low/normal/high/spike).
@@ -0,0 +1,7 @@
1
+ import { type UseBotDecisionOptions } from "./useBotDecision.js";
2
+ export type BotDecisionPanelProps = UseBotDecisionOptions & {
3
+ className?: string;
4
+ title?: string;
5
+ };
6
+ export declare function BotDecisionPanel({ className, title, ...hookOptions }: BotDecisionPanelProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=BotDecisionPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BotDecisionPanel.d.ts","sourceRoot":"","sources":["../src/BotDecisionPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAGjF,MAAM,MAAM,qBAAqB,GAAG,qBAAqB,GAAG;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAsCF,wBAAgB,gBAAgB,CAAC,EAAE,SAAS,EAAE,KAA6B,EAAE,GAAG,WAAW,EAAE,EAAE,qBAAqB,2CA2HnH"}
@@ -0,0 +1,124 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useBotDecision } from "./useBotDecision.js";
3
+ const palette = {
4
+ bg: "#0a0a0a",
5
+ surface: "#111113",
6
+ surfaceMuted: "#0f0f11",
7
+ border: "#1f1f23",
8
+ text: "#f5f5f7",
9
+ textMuted: "#9ca3af",
10
+ textDim: "#52525b",
11
+ buy: "#10b981",
12
+ sell: "#ef4444",
13
+ wait: "#fbbf24",
14
+ standdown: "#6b7280",
15
+ good: "#10b981",
16
+ bad: "#ef4444",
17
+ neutral: "#9ca3af"
18
+ };
19
+ const decisionMeta = {
20
+ BUY: { label: "WOULD BUY", color: palette.buy, arrow: "▲" },
21
+ SELL: { label: "WOULD SELL", color: palette.sell, arrow: "▼" },
22
+ WAIT: { label: "WAIT", color: palette.wait, arrow: "•" },
23
+ "STAND DOWN": { label: "STAND DOWN", color: palette.standdown, arrow: "■" }
24
+ };
25
+ const reasonColor = {
26
+ good: palette.good,
27
+ bad: palette.bad,
28
+ neutral: palette.neutral
29
+ };
30
+ const reasonSymbol = {
31
+ good: "✓",
32
+ bad: "✗",
33
+ neutral: "•"
34
+ };
35
+ export function BotDecisionPanel({ className, title = "Bot Decision (live)", ...hookOptions }) {
36
+ const result = useBotDecision(hookOptions);
37
+ const meta = decisionMeta[result.decision];
38
+ const recent = result.history.slice(0, 20);
39
+ const tally = recent.reduce((acc, h) => {
40
+ acc[h.decision] = (acc[h.decision] ?? 0) + 1;
41
+ return acc;
42
+ }, { BUY: 0, SELL: 0, WAIT: 0, "STAND DOWN": 0 });
43
+ return (_jsxs("section", { className: className, style: {
44
+ background: palette.bg,
45
+ border: `1px solid ${palette.border}`,
46
+ borderRadius: 14,
47
+ boxShadow: "0 20px 60px rgba(0, 0, 0, 0.55)",
48
+ color: palette.text,
49
+ display: "grid",
50
+ fontFamily: "system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif",
51
+ gap: 14,
52
+ maxWidth: 420,
53
+ padding: 18
54
+ }, children: [_jsxs("header", { style: { alignItems: "center", display: "flex", justifyContent: "space-between" }, children: [_jsx("h3", { style: { fontSize: 14, fontWeight: 700, letterSpacing: "0.02em", margin: 0 }, children: title }), _jsx("span", { style: { color: palette.textDim, fontSize: 11 }, children: result.evaluatedAt ? `updated ${timeAgoShort(result.evaluatedAt.getTime())}` : "warming up" })] }), _jsxs("div", { style: {
55
+ alignItems: "center",
56
+ background: palette.surface,
57
+ border: `1px solid ${meta.color}55`,
58
+ borderRadius: 10,
59
+ display: "flex",
60
+ gap: 12,
61
+ padding: "12px 14px"
62
+ }, children: [_jsx("span", { "aria-hidden": true, style: {
63
+ color: meta.color,
64
+ fontSize: 22,
65
+ fontWeight: 800,
66
+ lineHeight: 1,
67
+ width: 22
68
+ }, children: meta.arrow }), _jsxs("div", { style: { display: "grid", flex: 1, gap: 2 }, children: [_jsx("strong", { style: { color: meta.color, fontSize: 15, fontWeight: 700, letterSpacing: "0.04em" }, children: meta.label }), _jsxs("span", { style: { color: palette.textMuted, fontSize: 11 }, children: ["confidence ", result.confidence, "%"] })] }), _jsx(ConfidenceBar, { value: result.confidence, color: meta.color })] }), _jsx("ul", { style: { display: "grid", gap: 4, listStyle: "none", margin: 0, padding: 0 }, children: result.reasons.map((reason, idx) => (_jsxs("li", { style: {
69
+ alignItems: "baseline",
70
+ color: palette.text,
71
+ display: "grid",
72
+ fontSize: 12,
73
+ gap: 8,
74
+ gridTemplateColumns: "12px 1fr"
75
+ }, children: [_jsx("span", { style: { color: reasonColor[reason.tone] }, children: reasonSymbol[reason.tone] }), _jsx("span", { style: { color: reason.tone === "bad" ? palette.textMuted : palette.text }, children: reason.text })] }, `${reason.text}-${idx}`))) }), _jsxs("div", { style: {
76
+ alignItems: "center",
77
+ background: palette.surfaceMuted,
78
+ border: `1px solid ${palette.border}`,
79
+ borderRadius: 8,
80
+ color: palette.textMuted,
81
+ display: "flex",
82
+ fontSize: 11,
83
+ fontVariantNumeric: "tabular-nums",
84
+ gap: 10,
85
+ justifyContent: "space-between",
86
+ padding: "8px 12px"
87
+ }, children: [_jsxs("span", { children: ["Last ", recent.length, ":"] }), _jsx(Pill, { label: "BUY", count: tally.BUY, color: palette.buy }), _jsx(Pill, { label: "WAIT", count: tally.WAIT, color: palette.wait }), _jsx(Pill, { label: "SELL", count: tally.SELL, color: palette.sell }), _jsx(Pill, { label: "OFF", count: tally["STAND DOWN"], color: palette.standdown })] }), _jsx("div", { style: {
88
+ color: palette.textDim,
89
+ fontSize: 10,
90
+ lineHeight: 1.4
91
+ }, children: "Preview only. Decisions here describe what the strategy would do given live Binance + Coinbase data \u2014 they do not execute on-chain trades. The actual bot runs as a separate Node process against Uniswap." })] }));
92
+ }
93
+ function Pill({ label, count, color }) {
94
+ return (_jsxs("span", { style: {
95
+ alignItems: "center",
96
+ background: `${color}1f`,
97
+ border: `1px solid ${color}55`,
98
+ borderRadius: 6,
99
+ color,
100
+ display: "inline-flex",
101
+ fontSize: 10,
102
+ fontWeight: 700,
103
+ gap: 4,
104
+ padding: "2px 8px"
105
+ }, children: [label, " ", _jsx("strong", { children: count })] }));
106
+ }
107
+ function ConfidenceBar({ value, color }) {
108
+ const safe = Math.max(0, Math.min(100, value));
109
+ return (_jsx("div", { "aria-hidden": true, style: {
110
+ background: palette.surfaceMuted,
111
+ border: `1px solid ${palette.border}`,
112
+ borderRadius: 6,
113
+ height: 6,
114
+ overflow: "hidden",
115
+ width: 72
116
+ }, children: _jsx("div", { style: { background: color, height: "100%", width: `${safe}%` } }) }));
117
+ }
118
+ function timeAgoShort(ts) {
119
+ const seconds = Math.max(0, (Date.now() - ts) / 1000);
120
+ if (seconds < 60)
121
+ return `${seconds.toFixed(0)}s ago`;
122
+ return `${Math.floor(seconds / 60)}m ago`;
123
+ }
124
+ //# sourceMappingURL=BotDecisionPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BotDecisionPanel.js","sourceRoot":"","sources":["../src/BotDecisionPanel.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { Regime } from "./useVolatilityRegime.js";
2
+ export type BotDecision = "BUY" | "SELL" | "WAIT" | "STAND DOWN";
3
+ export type DecisionReason = {
4
+ text: string;
5
+ tone: "good" | "bad" | "neutral";
6
+ };
7
+ export type DecisionInputs = {
8
+ regime: Regime;
9
+ bookImbalance: number;
10
+ tradeImbalance: number;
11
+ spreadPct: number;
12
+ venueDivergenceBps: number | null;
13
+ bookDepthWithinHalfPctEth: number;
14
+ midPrice: number | null;
15
+ };
16
+ export type DecisionResult = {
17
+ decision: BotDecision;
18
+ confidence: number;
19
+ reasons: DecisionReason[];
20
+ };
21
+ export type DecisionThresholds = {
22
+ imbalanceThreshold: number;
23
+ flowThreshold: number;
24
+ spreadWidePct: number;
25
+ divergenceBps: number;
26
+ minDepthEth: number;
27
+ };
28
+ export declare const DEFAULT_DECISION_THRESHOLDS: DecisionThresholds;
29
+ export declare function computeBotDecision(inputs: DecisionInputs, thresholds?: DecisionThresholds): DecisionResult;
30
+ export type BookLevel = {
31
+ price: number;
32
+ size: number;
33
+ };
34
+ export declare function computeBookImbalance(bids: BookLevel[], asks: BookLevel[], midPrice: number, withinPct?: number): number;
35
+ export declare function computeBookDepth(bids: BookLevel[], asks: BookLevel[], midPrice: number, withinPct?: number): number;
36
+ export declare function computeTradeImbalance(trades: {
37
+ side: "buy" | "sell";
38
+ size: number;
39
+ timestamp: number;
40
+ }[], windowMs?: number, nowMs?: number): number;
41
+ //# sourceMappingURL=decisionEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decisionEngine.d.ts","sourceRoot":"","sources":["../src/decisionEngine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAEvD,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,YAAY,CAAC;AAEjE,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;CAClC,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,yBAAyB,EAAE,MAAM,CAAC;IAClC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,WAAW,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,2BAA2B,EAAE,kBAMzC,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,cAAc,EACtB,UAAU,GAAE,kBAAgD,GAC3D,cAAc,CA2HhB;AAKD,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAExD,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,SAAS,EAAE,EACjB,IAAI,EAAE,SAAS,EAAE,EACjB,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAQ,GAChB,MAAM,CASR;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,SAAS,EAAE,EACjB,IAAI,EAAE,SAAS,EAAE,EACjB,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAQ,GAChB,MAAM,CAMR;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE;IAAE,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,EAAE,EACnE,QAAQ,SAAS,EACjB,KAAK,SAAa,GACjB,MAAM,CAYR"}
@@ -0,0 +1,166 @@
1
+ export const DEFAULT_DECISION_THRESHOLDS = {
2
+ imbalanceThreshold: 0.15,
3
+ flowThreshold: 0.1,
4
+ spreadWidePct: 0.05,
5
+ divergenceBps: 5,
6
+ minDepthEth: 50
7
+ };
8
+ export function computeBotDecision(inputs, thresholds = DEFAULT_DECISION_THRESHOLDS) {
9
+ const reasons = [];
10
+ // Regime gate
11
+ if (inputs.regime === "UNKNOWN") {
12
+ return {
13
+ decision: "WAIT",
14
+ confidence: 0,
15
+ reasons: [{ text: "Volatility classifier still warming up", tone: "neutral" }]
16
+ };
17
+ }
18
+ if (inputs.regime === "TRENDING") {
19
+ return {
20
+ decision: "STAND DOWN",
21
+ confidence: 90,
22
+ reasons: [
23
+ { text: "Regime: TRENDING — range bot sits out", tone: "bad" },
24
+ { text: "Re-engages when price stabilises in a range", tone: "neutral" }
25
+ ]
26
+ };
27
+ }
28
+ let scoreBuy = 0;
29
+ let scoreSell = 0;
30
+ let confidence = inputs.regime === "RANGING" ? 45 : 30;
31
+ if (inputs.regime === "RANGING") {
32
+ reasons.push({ text: "Regime: RANGING (favourable)", tone: "good" });
33
+ }
34
+ else {
35
+ reasons.push({ text: "Regime: MIXED", tone: "neutral" });
36
+ }
37
+ // Book imbalance
38
+ if (inputs.bookImbalance >= thresholds.imbalanceThreshold) {
39
+ const pct = inputs.bookImbalance * 100;
40
+ scoreBuy += 25;
41
+ reasons.push({ text: `Book imbalance +${pct.toFixed(0)}% bid-heavy`, tone: "good" });
42
+ }
43
+ else if (inputs.bookImbalance <= -thresholds.imbalanceThreshold) {
44
+ const pct = inputs.bookImbalance * 100;
45
+ scoreSell += 25;
46
+ reasons.push({ text: `Book imbalance ${pct.toFixed(0)}% ask-heavy`, tone: "good" });
47
+ }
48
+ else {
49
+ reasons.push({
50
+ text: `Book imbalance balanced (${(inputs.bookImbalance * 100).toFixed(0)}%)`,
51
+ tone: "neutral"
52
+ });
53
+ }
54
+ // Trade flow (last 60s)
55
+ if (inputs.tradeImbalance >= thresholds.flowThreshold) {
56
+ const buyPct = (0.5 + inputs.tradeImbalance / 2) * 100;
57
+ scoreBuy += 20;
58
+ reasons.push({ text: `Recent flow ${buyPct.toFixed(0)}% buys`, tone: "good" });
59
+ }
60
+ else if (inputs.tradeImbalance <= -thresholds.flowThreshold) {
61
+ const sellPct = (0.5 - inputs.tradeImbalance / 2) * 100;
62
+ scoreSell += 20;
63
+ reasons.push({ text: `Recent flow ${sellPct.toFixed(0)}% sells`, tone: "good" });
64
+ }
65
+ else {
66
+ reasons.push({ text: "Recent flow balanced", tone: "neutral" });
67
+ }
68
+ // Spread
69
+ if (inputs.spreadPct > thresholds.spreadWidePct) {
70
+ confidence -= 10;
71
+ reasons.push({ text: `Spread wide (${inputs.spreadPct.toFixed(3)}%)`, tone: "bad" });
72
+ }
73
+ else {
74
+ confidence += 5;
75
+ reasons.push({ text: `Spread tight (${inputs.spreadPct.toFixed(3)}%)`, tone: "good" });
76
+ }
77
+ // Cross-venue divergence
78
+ if (inputs.venueDivergenceBps !== null && Number.isFinite(inputs.venueDivergenceBps)) {
79
+ if (inputs.venueDivergenceBps > thresholds.divergenceBps) {
80
+ scoreBuy += 12;
81
+ reasons.push({
82
+ text: `Binance leading +${inputs.venueDivergenceBps.toFixed(1)} bps vs Coinbase`,
83
+ tone: "good"
84
+ });
85
+ }
86
+ else if (inputs.venueDivergenceBps < -thresholds.divergenceBps) {
87
+ scoreSell += 12;
88
+ reasons.push({
89
+ text: `Binance lagging ${inputs.venueDivergenceBps.toFixed(1)} bps vs Coinbase`,
90
+ tone: "good"
91
+ });
92
+ }
93
+ else {
94
+ reasons.push({
95
+ text: `Venue divergence ${inputs.venueDivergenceBps.toFixed(1)} bps (aligned)`,
96
+ tone: "neutral"
97
+ });
98
+ }
99
+ }
100
+ // Depth check
101
+ if (inputs.bookDepthWithinHalfPctEth < thresholds.minDepthEth) {
102
+ confidence -= 15;
103
+ reasons.push({
104
+ text: `Thin book — ${inputs.bookDepthWithinHalfPctEth.toFixed(0)} ETH within ±0.5%`,
105
+ tone: "bad"
106
+ });
107
+ }
108
+ else {
109
+ confidence += 5;
110
+ reasons.push({
111
+ text: `Book depth healthy (${inputs.bookDepthWithinHalfPctEth.toFixed(0)} ETH within ±0.5%)`,
112
+ tone: "good"
113
+ });
114
+ }
115
+ let decision;
116
+ let finalConfidence = confidence;
117
+ if (scoreBuy > scoreSell + 12) {
118
+ decision = "BUY";
119
+ finalConfidence = Math.min(100, Math.max(0, confidence + scoreBuy));
120
+ }
121
+ else if (scoreSell > scoreBuy + 12) {
122
+ decision = "SELL";
123
+ finalConfidence = Math.min(100, Math.max(0, confidence + scoreSell));
124
+ }
125
+ else {
126
+ decision = "WAIT";
127
+ finalConfidence = Math.min(100, Math.max(0, confidence + Math.max(scoreBuy, scoreSell) / 2));
128
+ reasons.push({ text: "Signals mixed — no clear direction", tone: "neutral" });
129
+ }
130
+ return { decision, confidence: finalConfidence, reasons };
131
+ }
132
+ export function computeBookImbalance(bids, asks, midPrice, withinPct = 0.005) {
133
+ const lowerBound = midPrice * (1 - withinPct);
134
+ const upperBound = midPrice * (1 + withinPct);
135
+ const bidSize = bids.filter((b) => b.price >= lowerBound).reduce((sum, b) => sum + b.size, 0);
136
+ const askSize = asks.filter((a) => a.price <= upperBound).reduce((sum, a) => sum + a.size, 0);
137
+ const total = bidSize + askSize;
138
+ if (total <= 0)
139
+ return 0;
140
+ return (bidSize - askSize) / total;
141
+ }
142
+ export function computeBookDepth(bids, asks, midPrice, withinPct = 0.005) {
143
+ const lowerBound = midPrice * (1 - withinPct);
144
+ const upperBound = midPrice * (1 + withinPct);
145
+ const bidSize = bids.filter((b) => b.price >= lowerBound).reduce((sum, b) => sum + b.size, 0);
146
+ const askSize = asks.filter((a) => a.price <= upperBound).reduce((sum, a) => sum + a.size, 0);
147
+ return bidSize + askSize;
148
+ }
149
+ export function computeTradeImbalance(trades, windowMs = 60_000, nowMs = Date.now()) {
150
+ const cutoff = nowMs - windowMs;
151
+ let buyVolume = 0;
152
+ let sellVolume = 0;
153
+ for (const t of trades) {
154
+ if (t.timestamp < cutoff)
155
+ continue;
156
+ if (t.side === "buy")
157
+ buyVolume += t.size;
158
+ else
159
+ sellVolume += t.size;
160
+ }
161
+ const total = buyVolume + sellVolume;
162
+ if (total <= 0)
163
+ return 0;
164
+ return (buyVolume - sellVolume) / total;
165
+ }
166
+ //# sourceMappingURL=decisionEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decisionEngine.js","sourceRoot":"","sources":["../src/decisionEngine.ts"],"names":[],"mappings":"AAiCA,MAAM,CAAC,MAAM,2BAA2B,GAAuB;IAC7D,kBAAkB,EAAE,IAAI;IACxB,aAAa,EAAE,GAAG;IAClB,aAAa,EAAE,IAAI;IACnB,aAAa,EAAE,CAAC;IAChB,WAAW,EAAE,EAAE;CAChB,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAChC,MAAsB,EACtB,aAAiC,2BAA2B;IAE5D,MAAM,OAAO,GAAqB,EAAE,CAAC;IAErC,cAAc;IACd,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO;YACL,QAAQ,EAAE,MAAM;YAChB,UAAU,EAAE,CAAC;YACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,wCAAwC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;SAC/E,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,OAAO;YACL,QAAQ,EAAE,YAAY;YACtB,UAAU,EAAE,EAAE;YACd,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,uCAAuC,EAAE,IAAI,EAAE,KAAK,EAAE;gBAC9D,EAAE,IAAI,EAAE,6CAA6C,EAAE,IAAI,EAAE,SAAS,EAAE;aACzE;SACF,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,UAAU,GAAG,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvD,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,8BAA8B,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACvE,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,iBAAiB;IACjB,IAAI,MAAM,CAAC,aAAa,IAAI,UAAU,CAAC,kBAAkB,EAAE,CAAC;QAC1D,MAAM,GAAG,GAAG,MAAM,CAAC,aAAa,GAAG,GAAG,CAAC;QACvC,QAAQ,IAAI,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,mBAAmB,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACvF,CAAC;SAAM,IAAI,MAAM,CAAC,aAAa,IAAI,CAAC,UAAU,CAAC,kBAAkB,EAAE,CAAC;QAClE,MAAM,GAAG,GAAG,MAAM,CAAC,aAAa,GAAG,GAAG,CAAC;QACvC,SAAS,IAAI,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,kBAAkB,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACtF,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,4BAA4B,CAAC,MAAM,CAAC,aAAa,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;YAC7E,IAAI,EAAE,SAAS;SAChB,CAAC,CAAC;IACL,CAAC;IAED,wBAAwB;IACxB,IAAI,MAAM,CAAC,cAAc,IAAI,UAAU,CAAC,aAAa,EAAE,CAAC;QACtD,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,MAAM,CAAC,cAAc,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;QACvD,QAAQ,IAAI,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,eAAe,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACjF,CAAC;SAAM,IAAI,MAAM,CAAC,cAAc,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC;QAC9D,MAAM,OAAO,GAAG,CAAC,GAAG,GAAG,MAAM,CAAC,cAAc,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;QACxD,SAAS,IAAI,EAAE,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,eAAe,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACnF,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,sBAAsB,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,SAAS;IACT,IAAI,MAAM,CAAC,SAAS,GAAG,UAAU,CAAC,aAAa,EAAE,CAAC;QAChD,UAAU,IAAI,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,gBAAgB,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACvF,CAAC;SAAM,CAAC;QACN,UAAU,IAAI,CAAC,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IACzF,CAAC;IAED,yBAAyB;IACzB,IAAI,MAAM,CAAC,kBAAkB,KAAK,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACrF,IAAI,MAAM,CAAC,kBAAkB,GAAG,UAAU,CAAC,aAAa,EAAE,CAAC;YACzD,QAAQ,IAAI,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,oBAAoB,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB;gBAChF,IAAI,EAAE,MAAM;aACb,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,MAAM,CAAC,kBAAkB,GAAG,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC;YACjE,SAAS,IAAI,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,mBAAmB,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,kBAAkB;gBAC/E,IAAI,EAAE,MAAM;aACb,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,oBAAoB,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB;gBAC9E,IAAI,EAAE,SAAS;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,cAAc;IACd,IAAI,MAAM,CAAC,yBAAyB,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;QAC9D,UAAU,IAAI,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,eAAe,MAAM,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAmB;YACnF,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,UAAU,IAAI,CAAC,CAAC;QAChB,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,uBAAuB,MAAM,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB;YAC5F,IAAI,EAAE,MAAM;SACb,CAAC,CAAC;IACL,CAAC;IAED,IAAI,QAAqB,CAAC;IAC1B,IAAI,eAAe,GAAG,UAAU,CAAC;IAEjC,IAAI,QAAQ,GAAG,SAAS,GAAG,EAAE,EAAE,CAAC;QAC9B,QAAQ,GAAG,KAAK,CAAC;QACjB,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,QAAQ,CAAC,CAAC,CAAC;IACtE,CAAC;SAAM,IAAI,SAAS,GAAG,QAAQ,GAAG,EAAE,EAAE,CAAC;QACrC,QAAQ,GAAG,MAAM,CAAC;QAClB,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC;IACvE,CAAC;SAAM,CAAC;QACN,QAAQ,GAAG,MAAM,CAAC;QAClB,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC7F,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,oCAAoC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IAChF,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC;AAC5D,CAAC;AAOD,MAAM,UAAU,oBAAoB,CAClC,IAAiB,EACjB,IAAiB,EACjB,QAAgB,EAChB,SAAS,GAAG,KAAK;IAEjB,MAAM,UAAU,GAAG,QAAQ,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,QAAQ,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9F,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9F,MAAM,KAAK,GAAG,OAAO,GAAG,OAAO,CAAC;IAChC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IACzB,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,KAAK,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,IAAiB,EACjB,IAAiB,EACjB,QAAgB,EAChB,SAAS,GAAG,KAAK;IAEjB,MAAM,UAAU,GAAG,QAAQ,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC9C,MAAM,UAAU,GAAG,QAAQ,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9F,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9F,OAAO,OAAO,GAAG,OAAO,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,MAAmE,EACnE,QAAQ,GAAG,MAAM,EACjB,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE;IAElB,MAAM,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAChC,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,IAAI,CAAC,CAAC,SAAS,GAAG,MAAM;YAAE,SAAS;QACnC,IAAI,CAAC,CAAC,IAAI,KAAK,KAAK;YAAE,SAAS,IAAI,CAAC,CAAC,IAAI,CAAC;;YACrC,UAAU,IAAI,CAAC,CAAC,IAAI,CAAC;IAC5B,CAAC;IACD,MAAM,KAAK,GAAG,SAAS,GAAG,UAAU,CAAC;IACrC,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IACzB,OAAO,CAAC,SAAS,GAAG,UAAU,CAAC,GAAG,KAAK,CAAC;AAC1C,CAAC"}
package/dist/index.d.ts CHANGED
@@ -21,6 +21,10 @@ export { useVolatilityRegime, type Regime, type UseVolatilityRegimeOptions, type
21
21
  export { RegimeBadge, type RegimeBadgeProps } from "./RegimeBadge.js";
22
22
  export { useGasPrice, type GasTier, type UseGasPriceOptions, type UseGasPriceResult } from "./useGasPrice.js";
23
23
  export { GasBadge, type GasBadgeProps } from "./GasBadge.js";
24
+ export { useCoinbaseTicker, type CoinbaseTickerStatus, type CoinbaseTickerMessage, type UseCoinbaseTickerOptions, type UseCoinbaseTickerResult } from "./useCoinbaseTicker.js";
25
+ export { useBotDecision, type BotDecision, type DecisionResult, type DecisionThresholds, type DecisionHistoryEntry, type UseBotDecisionOptions, type UseBotDecisionResult } from "./useBotDecision.js";
26
+ export { computeBotDecision, computeBookImbalance, computeBookDepth, computeTradeImbalance, DEFAULT_DECISION_THRESHOLDS, type DecisionInputs, type DecisionReason, type BookLevel } from "./decisionEngine.js";
27
+ export { BotDecisionPanel, type BotDecisionPanelProps } from "./BotDecisionPanel.js";
24
28
  /** @deprecated Use BAND_STRATEGY_ABI. Will be removed in v1.0. */
25
29
  export { BAND_STRATEGY_ABI as ETH_BOT_VAULT_ABI } from "./bandStrategyAbi.js";
26
30
  /** @deprecated Use BAND_STRATEGY_BYTECODE. Will be removed in v1.0. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -24,6 +24,11 @@ export { useVolatilityRegime } from "./useVolatilityRegime.js";
24
24
  export { RegimeBadge } from "./RegimeBadge.js";
25
25
  export { useGasPrice } from "./useGasPrice.js";
26
26
  export { GasBadge } from "./GasBadge.js";
27
+ // --- Cross-venue + decision proxy ---------------------------------------
28
+ export { useCoinbaseTicker } from "./useCoinbaseTicker.js";
29
+ export { useBotDecision } from "./useBotDecision.js";
30
+ export { computeBotDecision, computeBookImbalance, computeBookDepth, computeTradeImbalance, DEFAULT_DECISION_THRESHOLDS } from "./decisionEngine.js";
31
+ export { BotDecisionPanel } from "./BotDecisionPanel.js";
27
32
  /** @deprecated Use BAND_STRATEGY_ABI. Will be removed in v1.0. */
28
33
  export { BAND_STRATEGY_ABI as ETH_BOT_VAULT_ABI } from "./bandStrategyAbi.js";
29
34
  /** @deprecated Use BAND_STRATEGY_BYTECODE. Will be removed in v1.0. */
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,SAAS,EAAuB,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAA+B,MAAM,wBAAwB,CAAC;AACxF,OAAO,EAAE,mBAAmB,EAAmC,MAAM,iBAAiB,CAAC;AACvF,OAAO,EACL,iCAAiC,EACjC,6BAA6B,EAG9B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAgC,MAAM,uBAAuB,CAAC;AACvF,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,EAI1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,iBAAiB,EAKlB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,mBAAmB,EAIpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAmE,MAAM,0BAA0B,CAAC;AAEhI,4EAA4E;AAC5E,OAAO,EACL,mBAAmB,EAMpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAA6B,MAAM,sBAAsB,CAAC;AAElF,OAAO,EACL,gBAAgB,EAMjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAuB,MAAM,gBAAgB,CAAC;AAEhE,4EAA4E;AAC5E,OAAO,EACL,yBAAyB,EAK1B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAA+B,MAAM,wBAAwB,CAAC;AAExF,4EAA4E;AAC5E,OAAO,EACL,mBAAmB,EAIpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,WAAW,EAAyB,MAAM,kBAAkB,CAAC;AAEtE,OAAO,EACL,WAAW,EAIZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAsB,MAAM,eAAe,CAAC;AAE7D,kEAAkE;AAClE,OAAO,EAAE,iBAAiB,IAAI,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,uEAAuE;AACvE,OAAO,EAAE,sBAAsB,IAAI,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AAC7F,kEAAkE;AAClE,OAAO,EAAE,iBAAiB,IAAI,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnF,uEAAuE;AACvE,OAAO,EAAE,sBAAsB,IAAI,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAClG,0DAA0D;AAC1D,OAAO,EAAE,SAAS,IAAI,WAAW,EAA2C,MAAM,gBAAgB,CAAC;AACnG,kEAAkE;AAClE,OAAO,EAAE,iBAAiB,IAAI,aAAa,EAAqD,MAAM,wBAAwB,CAAC;AAC/H,oEAAoE;AACpE,OAAO,EAAE,mBAAmB,IAAI,4BAA4B,EAA0E,MAAM,iBAAiB,CAAC;AAC9J,kFAAkF;AAClF,OAAO,EAAE,iCAAiC,IAAI,iCAAiC,EAAE,MAAM,oCAAoC,CAAC;AAC5H,8EAA8E;AAC9E,OAAO,EACL,6BAA6B,IAAI,wBAAwB,EAG1D,MAAM,oCAAoC,CAAC;AAC5C,iEAAiE;AACjE,OAAO,EAAE,gBAAgB,IAAI,kBAAkB,EAA6D,MAAM,uBAAuB,CAAC;AAC1I,oEAAoE;AACpE,OAAO,EACL,mBAAmB,IAAI,WAAW,EAInC,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import { type UseBinanceOrderBookOptions } from "./useBinanceOrderBook.js";
2
+ import { type UseBinanceTradesOptions } from "./useBinanceTrades.js";
3
+ import { type UseCoinbaseTickerOptions } from "./useCoinbaseTicker.js";
4
+ import { type UseVolatilityRegimeOptions } from "./useVolatilityRegime.js";
5
+ import { type BotDecision, type DecisionResult, type DecisionThresholds } from "./decisionEngine.js";
6
+ export type { BotDecision, DecisionResult, DecisionThresholds } from "./decisionEngine.js";
7
+ export type DecisionHistoryEntry = {
8
+ at: number;
9
+ decision: BotDecision;
10
+ confidence: number;
11
+ };
12
+ export type UseBotDecisionOptions = {
13
+ enabled?: boolean;
14
+ evaluationIntervalMs?: number;
15
+ flowWindowMs?: number;
16
+ historyLimit?: number;
17
+ thresholds?: Partial<DecisionThresholds>;
18
+ orderBook?: UseBinanceOrderBookOptions;
19
+ trades?: UseBinanceTradesOptions;
20
+ coinbase?: UseCoinbaseTickerOptions;
21
+ regime?: UseVolatilityRegimeOptions;
22
+ };
23
+ export type UseBotDecisionResult = DecisionResult & {
24
+ midPriceBinance: number | null;
25
+ midPriceCoinbase: number | null;
26
+ venueDivergenceBps: number | null;
27
+ bookDepthWithinHalfPctEth: number;
28
+ history: DecisionHistoryEntry[];
29
+ evaluatedAt?: Date;
30
+ };
31
+ export declare function useBotDecision(options?: UseBotDecisionOptions): UseBotDecisionResult;
32
+ //# sourceMappingURL=useBotDecision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBotDecision.d.ts","sourceRoot":"","sources":["../src/useBotDecision.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,EAAoB,KAAK,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EAAqB,KAAK,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAC1F,OAAO,EAAuB,KAAK,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,EAML,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,WAAW,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACzC,SAAS,CAAC,EAAE,0BAA0B,CAAC;IACvC,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,QAAQ,CAAC,EAAE,wBAAwB,CAAC;IACpC,MAAM,CAAC,EAAE,0BAA0B,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG;IAClD,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,yBAAyB,EAAE,MAAM,CAAC;IAClC,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,WAAW,CAAC,EAAE,IAAI,CAAC;CACpB,CAAC;AAQF,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,oBAAoB,CAqHxF"}
@@ -0,0 +1,104 @@
1
+ import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { useBinanceOrderBook } from "./useBinanceOrderBook.js";
3
+ import { useBinanceTrades } from "./useBinanceTrades.js";
4
+ import { useCoinbaseTicker } from "./useCoinbaseTicker.js";
5
+ import { useVolatilityRegime } from "./useVolatilityRegime.js";
6
+ import { computeBookDepth, computeBookImbalance, computeBotDecision, computeTradeImbalance, DEFAULT_DECISION_THRESHOLDS } from "./decisionEngine.js";
7
+ const EMPTY_RESULT = {
8
+ decision: "WAIT",
9
+ confidence: 0,
10
+ reasons: [{ text: "Waiting for live data", tone: "neutral" }]
11
+ };
12
+ export function useBotDecision(options = {}) {
13
+ const enabled = options.enabled ?? true;
14
+ const evaluationIntervalMs = options.evaluationIntervalMs ?? 2000;
15
+ const flowWindowMs = options.flowWindowMs ?? 60_000;
16
+ const historyLimit = options.historyLimit ?? 30;
17
+ const thresholds = { ...DEFAULT_DECISION_THRESHOLDS, ...(options.thresholds ?? {}) };
18
+ const book = useBinanceOrderBook({ ...options.orderBook, enabled });
19
+ const trades = useBinanceTrades({ ...options.trades, enabled });
20
+ const coinbase = useCoinbaseTicker({ ...options.coinbase, enabled });
21
+ const regime = useVolatilityRegime({ ...options.regime, enabled });
22
+ const bookRef = useRef(book);
23
+ const tradesRef = useRef(trades);
24
+ const coinbaseRef = useRef(coinbase);
25
+ const regimeRef = useRef(regime);
26
+ useEffect(() => {
27
+ bookRef.current = book;
28
+ }, [book]);
29
+ useEffect(() => {
30
+ tradesRef.current = trades;
31
+ }, [trades]);
32
+ useEffect(() => {
33
+ coinbaseRef.current = coinbase;
34
+ }, [coinbase]);
35
+ useEffect(() => {
36
+ regimeRef.current = regime;
37
+ }, [regime]);
38
+ const [snapshot, setSnapshot] = useState({
39
+ result: EMPTY_RESULT,
40
+ midBinance: null,
41
+ midCoinbase: null,
42
+ divergenceBps: null,
43
+ depthEth: 0
44
+ });
45
+ const [history, setHistory] = useState([]);
46
+ useEffect(() => {
47
+ if (!enabled)
48
+ return;
49
+ function evaluate() {
50
+ const b = bookRef.current;
51
+ const t = tradesRef.current;
52
+ const c = coinbaseRef.current;
53
+ const r = regimeRef.current;
54
+ const midBinance = b.midPrice;
55
+ if (midBinance === null) {
56
+ setSnapshot((prev) => ({ ...prev, evaluatedAt: new Date() }));
57
+ return;
58
+ }
59
+ const bookImbalance = computeBookImbalance(b.bids, b.asks, midBinance);
60
+ const depthEth = computeBookDepth(b.bids, b.asks, midBinance);
61
+ const tradeImbalance = computeTradeImbalance(t.trades, flowWindowMs);
62
+ const spreadPct = b.spreadPercent ?? 0;
63
+ const midCoinbase = c.midPrice;
64
+ const divergenceBps = midCoinbase !== null && midCoinbase > 0
65
+ ? ((midBinance - midCoinbase) / midCoinbase) * 10000
66
+ : null;
67
+ const result = computeBotDecision({
68
+ regime: r.regime,
69
+ bookImbalance,
70
+ tradeImbalance,
71
+ spreadPct,
72
+ venueDivergenceBps: divergenceBps,
73
+ bookDepthWithinHalfPctEth: depthEth,
74
+ midPrice: midBinance
75
+ }, thresholds);
76
+ const now = Date.now();
77
+ setSnapshot({
78
+ result,
79
+ midBinance,
80
+ midCoinbase,
81
+ divergenceBps,
82
+ depthEth,
83
+ evaluatedAt: new Date(now)
84
+ });
85
+ setHistory((prev) => [
86
+ { at: now, decision: result.decision, confidence: result.confidence },
87
+ ...prev
88
+ ].slice(0, historyLimit));
89
+ }
90
+ evaluate();
91
+ const id = window.setInterval(evaluate, evaluationIntervalMs);
92
+ return () => window.clearInterval(id);
93
+ }, [enabled, evaluationIntervalMs, flowWindowMs, historyLimit, thresholds]);
94
+ return useMemo(() => ({
95
+ ...snapshot.result,
96
+ midPriceBinance: snapshot.midBinance,
97
+ midPriceCoinbase: snapshot.midCoinbase,
98
+ venueDivergenceBps: snapshot.divergenceBps,
99
+ bookDepthWithinHalfPctEth: snapshot.depthEth,
100
+ history,
101
+ evaluatedAt: snapshot.evaluatedAt
102
+ }), [history, snapshot]);
103
+ }
104
+ //# sourceMappingURL=useBotDecision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useBotDecision.js","sourceRoot":"","sources":["../src/useBotDecision.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ export type CoinbaseTickerStatus = "connecting" | "live" | "stale" | "error";
2
+ export type CoinbaseTickerMessage = {
3
+ type: string;
4
+ product_id: string;
5
+ price?: string;
6
+ best_bid?: string;
7
+ best_ask?: string;
8
+ side?: "buy" | "sell";
9
+ time?: string;
10
+ };
11
+ export type UseCoinbaseTickerOptions = {
12
+ enabled?: boolean;
13
+ productId?: string;
14
+ reconnectDelayMs?: number;
15
+ };
16
+ export type UseCoinbaseTickerResult = {
17
+ price: number | null;
18
+ bestBid: number | null;
19
+ bestAsk: number | null;
20
+ midPrice: number | null;
21
+ lastSide?: "buy" | "sell";
22
+ status: CoinbaseTickerStatus;
23
+ error?: string;
24
+ productId: string;
25
+ updatedAt?: Date;
26
+ };
27
+ export declare function useCoinbaseTicker(options?: UseCoinbaseTickerOptions): UseCoinbaseTickerResult;
28
+ //# sourceMappingURL=useCoinbaseTicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCoinbaseTicker.d.ts","sourceRoot":"","sources":["../src/useCoinbaseTicker.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAE7E,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAC1B,MAAM,EAAE,oBAAoB,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB,CAAC;AAIF,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,wBAA6B,GAAG,uBAAuB,CA+FjG"}
@@ -0,0 +1,87 @@
1
+ import { useEffect, useMemo, useState } from "react";
2
+ const DEFAULT_URL = "wss://ws-feed.exchange.coinbase.com";
3
+ export function useCoinbaseTicker(options = {}) {
4
+ const enabled = options.enabled ?? true;
5
+ const productId = options.productId ?? "ETH-USD";
6
+ const reconnectDelayMs = options.reconnectDelayMs ?? 3000;
7
+ const [message, setMessage] = useState();
8
+ const [status, setStatus] = useState("connecting");
9
+ const [error, setError] = useState();
10
+ const [updatedAt, setUpdatedAt] = useState();
11
+ useEffect(() => {
12
+ if (!enabled) {
13
+ setStatus("stale");
14
+ return;
15
+ }
16
+ if (typeof WebSocket === "undefined") {
17
+ setStatus("error");
18
+ setError("WebSocket is not available in this runtime.");
19
+ return;
20
+ }
21
+ let reconnectTimer;
22
+ let closedByHook = false;
23
+ let socket;
24
+ function connect() {
25
+ setStatus("connecting");
26
+ socket = new WebSocket(DEFAULT_URL);
27
+ socket.onopen = () => {
28
+ socket?.send(JSON.stringify({
29
+ type: "subscribe",
30
+ product_ids: [productId],
31
+ channels: ["ticker"]
32
+ }));
33
+ setStatus("live");
34
+ setError(undefined);
35
+ };
36
+ socket.onmessage = (event) => {
37
+ try {
38
+ const parsed = JSON.parse(String(event.data));
39
+ if (parsed.type === "ticker" && parsed.product_id === productId) {
40
+ setMessage(parsed);
41
+ setStatus("live");
42
+ setUpdatedAt(new Date());
43
+ }
44
+ }
45
+ catch {
46
+ setStatus("error");
47
+ setError("Could not parse Coinbase ticker message.");
48
+ }
49
+ };
50
+ socket.onerror = () => {
51
+ setStatus("error");
52
+ setError("Coinbase ticker connection failed.");
53
+ };
54
+ socket.onclose = () => {
55
+ if (closedByHook)
56
+ return;
57
+ setStatus("stale");
58
+ reconnectTimer = setTimeout(connect, reconnectDelayMs);
59
+ };
60
+ }
61
+ connect();
62
+ return () => {
63
+ closedByHook = true;
64
+ if (reconnectTimer)
65
+ clearTimeout(reconnectTimer);
66
+ socket?.close();
67
+ };
68
+ }, [enabled, productId, reconnectDelayMs]);
69
+ return useMemo(() => {
70
+ const price = message?.price ? Number(message.price) : null;
71
+ const bestBid = message?.best_bid ? Number(message.best_bid) : null;
72
+ const bestAsk = message?.best_ask ? Number(message.best_ask) : null;
73
+ const midPrice = bestBid !== null && bestAsk !== null ? (bestBid + bestAsk) / 2 : null;
74
+ return {
75
+ price: price !== null && Number.isFinite(price) ? price : null,
76
+ bestBid: bestBid !== null && Number.isFinite(bestBid) ? bestBid : null,
77
+ bestAsk: bestAsk !== null && Number.isFinite(bestAsk) ? bestAsk : null,
78
+ midPrice: midPrice !== null && Number.isFinite(midPrice) ? midPrice : null,
79
+ lastSide: message?.side,
80
+ status,
81
+ error,
82
+ productId,
83
+ updatedAt
84
+ };
85
+ }, [error, message, productId, status, updatedAt]);
86
+ }
87
+ //# sourceMappingURL=useCoinbaseTicker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCoinbaseTicker.js","sourceRoot":"","sources":["../src/useCoinbaseTicker.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bandkit",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Open-source scaffolding for Ethereum range-bound trading strategies: strategy contract deployment, restricted executor, and React hooks. Strategy logic is not included.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -63,6 +63,7 @@
63
63
  "@tanstack/react-query": "^5.0.0",
64
64
  "@types/node": "^22.0.0",
65
65
  "@types/react": "^19.0.0",
66
+ "@types/ws": "^8.18.1",
66
67
  "chai": "^6.2.2",
67
68
  "dotenv": "^16.4.7",
68
69
  "fp-ts": "^2.16.11",
@@ -73,7 +74,8 @@
73
74
  "ts-node": "^10.9.2",
74
75
  "typescript": "^5.7.0",
75
76
  "viem": "^2.0.0",
76
- "wagmi": "^2.0.0"
77
+ "wagmi": "^2.0.0",
78
+ "ws": "^8.21.0"
77
79
  },
78
80
  "keywords": [
79
81
  "ethereum",
@@ -0,0 +1,417 @@
1
+ /* eslint-disable */
2
+ //
3
+ // test-decision-stream.cjs
4
+ //
5
+ // Standalone Node script that streams live Binance ETH/USDT order book + trades
6
+ // and Coinbase ETH-USD ticker, and runs the same decision logic as
7
+ // src/decisionEngine.ts. Prints live decisions; after the run window,
8
+ // prints a summary tally and a diagnostic signal-vs-forward-price comparison.
9
+ //
10
+ // IMPORTANT — what this proves and does NOT prove:
11
+ // - It DOES verify the decision logic responds to live data without crashing.
12
+ // - It DOES show the cadence and balance of signals.
13
+ // - It DOES report a crude correlation between BUY/SELL signals and the
14
+ // next minute's price change.
15
+ // - It DOES NOT prove the strategy is profitable.
16
+ // Real validation needs months of historical data, realistic slippage and
17
+ // gas modelling, out-of-sample windows, and statistical significance tests.
18
+ //
19
+ // Usage:
20
+ // node scripts/test-decision-stream.cjs [durationMinutes]
21
+ // default duration: 7 minutes
22
+ //
23
+
24
+ const WebSocket = require("ws");
25
+
26
+ const DURATION_MIN = Number(process.argv[2] ?? 7);
27
+ const BINANCE_DEPTH_URL = "wss://stream.binance.com:9443/ws/ethusdt@depth20@100ms";
28
+ const BINANCE_TRADE_URL = "wss://stream.binance.com:9443/ws/ethusdt@trade";
29
+ const COINBASE_URL = "wss://ws-feed.exchange.coinbase.com";
30
+
31
+ // ---- State ----------------------------------------------------------------
32
+ const state = {
33
+ binanceBook: { bids: [], asks: [], midPrice: null, spreadPct: null, ts: 0 },
34
+ coinbaseMid: null,
35
+ recentTrades: [], // { price, size, side, ts }
36
+ priceTicks: [], // for volatility regime
37
+ decisions: [], // { at, decision, confidence, midPrice, reasons }
38
+ startedAt: Date.now()
39
+ };
40
+
41
+ const PRICE_TICK_WINDOW = 60;
42
+ const FLOW_WINDOW_MS = 60_000;
43
+ const EVAL_INTERVAL_MS = 2000;
44
+ const FORWARD_PRICE_DELAY_MS = 60_000;
45
+
46
+ // ---- Decision logic (mirrors src/decisionEngine.ts) ----------------------
47
+ function computeBookImbalance(bids, asks, mid, withinPct = 0.005) {
48
+ const lo = mid * (1 - withinPct);
49
+ const hi = mid * (1 + withinPct);
50
+ let bidSum = 0;
51
+ let askSum = 0;
52
+ for (const b of bids) if (b.price >= lo) bidSum += b.size;
53
+ for (const a of asks) if (a.price <= hi) askSum += a.size;
54
+ const total = bidSum + askSum;
55
+ return total > 0 ? (bidSum - askSum) / total : 0;
56
+ }
57
+
58
+ function computeBookDepth(bids, asks, mid, withinPct = 0.005) {
59
+ const lo = mid * (1 - withinPct);
60
+ const hi = mid * (1 + withinPct);
61
+ let total = 0;
62
+ for (const b of bids) if (b.price >= lo) total += b.size;
63
+ for (const a of asks) if (a.price <= hi) total += a.size;
64
+ return total;
65
+ }
66
+
67
+ function computeTradeImbalance(trades, windowMs, nowMs) {
68
+ const cutoff = nowMs - windowMs;
69
+ let buyVol = 0;
70
+ let sellVol = 0;
71
+ for (const t of trades) {
72
+ if (t.ts < cutoff) continue;
73
+ if (t.side === "buy") buyVol += t.size;
74
+ else sellVol += t.size;
75
+ }
76
+ const total = buyVol + sellVol;
77
+ return total > 0 ? (buyVol - sellVol) / total : 0;
78
+ }
79
+
80
+ function classifyRegime(prices) {
81
+ if (prices.length < Math.max(8, Math.floor(PRICE_TICK_WINDOW / 4))) {
82
+ return { regime: "UNKNOWN", stddevPct: null, driftPct: null };
83
+ }
84
+ const mean = prices.reduce((s, v) => s + v, 0) / prices.length;
85
+ const variance = prices.reduce((s, v) => s + (v - mean) ** 2, 0) / prices.length;
86
+ const stddev = Math.sqrt(variance);
87
+ const stddevPct = mean > 0 ? (stddev / mean) * 100 : null;
88
+ const drift = prices[prices.length - 1] - prices[0];
89
+ const driftPct = mean > 0 ? (drift / mean) * 100 : null;
90
+ const absDriftPct = driftPct === null ? null : Math.abs(driftPct);
91
+
92
+ let regime = "MIXED";
93
+ if (stddevPct !== null && absDriftPct !== null) {
94
+ if (absDriftPct >= 0.45) regime = "TRENDING";
95
+ else if (stddevPct <= 0.15 && absDriftPct < 0.45) regime = "RANGING";
96
+ }
97
+ return { regime, stddevPct, driftPct };
98
+ }
99
+
100
+ function computeDecision(inputs) {
101
+ const reasons = [];
102
+ if (inputs.regime === "UNKNOWN") {
103
+ return { decision: "WAIT", confidence: 0, reasons: ["Warming up"] };
104
+ }
105
+ if (inputs.regime === "TRENDING") {
106
+ return {
107
+ decision: "STAND DOWN",
108
+ confidence: 90,
109
+ reasons: ["TRENDING regime — bot sits out"]
110
+ };
111
+ }
112
+
113
+ let scoreBuy = 0;
114
+ let scoreSell = 0;
115
+ let confidence = inputs.regime === "RANGING" ? 45 : 30;
116
+ reasons.push(`Regime: ${inputs.regime}`);
117
+
118
+ if (inputs.bookImbalance >= 0.15) {
119
+ scoreBuy += 25;
120
+ reasons.push(`Book +${(inputs.bookImbalance * 100).toFixed(0)}% bid`);
121
+ } else if (inputs.bookImbalance <= -0.15) {
122
+ scoreSell += 25;
123
+ reasons.push(`Book ${(inputs.bookImbalance * 100).toFixed(0)}% ask`);
124
+ } else {
125
+ reasons.push(`Book ${(inputs.bookImbalance * 100).toFixed(0)}%`);
126
+ }
127
+
128
+ if (inputs.tradeImbalance >= 0.10) {
129
+ scoreBuy += 20;
130
+ reasons.push(`Flow ${((0.5 + inputs.tradeImbalance / 2) * 100).toFixed(0)}% buys`);
131
+ } else if (inputs.tradeImbalance <= -0.10) {
132
+ scoreSell += 20;
133
+ reasons.push(`Flow ${((0.5 - inputs.tradeImbalance / 2) * 100).toFixed(0)}% sells`);
134
+ } else {
135
+ reasons.push("Flow balanced");
136
+ }
137
+
138
+ if (inputs.spreadPct > 0.05) {
139
+ confidence -= 10;
140
+ reasons.push(`Spread wide ${inputs.spreadPct.toFixed(3)}%`);
141
+ } else {
142
+ confidence += 5;
143
+ }
144
+
145
+ if (inputs.venueDivergenceBps !== null && Number.isFinite(inputs.venueDivergenceBps)) {
146
+ if (inputs.venueDivergenceBps > 5) {
147
+ scoreBuy += 12;
148
+ reasons.push(`BIN +${inputs.venueDivergenceBps.toFixed(1)}bps`);
149
+ } else if (inputs.venueDivergenceBps < -5) {
150
+ scoreSell += 12;
151
+ reasons.push(`BIN ${inputs.venueDivergenceBps.toFixed(1)}bps`);
152
+ }
153
+ }
154
+
155
+ if (inputs.bookDepth < 50) {
156
+ confidence -= 15;
157
+ reasons.push(`Thin book (${inputs.bookDepth.toFixed(0)} ETH)`);
158
+ } else {
159
+ confidence += 5;
160
+ }
161
+
162
+ let decision = "WAIT";
163
+ if (scoreBuy > scoreSell + 12) {
164
+ decision = "BUY";
165
+ confidence = Math.min(100, Math.max(0, confidence + scoreBuy));
166
+ } else if (scoreSell > scoreBuy + 12) {
167
+ decision = "SELL";
168
+ confidence = Math.min(100, Math.max(0, confidence + scoreSell));
169
+ } else {
170
+ confidence = Math.min(100, Math.max(0, confidence + Math.max(scoreBuy, scoreSell) / 2));
171
+ }
172
+
173
+ return { decision, confidence: Math.round(confidence), reasons };
174
+ }
175
+
176
+ // ---- WebSocket setup -----------------------------------------------------
177
+ function connectBinanceDepth() {
178
+ const ws = new WebSocket(BINANCE_DEPTH_URL);
179
+ ws.on("message", (raw) => {
180
+ try {
181
+ const msg = JSON.parse(raw.toString());
182
+ const bids = (msg.bids || [])
183
+ .map(([p, q]) => ({ price: Number(p), size: Number(q) }))
184
+ .filter((l) => l.size > 0);
185
+ const asks = (msg.asks || [])
186
+ .map(([p, q]) => ({ price: Number(p), size: Number(q) }))
187
+ .filter((l) => l.size > 0);
188
+ const bestBid = bids[0]?.price ?? null;
189
+ const bestAsk = asks[0]?.price ?? null;
190
+ if (bestBid && bestAsk) {
191
+ const mid = (bestBid + bestAsk) / 2;
192
+ state.binanceBook = {
193
+ bids,
194
+ asks,
195
+ midPrice: mid,
196
+ spreadPct: ((bestAsk - bestBid) / mid) * 100,
197
+ ts: Date.now()
198
+ };
199
+ // Sample price tick (rate-limited)
200
+ if (
201
+ state.priceTicks.length === 0 ||
202
+ Date.now() - (state.priceTicks._lastSampleAt || 0) > 1000
203
+ ) {
204
+ state.priceTicks.push(mid);
205
+ if (state.priceTicks.length > PRICE_TICK_WINDOW) state.priceTicks.shift();
206
+ state.priceTicks._lastSampleAt = Date.now();
207
+ }
208
+ }
209
+ } catch (err) {
210
+ /* swallow parse */
211
+ }
212
+ });
213
+ ws.on("error", () => {});
214
+ ws.on("close", () => setTimeout(connectBinanceDepth, 3000));
215
+ return ws;
216
+ }
217
+
218
+ function connectBinanceTrades() {
219
+ const ws = new WebSocket(BINANCE_TRADE_URL);
220
+ ws.on("message", (raw) => {
221
+ try {
222
+ const msg = JSON.parse(raw.toString());
223
+ const trade = {
224
+ price: Number(msg.p),
225
+ size: Number(msg.q),
226
+ side: msg.m ? "sell" : "buy",
227
+ ts: Number(msg.T)
228
+ };
229
+ if (Number.isFinite(trade.price) && Number.isFinite(trade.size)) {
230
+ state.recentTrades.push(trade);
231
+ // Prune
232
+ const cutoff = Date.now() - FLOW_WINDOW_MS * 2;
233
+ while (state.recentTrades.length > 0 && state.recentTrades[0].ts < cutoff) {
234
+ state.recentTrades.shift();
235
+ }
236
+ }
237
+ } catch (err) {
238
+ /* swallow */
239
+ }
240
+ });
241
+ ws.on("error", () => {});
242
+ ws.on("close", () => setTimeout(connectBinanceTrades, 3000));
243
+ return ws;
244
+ }
245
+
246
+ function connectCoinbase() {
247
+ const ws = new WebSocket(COINBASE_URL);
248
+ ws.on("open", () => {
249
+ ws.send(
250
+ JSON.stringify({
251
+ type: "subscribe",
252
+ product_ids: ["ETH-USD"],
253
+ channels: ["ticker"]
254
+ })
255
+ );
256
+ });
257
+ ws.on("message", (raw) => {
258
+ try {
259
+ const msg = JSON.parse(raw.toString());
260
+ if (msg.type === "ticker" && msg.product_id === "ETH-USD") {
261
+ const bid = Number(msg.best_bid);
262
+ const ask = Number(msg.best_ask);
263
+ if (Number.isFinite(bid) && Number.isFinite(ask)) {
264
+ state.coinbaseMid = (bid + ask) / 2;
265
+ }
266
+ }
267
+ } catch (err) {
268
+ /* swallow */
269
+ }
270
+ });
271
+ ws.on("error", () => {});
272
+ ws.on("close", () => setTimeout(connectCoinbase, 3000));
273
+ return ws;
274
+ }
275
+
276
+ // ---- Main loop -----------------------------------------------------------
277
+ function pad(s, n) {
278
+ s = String(s);
279
+ return s.length >= n ? s : s + " ".repeat(n - s.length);
280
+ }
281
+
282
+ function evaluate() {
283
+ const mid = state.binanceBook.midPrice;
284
+ if (!mid) return;
285
+
286
+ const bookImbalance = computeBookImbalance(state.binanceBook.bids, state.binanceBook.asks, mid);
287
+ const bookDepth = computeBookDepth(state.binanceBook.bids, state.binanceBook.asks, mid);
288
+ const tradeImbalance = computeTradeImbalance(state.recentTrades, FLOW_WINDOW_MS, Date.now());
289
+ const { regime, stddevPct, driftPct } = classifyRegime(state.priceTicks);
290
+ const venueDivergenceBps =
291
+ state.coinbaseMid && state.coinbaseMid > 0
292
+ ? ((mid - state.coinbaseMid) / state.coinbaseMid) * 10000
293
+ : null;
294
+
295
+ const result = computeDecision({
296
+ regime,
297
+ bookImbalance,
298
+ tradeImbalance,
299
+ spreadPct: state.binanceBook.spreadPct ?? 0,
300
+ venueDivergenceBps,
301
+ bookDepth
302
+ });
303
+
304
+ const entry = {
305
+ at: Date.now(),
306
+ decision: result.decision,
307
+ confidence: result.confidence,
308
+ midPrice: mid,
309
+ reasons: result.reasons,
310
+ regime,
311
+ bookImbalance,
312
+ tradeImbalance,
313
+ venueDivergenceBps,
314
+ stddevPct,
315
+ driftPct
316
+ };
317
+ state.decisions.push(entry);
318
+
319
+ const elapsedSec = ((Date.now() - state.startedAt) / 1000).toFixed(0);
320
+ console.log(
321
+ `[t+${pad(elapsedSec, 4)}s] ${pad(result.decision, 11)} conf=${pad(result.confidence, 3)}` +
322
+ ` mid=${mid.toFixed(2)} regime=${pad(regime, 8)}` +
323
+ ` book=${pad((bookImbalance * 100).toFixed(0) + "%", 5)}` +
324
+ ` flow=${pad((tradeImbalance * 100).toFixed(0) + "%", 5)}` +
325
+ ` div=${venueDivergenceBps === null ? " — " : (venueDivergenceBps.toFixed(1) + "bps")}` +
326
+ ` ${result.reasons.slice(0, 3).join(", ")}`
327
+ );
328
+ }
329
+
330
+ function printSummary() {
331
+ console.log("\n" + "=".repeat(80));
332
+ console.log(`SUMMARY (duration ${DURATION_MIN} min, ${state.decisions.length} evaluations)`);
333
+ console.log("=".repeat(80));
334
+
335
+ const tally = { BUY: 0, SELL: 0, WAIT: 0, "STAND DOWN": 0 };
336
+ const regimeTally = { RANGING: 0, MIXED: 0, TRENDING: 0, UNKNOWN: 0 };
337
+ for (const d of state.decisions) {
338
+ tally[d.decision]++;
339
+ regimeTally[d.regime]++;
340
+ }
341
+
342
+ console.log("\nDecision tally:");
343
+ for (const [k, v] of Object.entries(tally)) {
344
+ const pct = state.decisions.length > 0 ? ((v / state.decisions.length) * 100).toFixed(1) : "0.0";
345
+ console.log(` ${pad(k, 12)} ${pad(v, 5)} (${pct}%)`);
346
+ }
347
+
348
+ console.log("\nRegime time-in-state:");
349
+ for (const [k, v] of Object.entries(regimeTally)) {
350
+ const pct = state.decisions.length > 0 ? ((v / state.decisions.length) * 100).toFixed(1) : "0.0";
351
+ console.log(` ${pad(k, 12)} ${pad(v, 5)} (${pct}%)`);
352
+ }
353
+
354
+ // Forward-price diagnostic: for each BUY/SELL signal, look at the price
355
+ // ~60s later and tally whether it moved in the signal's favor.
356
+ const directional = state.decisions.filter((d) => d.decision === "BUY" || d.decision === "SELL");
357
+ let evaluated = 0;
358
+ let correct = 0;
359
+ let totalForwardBps = 0;
360
+ for (const d of directional) {
361
+ const targetTs = d.at + FORWARD_PRICE_DELAY_MS;
362
+ // find first decision at or after target
363
+ const futureIdx = state.decisions.findIndex((x) => x.at >= targetTs);
364
+ if (futureIdx === -1) continue;
365
+ const futurePrice = state.decisions[futureIdx].midPrice;
366
+ const moveBps = ((futurePrice - d.midPrice) / d.midPrice) * 10000;
367
+ evaluated++;
368
+ const signedMove = d.decision === "BUY" ? moveBps : -moveBps;
369
+ totalForwardBps += signedMove;
370
+ if (signedMove > 0) correct++;
371
+ }
372
+ console.log("\nDirectional diagnostic (60s-forward, NOT a profitability test):");
373
+ if (evaluated === 0) {
374
+ console.log(" no eligible directional signals with forward window");
375
+ } else {
376
+ const hitRate = ((correct / evaluated) * 100).toFixed(1);
377
+ const avgSignedBps = (totalForwardBps / evaluated).toFixed(2);
378
+ console.log(` signals scored: ${evaluated}`);
379
+ console.log(` moved in our favour: ${correct} (${hitRate}%)`);
380
+ console.log(` avg signed move: ${avgSignedBps} bps`);
381
+ console.log(
382
+ ` ↳ baseline ~50% means no edge; small samples (<50) are noise; gas + slippage`
383
+ );
384
+ console.log(` would eat anything below ~10 bps on Uniswap mainnet.`);
385
+ }
386
+
387
+ console.log("\nReminders:");
388
+ console.log(" - This verifies the logic responds. It does NOT prove profitability.");
389
+ console.log(" - Binance signals ≠ Uniswap fills. Cross-venue + AMM curves differ.");
390
+ console.log(" - Real validation needs months of historical data + walk-forward.\n");
391
+ }
392
+
393
+ // ---- Run ------------------------------------------------------------------
394
+ console.log(`Starting decision stream test for ${DURATION_MIN} minutes…`);
395
+ console.log(`Streams: Binance depth + trade (ETHUSDT), Coinbase ticker (ETH-USD)`);
396
+ console.log(`Evaluating every ${EVAL_INTERVAL_MS}ms. Press Ctrl-C to abort.\n`);
397
+
398
+ connectBinanceDepth();
399
+ connectBinanceTrades();
400
+ connectCoinbase();
401
+
402
+ const evalTimer = setInterval(evaluate, EVAL_INTERVAL_MS);
403
+ const stopAt = Date.now() + DURATION_MIN * 60_000;
404
+ const stopTimer = setInterval(() => {
405
+ if (Date.now() >= stopAt) {
406
+ clearInterval(evalTimer);
407
+ clearInterval(stopTimer);
408
+ printSummary();
409
+ process.exit(0);
410
+ }
411
+ }, 1000);
412
+
413
+ process.on("SIGINT", () => {
414
+ console.log("\n\nAborted by user. Printing partial summary…");
415
+ printSummary();
416
+ process.exit(0);
417
+ });