perp-cli 0.6.4 → 0.7.1

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.
Files changed (177) hide show
  1. package/README.md +7 -2
  2. package/dist/api/public/hyperliquid.d.ts +2 -2
  3. package/dist/api/public/hyperliquid.js +0 -10
  4. package/dist/api/public/lighter.d.ts +2 -1
  5. package/dist/api/public/pacifica.d.ts +2 -1
  6. package/dist/arb/index.d.ts +7 -4
  7. package/dist/arb/index.js +5 -4
  8. package/dist/arb/sizing.d.ts +1 -1
  9. package/dist/arb/state.d.ts +1 -1
  10. package/dist/arb/state.js +4 -3
  11. package/dist/arb/utils.d.ts +0 -10
  12. package/dist/arb/utils.js +0 -42
  13. package/dist/bot/apex/guard.d.ts +26 -0
  14. package/dist/bot/apex/guard.js +86 -0
  15. package/dist/bot/apex/index.d.ts +6 -0
  16. package/dist/bot/apex/index.js +4 -0
  17. package/dist/bot/apex/pulse.d.ts +22 -0
  18. package/dist/bot/apex/pulse.js +107 -0
  19. package/dist/bot/apex/radar.d.ts +17 -0
  20. package/dist/bot/apex/radar.js +150 -0
  21. package/dist/bot/conditions.d.ts +1 -1
  22. package/dist/bot/config.d.ts +5 -1
  23. package/dist/bot/config.js +1 -1
  24. package/dist/bot/engine.d.ts +23 -2
  25. package/dist/bot/engine.js +296 -351
  26. package/dist/bot/index.d.ts +16 -0
  27. package/dist/bot/index.js +10 -0
  28. package/dist/bot/indicators.d.ts +29 -0
  29. package/dist/bot/indicators.js +144 -0
  30. package/dist/bot/reflect.d.ts +34 -0
  31. package/dist/bot/reflect.js +155 -0
  32. package/dist/bot/strategies/aggressive-taker.d.ts +36 -0
  33. package/dist/bot/strategies/aggressive-taker.js +125 -0
  34. package/dist/bot/strategies/apex-strategy.d.ts +36 -0
  35. package/dist/bot/strategies/apex-strategy.js +185 -0
  36. package/dist/bot/strategies/avellaneda-mm.d.ts +27 -0
  37. package/dist/bot/strategies/avellaneda-mm.js +111 -0
  38. package/dist/bot/strategies/basis-arb.d.ts +27 -0
  39. package/dist/bot/strategies/basis-arb.js +97 -0
  40. package/dist/bot/strategies/claude-agent.d.ts +31 -0
  41. package/dist/bot/strategies/claude-agent.js +155 -0
  42. package/dist/bot/strategies/dca-strategy.d.ts +25 -0
  43. package/dist/bot/strategies/dca-strategy.js +59 -0
  44. package/dist/bot/strategies/engine-mm.d.ts +29 -0
  45. package/dist/bot/strategies/engine-mm.js +141 -0
  46. package/dist/bot/strategies/funding-arb-strategy.d.ts +32 -0
  47. package/dist/bot/strategies/funding-arb-strategy.js +168 -0
  48. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  49. package/dist/bot/strategies/funding-arb-v2.js +222 -0
  50. package/dist/bot/strategies/funding-auto.d.ts +1 -0
  51. package/dist/bot/strategies/funding-auto.js +572 -0
  52. package/dist/bot/strategies/grid-mm.d.ts +25 -0
  53. package/dist/bot/strategies/grid-mm.js +99 -0
  54. package/dist/bot/strategies/grid-strategy.d.ts +39 -0
  55. package/dist/bot/strategies/grid-strategy.js +176 -0
  56. package/dist/bot/strategies/hedge-agent.d.ts +25 -0
  57. package/dist/bot/strategies/hedge-agent.js +79 -0
  58. package/dist/bot/strategies/liquidation-mm.d.ts +25 -0
  59. package/dist/bot/strategies/liquidation-mm.js +108 -0
  60. package/dist/bot/strategies/mean-reversion.d.ts +35 -0
  61. package/dist/bot/strategies/mean-reversion.js +118 -0
  62. package/dist/bot/strategies/momentum-breakout.d.ts +36 -0
  63. package/dist/bot/strategies/momentum-breakout.js +159 -0
  64. package/dist/bot/strategies/regime-mm.d.ts +25 -0
  65. package/dist/bot/strategies/regime-mm.js +111 -0
  66. package/dist/bot/strategies/rfq-agent.d.ts +25 -0
  67. package/dist/bot/strategies/rfq-agent.js +71 -0
  68. package/dist/bot/strategies/simple-mm.d.ts +25 -0
  69. package/dist/bot/strategies/simple-mm.js +76 -0
  70. package/dist/bot/strategy-registry.d.ts +9 -0
  71. package/dist/bot/strategy-registry.js +14 -0
  72. package/dist/bot/strategy-types.d.ts +64 -0
  73. package/dist/bot/strategy-types.js +5 -0
  74. package/dist/bot/trade-journal.d.ts +27 -0
  75. package/dist/bot/trade-journal.js +53 -0
  76. package/dist/bot/tui/BotDashboard.d.ts +49 -0
  77. package/dist/bot/tui/BotDashboard.js +93 -0
  78. package/dist/bot/tui/index.d.ts +6 -0
  79. package/dist/bot/tui/index.js +8 -0
  80. package/dist/bridge-engine.d.ts +0 -1
  81. package/dist/bridge-engine.js +0 -1
  82. package/dist/cache.d.ts +0 -1
  83. package/dist/cache.js +0 -1
  84. package/dist/commands/account.d.ts +1 -1
  85. package/dist/commands/account.js +94 -91
  86. package/dist/commands/agent.d.ts +1 -1
  87. package/dist/commands/arb/index.d.ts +1 -1
  88. package/dist/commands/arb/index.js +1 -1
  89. package/dist/commands/arb-auto.d.ts +1 -1
  90. package/dist/commands/arb-auto.js +1 -1
  91. package/dist/commands/arb.d.ts +1 -1
  92. package/dist/commands/bot.d.ts +1 -1
  93. package/dist/commands/bot.js +198 -14
  94. package/dist/commands/dashboard.d.ts +1 -1
  95. package/dist/commands/dashboard.js +1 -1
  96. package/dist/commands/funds.d.ts +1 -1
  97. package/dist/commands/funds.js +33 -20
  98. package/dist/commands/history.d.ts +1 -1
  99. package/dist/commands/history.js +5 -4
  100. package/dist/commands/manage.d.ts +2 -3
  101. package/dist/commands/manage.js +11 -18
  102. package/dist/commands/market.d.ts +1 -1
  103. package/dist/commands/market.js +7 -7
  104. package/dist/commands/plan.d.ts +1 -1
  105. package/dist/commands/rebalance.d.ts +1 -1
  106. package/dist/commands/rebalance.js +8 -9
  107. package/dist/commands/risk.d.ts +1 -1
  108. package/dist/commands/settings.d.ts +1 -1
  109. package/dist/commands/settings.js +3 -1
  110. package/dist/commands/trade.d.ts +1 -1
  111. package/dist/commands/trade.js +39 -52
  112. package/dist/commands/wallet.d.ts +0 -2
  113. package/dist/commands/wallet.js +0 -5
  114. package/dist/constants.d.ts +0 -3
  115. package/dist/constants.js +0 -4
  116. package/dist/cross-chain-margin.d.ts +1 -1
  117. package/dist/dashboard/index.d.ts +4 -0
  118. package/dist/dashboard/index.js +3 -0
  119. package/dist/dashboard/server.d.ts +1 -1
  120. package/dist/dashboard/server.js +1 -1
  121. package/dist/dashboard/ws-feeds.d.ts +1 -1
  122. package/dist/event-stream.d.ts +1 -1
  123. package/dist/exchanges/aster.d.ts +58 -0
  124. package/dist/exchanges/aster.js +409 -0
  125. package/dist/exchanges/capabilities.d.ts +74 -0
  126. package/dist/exchanges/capabilities.js +44 -0
  127. package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
  128. package/dist/exchanges/hyperliquid-spot.js +20 -12
  129. package/dist/exchanges/hyperliquid.d.ts +8 -2
  130. package/dist/exchanges/hyperliquid.js +21 -6
  131. package/dist/exchanges/index.d.ts +11 -0
  132. package/dist/exchanges/index.js +10 -0
  133. package/dist/exchanges/interface.d.ts +3 -0
  134. package/dist/exchanges/lighter-spot.d.ts +13 -0
  135. package/dist/exchanges/lighter-spot.js +98 -26
  136. package/dist/exchanges/lighter.d.ts +9 -3
  137. package/dist/exchanges/lighter.js +18 -8
  138. package/dist/exchanges/nado.d.ts +52 -0
  139. package/dist/exchanges/nado.js +223 -0
  140. package/dist/exchanges/pacifica.d.ts +7 -1
  141. package/dist/exchanges/pacifica.js +17 -2
  142. package/dist/exchanges/registry.d.ts +13 -0
  143. package/dist/exchanges/registry.js +37 -0
  144. package/dist/exchanges/spot-interface.d.ts +9 -0
  145. package/dist/funding/rates.js +1 -1
  146. package/dist/index.js +2 -6
  147. package/dist/jobs.js +1 -1
  148. package/dist/liquidity.d.ts +1 -2
  149. package/dist/liquidity.js +12 -5
  150. package/dist/mcp-server.js +4 -2
  151. package/dist/pacifica/constants.d.ts +0 -3
  152. package/dist/pacifica/constants.js +0 -4
  153. package/dist/pacifica/types/account.d.ts +0 -8
  154. package/dist/pacifica/types/lake.d.ts +0 -6
  155. package/dist/pacifica/types/position.d.ts +0 -12
  156. package/dist/pacifica/types/ws.d.ts +0 -24
  157. package/dist/pacifica/types/ws.js +1 -41
  158. package/dist/plan-executor.d.ts +1 -1
  159. package/dist/rebalance.d.ts +1 -7
  160. package/dist/rebalance.js +0 -6
  161. package/dist/risk.d.ts +1 -1
  162. package/dist/risk.js +3 -1
  163. package/dist/smart-order.d.ts +1 -1
  164. package/dist/strategies/dca.d.ts +1 -1
  165. package/dist/strategies/funding-arb.d.ts +1 -1
  166. package/dist/strategies/funding-arb.js +193 -181
  167. package/dist/strategies/grid.d.ts +1 -1
  168. package/dist/strategies/grid.js +1 -1
  169. package/dist/strategies/index.d.ts +12 -0
  170. package/dist/strategies/index.js +7 -0
  171. package/dist/strategies/split-order.d.ts +1 -1
  172. package/dist/strategies/trailing-stop.d.ts +1 -1
  173. package/dist/strategies/twap.d.ts +1 -1
  174. package/dist/trade-validator.d.ts +1 -1
  175. package/package.json +7 -1
  176. package/skills/perp-cli/SKILL.md +20 -0
  177. package/skills/perp-cli/references/commands.md +22 -8
@@ -0,0 +1,99 @@
1
+ import { registerStrategy } from "../strategy-registry.js";
2
+ export class GridMmStrategy {
3
+ name = "grid-mm";
4
+ describe() {
5
+ return {
6
+ description: "Enhanced grid market making with per-level profit tracking",
7
+ params: [
8
+ { name: "gridSpacing", type: "number", required: true, description: "Spacing between levels in basis points" },
9
+ { name: "levels", type: "number", required: false, default: 5, description: "Number of levels each side" },
10
+ { name: "size", type: "number", required: true, description: "Order size per level" },
11
+ { name: "rebalancePct", type: "number", required: false, default: 3, description: "Rebalance threshold percentage" },
12
+ ],
13
+ };
14
+ }
15
+ get params() {
16
+ return this._config;
17
+ }
18
+ _config = {};
19
+ async init(ctx, snapshot) {
20
+ this._config = ctx.config;
21
+ ctx.state.set("gridCenter", snapshot.price);
22
+ ctx.state.set("levelProfits", new Map());
23
+ ctx.state.set("totalPnl", 0);
24
+ ctx.state.set("rebalanceCount", 0);
25
+ const p = this.params;
26
+ ctx.log(` [GRID-MM] spacing=${p.gridSpacing}bps, levels=${p.levels}, size=${p.size}, rebalance=${p.rebalancePct}%`);
27
+ }
28
+ async onTick(ctx, snapshot) {
29
+ const params = this.params;
30
+ const actions = [];
31
+ const gridCenter = ctx.state.get("gridCenter") ?? snapshot.price;
32
+ const levels = params.levels || 5;
33
+ const spacingFrac = params.gridSpacing / 10000;
34
+ // Check if rebalance needed
35
+ const deviation = Math.abs(snapshot.price - gridCenter) / gridCenter;
36
+ if (deviation > (params.rebalancePct || 3) / 100) {
37
+ const rebalanceCount = (ctx.state.get("rebalanceCount") ?? 0) + 1;
38
+ ctx.state.set("rebalanceCount", rebalanceCount);
39
+ ctx.state.set("gridCenter", snapshot.price);
40
+ ctx.log(` [GRID-MM] Rebalance #${rebalanceCount}: center moved to $${snapshot.price.toFixed(2)} (deviation: ${(deviation * 100).toFixed(2)}%)`);
41
+ }
42
+ const center = ctx.state.get("gridCenter") ?? snapshot.price;
43
+ // Cancel all existing orders then place fresh grid
44
+ actions.push({ type: "cancel_all" });
45
+ // Place bids below center and asks above center (both sides always)
46
+ for (let i = 1; i <= levels; i++) {
47
+ const bidPrice = center * (1 - spacingFrac * i);
48
+ const askPrice = center * (1 + spacingFrac * i);
49
+ actions.push({
50
+ type: "place_order",
51
+ side: "buy",
52
+ price: bidPrice.toFixed(6),
53
+ size: String(params.size),
54
+ orderType: "limit",
55
+ });
56
+ actions.push({
57
+ type: "place_order",
58
+ side: "sell",
59
+ price: askPrice.toFixed(6),
60
+ size: String(params.size),
61
+ orderType: "limit",
62
+ });
63
+ }
64
+ // Track per-level profit (simplified: estimate from price crossing levels)
65
+ const prevPrice = ctx.state.get("prevPrice") ?? snapshot.price;
66
+ const levelProfits = ctx.state.get("levelProfits") ?? new Map();
67
+ for (let i = 1; i <= levels; i++) {
68
+ const bidLevel = center * (1 - spacingFrac * i);
69
+ const askLevel = center * (1 + spacingFrac * i);
70
+ if (!levelProfits.has(i)) {
71
+ levelProfits.set(i, { fills: 0, pnl: 0 });
72
+ }
73
+ const lp = levelProfits.get(i);
74
+ // Price crossed bid level downward: bid filled
75
+ if (prevPrice > bidLevel && snapshot.price <= bidLevel) {
76
+ lp.fills++;
77
+ lp.pnl -= bidLevel * params.size; // cost basis
78
+ }
79
+ // Price crossed ask level upward: ask filled
80
+ if (prevPrice < askLevel && snapshot.price >= askLevel) {
81
+ lp.fills++;
82
+ lp.pnl += askLevel * params.size; // revenue
83
+ }
84
+ }
85
+ ctx.state.set("prevPrice", snapshot.price);
86
+ // Log per-level stats periodically
87
+ if (ctx.tick > 0 && ctx.tick % 20 === 0) {
88
+ let totalFills = 0;
89
+ for (const [, lp] of levelProfits)
90
+ totalFills += lp.fills;
91
+ ctx.log(` [GRID-MM] tick=${ctx.tick}, center=$${center.toFixed(2)}, totalFills=${totalFills}`);
92
+ }
93
+ return actions;
94
+ }
95
+ async onStop(_ctx) {
96
+ return [{ type: "cancel_all" }];
97
+ }
98
+ }
99
+ registerStrategy("grid-mm", (_config) => new GridMmStrategy());
@@ -0,0 +1,39 @@
1
+ import type { Strategy, StrategyContext, StrategyAction, EnrichedSnapshot } from "../strategy-types.js";
2
+ export declare class GridStrategy implements Strategy {
3
+ readonly name = "grid";
4
+ describe(): {
5
+ description: string;
6
+ params: ({
7
+ name: string;
8
+ type: "number";
9
+ required: boolean;
10
+ description: string;
11
+ default?: undefined;
12
+ } | {
13
+ name: string;
14
+ type: "string";
15
+ required: boolean;
16
+ default: string;
17
+ description: string;
18
+ } | {
19
+ name: string;
20
+ type: "number";
21
+ required: boolean;
22
+ default: number;
23
+ description: string;
24
+ } | {
25
+ name: string;
26
+ type: "boolean";
27
+ required: boolean;
28
+ default: boolean;
29
+ description: string;
30
+ })[];
31
+ };
32
+ private get params();
33
+ private _config;
34
+ init(ctx: StrategyContext, snapshot: EnrichedSnapshot): Promise<void>;
35
+ onTick(ctx: StrategyContext, snapshot: EnrichedSnapshot): Promise<StrategyAction[]>;
36
+ onStop(_ctx: StrategyContext): Promise<StrategyAction[]>;
37
+ /** Build initial grid order actions */
38
+ private buildGridOrders;
39
+ }
@@ -0,0 +1,176 @@
1
+ import { registerStrategy } from "../strategy-registry.js";
2
+ export class GridStrategy {
3
+ name = "grid";
4
+ describe() {
5
+ return {
6
+ description: "Grid trading with fixed intervals",
7
+ params: [
8
+ { name: "grids", type: "number", required: true, description: "Number of grid lines" },
9
+ { name: "size", type: "number", required: true, description: "Total position size" },
10
+ { name: "side", type: "string", required: false, default: "neutral", description: "long | short | neutral" },
11
+ { name: "range_mode", type: "string", required: false, default: "auto", description: "auto | fixed" },
12
+ { name: "range_pct", type: "number", required: false, default: 3, description: "Auto range +/- percentage" },
13
+ { name: "upper", type: "number", required: false, description: "Fixed mode upper bound" },
14
+ { name: "lower", type: "number", required: false, description: "Fixed mode lower bound" },
15
+ { name: "rebalance", type: "boolean", required: false, default: true, description: "Auto-rebalance on range exit" },
16
+ { name: "rebalance_cooldown", type: "number", required: false, default: 60, description: "Seconds between rebalances" },
17
+ { name: "leverage", type: "number", required: false, description: "Leverage multiplier" },
18
+ ],
19
+ };
20
+ }
21
+ get params() {
22
+ return this._config;
23
+ }
24
+ _config = {};
25
+ async init(ctx, snapshot) {
26
+ this._config = ctx.config;
27
+ const params = this.params;
28
+ // Determine price range
29
+ if (params.range_mode === "auto") {
30
+ const pct = params.range_pct ?? 3;
31
+ const upper = snapshot.price * (1 + pct / 100);
32
+ const lower = snapshot.price * (1 - pct / 100);
33
+ ctx.state.set("gridUpper", upper);
34
+ ctx.state.set("gridLower", lower);
35
+ ctx.log(` [GRID] Auto range: $${lower.toFixed(2)} - $${upper.toFixed(2)} (+/-${pct}%)`);
36
+ }
37
+ else {
38
+ if (!params.upper || !params.lower)
39
+ throw new Error("Fixed grid requires upper and lower");
40
+ ctx.state.set("gridUpper", params.upper);
41
+ ctx.state.set("gridLower", params.lower);
42
+ }
43
+ ctx.state.set("gridOrders", new Map());
44
+ }
45
+ async onTick(ctx, snapshot) {
46
+ const params = this.params;
47
+ const actions = [];
48
+ const gridOrders = ctx.state.get("gridOrders");
49
+ const gridUpper = ctx.state.get("gridUpper");
50
+ const gridLower = ctx.state.get("gridLower");
51
+ // First tick: set leverage + place initial grid
52
+ if (ctx.tick === 0) {
53
+ if (params.leverage) {
54
+ actions.push({ type: "set_leverage", leverage: params.leverage });
55
+ }
56
+ return [...actions, ...this.buildGridOrders(ctx, snapshot.price)];
57
+ }
58
+ if (params.grids < 2)
59
+ throw new Error("Grid requires at least 2 grid lines");
60
+ const step = (gridUpper - gridLower) / (params.grids - 1);
61
+ const sizePerGrid = params.size / params.grids;
62
+ // Check for fills
63
+ try {
64
+ const openOrders = await ctx.adapter.getOpenOrders();
65
+ const openIds = new Set(openOrders.filter(o => o.symbol.toUpperCase() === ctx.symbol.toUpperCase()).map(o => o.orderId));
66
+ // Build filled order IDs from order history for accurate fill detection
67
+ let filledIds = null;
68
+ try {
69
+ const history = await ctx.adapter.getOrderHistory(100);
70
+ filledIds = new Set(history.filter(o => o.status === "filled").map(o => o.orderId));
71
+ }
72
+ catch { /* non-critical -- fall back to assuming fills */ }
73
+ let newFills = 0;
74
+ for (const [idx, orderId] of gridOrders.entries()) {
75
+ if (!openIds.has(orderId)) {
76
+ // Verify the order was actually filled (not just cancelled)
77
+ if (filledIds && !filledIds.has(orderId)) {
78
+ ctx.log(` [GRID] Order ${orderId} missing -- likely cancelled, skipping`);
79
+ gridOrders.delete(idx);
80
+ continue;
81
+ }
82
+ // Order filled -- place opposite order
83
+ newFills++;
84
+ // Determine new side and price
85
+ const oldPrice = gridLower + step * idx;
86
+ const wasBuy = oldPrice < snapshot.price;
87
+ const newSide = wasBuy ? "sell" : "buy";
88
+ const newPrice = wasBuy ? oldPrice + step : oldPrice - step;
89
+ if (newPrice >= gridLower && newPrice <= gridUpper) {
90
+ actions.push({
91
+ type: "place_order",
92
+ side: newSide,
93
+ price: newPrice.toFixed(2),
94
+ size: String(sizePerGrid),
95
+ orderType: "limit",
96
+ });
97
+ // We'll update gridOrders when the action is executed
98
+ // For now mark this index as pending -- use a sentinel
99
+ gridOrders.set(idx, `pending-${idx}`);
100
+ }
101
+ else {
102
+ gridOrders.delete(idx);
103
+ }
104
+ }
105
+ }
106
+ if (newFills > 0) {
107
+ ctx.log(` [GRID] ${newFills} fill(s) detected`);
108
+ }
109
+ }
110
+ catch { /* retry next loop */ }
111
+ // Auto-rebalance if price exits range
112
+ if (params.rebalance) {
113
+ const lastRebalance = ctx.state.get("lastRebalance") ?? 0;
114
+ const outOfRange = snapshot.price > gridUpper || snapshot.price < gridLower;
115
+ const cooldownOk = Date.now() - lastRebalance > params.rebalance_cooldown * 1000;
116
+ if (outOfRange && cooldownOk) {
117
+ const pct = params.range_pct ?? 3;
118
+ const newUpper = snapshot.price * (1 + pct / 100);
119
+ const newLower = snapshot.price * (1 - pct / 100);
120
+ ctx.state.set("gridUpper", newUpper);
121
+ ctx.state.set("gridLower", newLower);
122
+ ctx.state.set("lastRebalance", Date.now());
123
+ const rebalanceCount = (ctx.state.get("rebalanceCount") ?? 0) + 1;
124
+ ctx.state.set("rebalanceCount", rebalanceCount);
125
+ ctx.log(` [GRID] Rebalance #${rebalanceCount}: price $${snapshot.price.toFixed(2)} outside range -> new $${newLower.toFixed(2)} - $${newUpper.toFixed(2)}`);
126
+ // Cancel all and rebuild
127
+ actions.push({ type: "cancel_all" });
128
+ gridOrders.clear();
129
+ return [...actions, ...this.buildGridOrders(ctx, snapshot.price)];
130
+ }
131
+ }
132
+ return actions;
133
+ }
134
+ async onStop(_ctx) {
135
+ return [{ type: "cancel_all" }];
136
+ }
137
+ /** Build initial grid order actions */
138
+ buildGridOrders(ctx, currentPrice) {
139
+ const params = this.params;
140
+ const gridUpper = ctx.state.get("gridUpper");
141
+ const gridLower = ctx.state.get("gridLower");
142
+ if (params.grids < 2)
143
+ throw new Error("Grid requires at least 2 grid lines");
144
+ const step = (gridUpper - gridLower) / (params.grids - 1);
145
+ const sizePerGrid = params.size / params.grids;
146
+ const actions = [];
147
+ // Cancel existing orders first
148
+ const gridOrders = ctx.state.get("gridOrders");
149
+ if (gridOrders.size > 0) {
150
+ actions.push({ type: "cancel_all" });
151
+ gridOrders.clear();
152
+ }
153
+ for (let i = 0; i < params.grids; i++) {
154
+ const price = gridLower + step * i;
155
+ let side;
156
+ if (params.side === "long")
157
+ side = "buy";
158
+ else if (params.side === "short")
159
+ side = "sell";
160
+ else
161
+ side = price < currentPrice ? "buy" : "sell";
162
+ actions.push({
163
+ type: "place_order",
164
+ side,
165
+ price: price.toFixed(2),
166
+ size: String(sizePerGrid),
167
+ orderType: "limit",
168
+ });
169
+ // Mark grid index with pending sentinel -- engine updates after execution
170
+ gridOrders.set(i, `pending-${i}`);
171
+ }
172
+ ctx.log(` [GRID] Placing ${params.grids} orders (step: $${step.toFixed(2)}, size: ${sizePerGrid.toFixed(6)})`);
173
+ return actions;
174
+ }
175
+ }
176
+ registerStrategy("grid", (_config) => new GridStrategy());
@@ -0,0 +1,25 @@
1
+ import type { Strategy, StrategyContext, StrategyAction, EnrichedSnapshot } from "../strategy-types.js";
2
+ export declare class HedgeAgentStrategy implements Strategy {
3
+ readonly name = "hedge-agent";
4
+ private _config;
5
+ private get params();
6
+ describe(): {
7
+ description: string;
8
+ params: ({
9
+ name: string;
10
+ type: "number";
11
+ required: boolean;
12
+ description: string;
13
+ default?: undefined;
14
+ } | {
15
+ name: string;
16
+ type: "number";
17
+ required: boolean;
18
+ default: number;
19
+ description: string;
20
+ })[];
21
+ };
22
+ init(ctx: StrategyContext, _snapshot: EnrichedSnapshot): Promise<void>;
23
+ onTick(ctx: StrategyContext, snapshot: EnrichedSnapshot): Promise<StrategyAction[]>;
24
+ onStop(_ctx: StrategyContext): Promise<StrategyAction[]>;
25
+ }
@@ -0,0 +1,79 @@
1
+ import { registerStrategy } from "../strategy-registry.js";
2
+ export class HedgeAgentStrategy {
3
+ name = "hedge-agent";
4
+ _config = {};
5
+ get params() {
6
+ return {
7
+ maxNotionalUsd: Number(this._config.maxNotionalUsd ?? 1000),
8
+ hedgeRatio: Number(this._config.hedgeRatio ?? 0.5),
9
+ checkIntervalTicks: Number(this._config.checkIntervalTicks ?? 5),
10
+ };
11
+ }
12
+ describe() {
13
+ return {
14
+ description: "Monitors portfolio net notional and hedges when exposure exceeds threshold",
15
+ params: [
16
+ { name: "maxNotionalUsd", type: "number", required: true, description: "Max net notional USD before hedging" },
17
+ { name: "hedgeRatio", type: "number", required: false, default: 0.5, description: "Fraction of excess exposure to hedge (0–1)" },
18
+ { name: "checkIntervalTicks", type: "number", required: false, default: 5, description: "Ticks between exposure checks" },
19
+ ],
20
+ };
21
+ }
22
+ async init(ctx, _snapshot) {
23
+ this._config = ctx.config;
24
+ ctx.state.set("lastNetNotional", 0);
25
+ ctx.log(` [HEDGE-AGENT] maxNotional=$${this.params.maxNotionalUsd} ` +
26
+ `hedgeRatio=${this.params.hedgeRatio} checkInterval=${this.params.checkIntervalTicks}ticks`);
27
+ }
28
+ async onTick(ctx, snapshot) {
29
+ const { maxNotionalUsd, hedgeRatio, checkIntervalTicks } = this.params;
30
+ // Only check every N ticks
31
+ if (ctx.tick % checkIntervalTicks !== 0) {
32
+ return [{ type: "noop" }];
33
+ }
34
+ let positions;
35
+ try {
36
+ positions = await ctx.adapter.getPositions();
37
+ }
38
+ catch (err) {
39
+ const msg = err instanceof Error ? err.message : String(err);
40
+ ctx.log(` [HEDGE-AGENT] Failed to fetch positions: ${msg}`);
41
+ return [{ type: "noop" }];
42
+ }
43
+ // Calculate net notional: long = positive, short = negative
44
+ let netNotional = 0;
45
+ for (const pos of positions) {
46
+ const size = parseFloat(pos.size);
47
+ const mark = parseFloat(pos.markPrice) || snapshot.price;
48
+ const notional = size * mark;
49
+ netNotional += pos.side === "long" ? notional : -notional;
50
+ }
51
+ ctx.state.set("lastNetNotional", netNotional);
52
+ ctx.log(` [HEDGE-AGENT] net notional: $${netNotional.toFixed(2)} (limit: $${maxNotionalUsd})`);
53
+ const absNet = Math.abs(netNotional);
54
+ if (absNet <= maxNotionalUsd) {
55
+ return [{ type: "noop" }];
56
+ }
57
+ const excess = absNet - maxNotionalUsd;
58
+ const hedgeNotional = excess * hedgeRatio;
59
+ const hedgeSize = hedgeNotional / snapshot.price;
60
+ // Hedge on the opposite side to reduce net exposure
61
+ const hedgeSide = netNotional > 0 ? "sell" : "buy";
62
+ ctx.log(` [HEDGE-AGENT] Excess $${excess.toFixed(2)} — placing ${hedgeSide} hedge ` +
63
+ `size=${hedgeSize.toFixed(6)} at market`);
64
+ return [
65
+ {
66
+ type: "place_order",
67
+ side: hedgeSide,
68
+ price: snapshot.price.toFixed(6),
69
+ size: hedgeSize.toFixed(6),
70
+ orderType: "market",
71
+ reduceOnly: false,
72
+ },
73
+ ];
74
+ }
75
+ async onStop(_ctx) {
76
+ return [{ type: "cancel_all" }];
77
+ }
78
+ }
79
+ registerStrategy("hedge-agent", (_config) => new HedgeAgentStrategy());
@@ -0,0 +1,25 @@
1
+ import type { Strategy, StrategyContext, StrategyAction, EnrichedSnapshot } from "../strategy-types.js";
2
+ export declare class LiquidationMmStrategy implements Strategy {
3
+ readonly name = "liquidation-mm";
4
+ describe(): {
5
+ description: string;
6
+ params: ({
7
+ name: string;
8
+ type: "number";
9
+ required: boolean;
10
+ description: string;
11
+ default?: undefined;
12
+ } | {
13
+ name: string;
14
+ type: "number";
15
+ required: boolean;
16
+ default: number;
17
+ description: string;
18
+ })[];
19
+ };
20
+ private get params();
21
+ private _config;
22
+ init(ctx: StrategyContext, snapshot: EnrichedSnapshot): Promise<void>;
23
+ onTick(ctx: StrategyContext, snapshot: EnrichedSnapshot): Promise<StrategyAction[]>;
24
+ onStop(_ctx: StrategyContext): Promise<StrategyAction[]>;
25
+ }
@@ -0,0 +1,108 @@
1
+ import { registerStrategy } from "../strategy-registry.js";
2
+ export class LiquidationMmStrategy {
3
+ name = "liquidation-mm";
4
+ describe() {
5
+ return {
6
+ description: "Cascade liquidity provider that widens spread and increases size on OI drops",
7
+ params: [
8
+ { name: "normalSpread", type: "number", required: true, description: "Normal mode spread in basis points" },
9
+ { name: "cascadeSpread", type: "number", required: true, description: "Cascade mode spread in basis points" },
10
+ { name: "cascadeOiDropPct", type: "number", required: false, default: 5, description: "OI drop % to trigger cascade mode" },
11
+ { name: "size", type: "number", required: true, description: "Base order size" },
12
+ { name: "cascadeMultiplier", type: "number", required: false, default: 3, description: "Size multiplier in cascade mode" },
13
+ ],
14
+ };
15
+ }
16
+ get params() {
17
+ return this._config;
18
+ }
19
+ _config = {};
20
+ async init(ctx, snapshot) {
21
+ this._config = ctx.config;
22
+ ctx.state.set("prevOi", parseFloat(snapshot.openInterest) || 0);
23
+ ctx.state.set("cascadeMode", false);
24
+ ctx.state.set("cascadeTicks", 0);
25
+ const p = this.params;
26
+ ctx.log(` [LIQUIDATION-MM] normalSpread=${p.normalSpread}bps, cascadeSpread=${p.cascadeSpread}bps, oiDrop=${p.cascadeOiDropPct || 5}%, mult=${p.cascadeMultiplier || 3}x`);
27
+ }
28
+ async onTick(ctx, snapshot) {
29
+ const params = this.params;
30
+ const actions = [];
31
+ actions.push({ type: "cancel_all" });
32
+ const currentOi = parseFloat(snapshot.openInterest) || 0;
33
+ const prevOi = ctx.state.get("prevOi") ?? currentOi;
34
+ let cascadeMode = ctx.state.get("cascadeMode") ?? false;
35
+ let cascadeTicks = ctx.state.get("cascadeTicks") ?? 0;
36
+ // Detect cascade: OI drop exceeds threshold
37
+ const oiDropThreshold = (params.cascadeOiDropPct || 5) / 100;
38
+ if (prevOi > 0 && currentOi > 0) {
39
+ const oiChange = (currentOi - prevOi) / prevOi;
40
+ if (oiChange < -oiDropThreshold) {
41
+ if (!cascadeMode) {
42
+ ctx.log(` [LIQUIDATION-MM] CASCADE triggered: OI drop ${(oiChange * 100).toFixed(2)}% (threshold: -${(oiDropThreshold * 100).toFixed(1)}%)`);
43
+ }
44
+ cascadeMode = true;
45
+ cascadeTicks = 0; // reset decay timer
46
+ }
47
+ }
48
+ // Cascade mode decays after 10 ticks
49
+ if (cascadeMode) {
50
+ cascadeTicks++;
51
+ if (cascadeTicks > 10) {
52
+ ctx.log(` [LIQUIDATION-MM] CASCADE ended after ${cascadeTicks} ticks`);
53
+ cascadeMode = false;
54
+ cascadeTicks = 0;
55
+ }
56
+ }
57
+ ctx.state.set("prevOi", currentOi);
58
+ ctx.state.set("cascadeMode", cascadeMode);
59
+ ctx.state.set("cascadeTicks", cascadeTicks);
60
+ // Select parameters based on mode
61
+ const spreadBps = cascadeMode ? params.cascadeSpread : params.normalSpread;
62
+ const spreadFrac = spreadBps / 10000;
63
+ const size = cascadeMode ? params.size * (params.cascadeMultiplier || 3) : params.size;
64
+ const mid = snapshot.price;
65
+ const bidPrice = mid * (1 - spreadFrac / 2);
66
+ const askPrice = mid * (1 + spreadFrac / 2);
67
+ actions.push({
68
+ type: "place_order",
69
+ side: "buy",
70
+ price: bidPrice.toFixed(6),
71
+ size: String(size),
72
+ orderType: "limit",
73
+ });
74
+ actions.push({
75
+ type: "place_order",
76
+ side: "sell",
77
+ price: askPrice.toFixed(6),
78
+ size: String(size),
79
+ orderType: "limit",
80
+ });
81
+ // In cascade mode, add extra levels for deeper liquidity
82
+ if (cascadeMode) {
83
+ for (let i = 2; i <= 3; i++) {
84
+ const deepBid = mid * (1 - spreadFrac * i / 2);
85
+ const deepAsk = mid * (1 + spreadFrac * i / 2);
86
+ actions.push({
87
+ type: "place_order",
88
+ side: "buy",
89
+ price: deepBid.toFixed(6),
90
+ size: String(size * 0.5),
91
+ orderType: "limit",
92
+ });
93
+ actions.push({
94
+ type: "place_order",
95
+ side: "sell",
96
+ price: deepAsk.toFixed(6),
97
+ size: String(size * 0.5),
98
+ orderType: "limit",
99
+ });
100
+ }
101
+ }
102
+ return actions;
103
+ }
104
+ async onStop(_ctx) {
105
+ return [{ type: "cancel_all" }];
106
+ }
107
+ }
108
+ registerStrategy("liquidation-mm", (_config) => new LiquidationMmStrategy());
@@ -0,0 +1,35 @@
1
+ import type { Strategy, StrategyContext, StrategyAction, EnrichedSnapshot } from "../strategy-types.js";
2
+ /**
3
+ * Mean reversion strategy.
4
+ * Entry: price deviates > threshold from SMA, confirmed by Bollinger Band touch.
5
+ * - Buy when price touches lower band (oversold).
6
+ * - Sell when price touches upper band (overbought).
7
+ * Exit: price returns to SMA (the mean).
8
+ */
9
+ export declare class MeanReversionStrategy implements Strategy {
10
+ readonly name = "mean-reversion";
11
+ private _config;
12
+ describe(): {
13
+ description: string;
14
+ params: ({
15
+ name: string;
16
+ type: "number";
17
+ required: boolean;
18
+ default: number;
19
+ description: string;
20
+ } | {
21
+ name: string;
22
+ type: "number";
23
+ required: boolean;
24
+ description: string;
25
+ default?: undefined;
26
+ })[];
27
+ };
28
+ private get smaPeriod();
29
+ private get bollingerStdDev();
30
+ private get entryThreshold();
31
+ private get size();
32
+ init(ctx: StrategyContext, _snapshot: EnrichedSnapshot): Promise<void>;
33
+ onTick(ctx: StrategyContext, snapshot: EnrichedSnapshot): Promise<StrategyAction[]>;
34
+ onStop(ctx: StrategyContext): Promise<StrategyAction[]>;
35
+ }