perp-cli 0.6.4 → 0.7.2

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 (181) 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 +6 -1
  23. package/dist/bot/config.js +2 -2
  24. package/dist/bot/engine.d.ts +23 -2
  25. package/dist/bot/engine.js +331 -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 +56 -0
  77. package/dist/bot/tui/BotDashboard.js +94 -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 +5 -3
  91. package/dist/commands/arb.d.ts +2 -1
  92. package/dist/commands/arb.js +2 -0
  93. package/dist/commands/bot.d.ts +1 -1
  94. package/dist/commands/bot.js +236 -15
  95. package/dist/commands/bridge.js +1 -1
  96. package/dist/commands/dashboard.d.ts +1 -1
  97. package/dist/commands/dashboard.js +1 -1
  98. package/dist/commands/funds.d.ts +1 -1
  99. package/dist/commands/funds.js +34 -21
  100. package/dist/commands/history.d.ts +1 -1
  101. package/dist/commands/history.js +10 -5
  102. package/dist/commands/manage.d.ts +2 -3
  103. package/dist/commands/manage.js +11 -18
  104. package/dist/commands/market.d.ts +1 -1
  105. package/dist/commands/market.js +26 -17
  106. package/dist/commands/plan.d.ts +1 -1
  107. package/dist/commands/rebalance.d.ts +1 -1
  108. package/dist/commands/rebalance.js +8 -9
  109. package/dist/commands/risk.d.ts +1 -1
  110. package/dist/commands/settings.d.ts +1 -1
  111. package/dist/commands/settings.js +3 -1
  112. package/dist/commands/trade.d.ts +1 -1
  113. package/dist/commands/trade.js +42 -55
  114. package/dist/commands/wallet.d.ts +0 -2
  115. package/dist/commands/wallet.js +0 -5
  116. package/dist/constants.d.ts +0 -3
  117. package/dist/constants.js +0 -4
  118. package/dist/cross-chain-margin.d.ts +1 -1
  119. package/dist/dashboard/index.d.ts +4 -0
  120. package/dist/dashboard/index.js +3 -0
  121. package/dist/dashboard/server.d.ts +1 -1
  122. package/dist/dashboard/server.js +1 -1
  123. package/dist/dashboard/ws-feeds.d.ts +1 -1
  124. package/dist/event-stream.d.ts +1 -1
  125. package/dist/exchanges/aster.d.ts +62 -0
  126. package/dist/exchanges/aster.js +420 -0
  127. package/dist/exchanges/capabilities.d.ts +74 -0
  128. package/dist/exchanges/capabilities.js +44 -0
  129. package/dist/exchanges/edgex.d.ts +62 -0
  130. package/dist/exchanges/edgex.js +552 -0
  131. package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
  132. package/dist/exchanges/hyperliquid-spot.js +20 -12
  133. package/dist/exchanges/hyperliquid.d.ts +8 -2
  134. package/dist/exchanges/hyperliquid.js +21 -6
  135. package/dist/exchanges/index.d.ts +11 -0
  136. package/dist/exchanges/index.js +10 -0
  137. package/dist/exchanges/interface.d.ts +3 -0
  138. package/dist/exchanges/lighter-spot.d.ts +13 -0
  139. package/dist/exchanges/lighter-spot.js +98 -26
  140. package/dist/exchanges/lighter.d.ts +9 -3
  141. package/dist/exchanges/lighter.js +18 -8
  142. package/dist/exchanges/nado.d.ts +52 -0
  143. package/dist/exchanges/nado.js +223 -0
  144. package/dist/exchanges/pacifica.d.ts +7 -1
  145. package/dist/exchanges/pacifica.js +17 -2
  146. package/dist/exchanges/registry.d.ts +13 -0
  147. package/dist/exchanges/registry.js +37 -0
  148. package/dist/exchanges/spot-interface.d.ts +9 -0
  149. package/dist/funding/rates.js +1 -1
  150. package/dist/index.js +2 -6
  151. package/dist/jobs.js +1 -1
  152. package/dist/liquidity.d.ts +1 -2
  153. package/dist/liquidity.js +12 -5
  154. package/dist/mcp-server.js +4 -2
  155. package/dist/pacifica/constants.d.ts +0 -3
  156. package/dist/pacifica/constants.js +0 -4
  157. package/dist/pacifica/types/account.d.ts +0 -8
  158. package/dist/pacifica/types/lake.d.ts +0 -6
  159. package/dist/pacifica/types/position.d.ts +0 -12
  160. package/dist/pacifica/types/ws.d.ts +0 -24
  161. package/dist/pacifica/types/ws.js +1 -41
  162. package/dist/plan-executor.d.ts +1 -1
  163. package/dist/rebalance.d.ts +1 -7
  164. package/dist/rebalance.js +0 -6
  165. package/dist/risk.d.ts +1 -1
  166. package/dist/risk.js +3 -1
  167. package/dist/smart-order.d.ts +1 -1
  168. package/dist/strategies/dca.d.ts +1 -1
  169. package/dist/strategies/funding-arb.d.ts +1 -1
  170. package/dist/strategies/funding-arb.js +193 -181
  171. package/dist/strategies/grid.d.ts +1 -1
  172. package/dist/strategies/grid.js +1 -1
  173. package/dist/strategies/index.d.ts +12 -0
  174. package/dist/strategies/index.js +7 -0
  175. package/dist/strategies/split-order.d.ts +1 -1
  176. package/dist/strategies/trailing-stop.d.ts +1 -1
  177. package/dist/strategies/twap.d.ts +1 -1
  178. package/dist/trade-validator.d.ts +1 -1
  179. package/package.json +7 -1
  180. package/skills/perp-cli/SKILL.md +20 -0
  181. package/skills/perp-cli/references/commands.md +22 -8
@@ -0,0 +1,118 @@
1
+ import { registerStrategy } from "../strategy-registry.js";
2
+ import { calculateSMA, calculateBollingerBands } from "../indicators.js";
3
+ /**
4
+ * Mean reversion strategy.
5
+ * Entry: price deviates > threshold from SMA, confirmed by Bollinger Band touch.
6
+ * - Buy when price touches lower band (oversold).
7
+ * - Sell when price touches upper band (overbought).
8
+ * Exit: price returns to SMA (the mean).
9
+ */
10
+ export class MeanReversionStrategy {
11
+ name = "mean-reversion";
12
+ _config = {};
13
+ describe() {
14
+ return {
15
+ description: "Mean reversion using Bollinger Bands — buy at lower band, sell at upper band, exit at mean",
16
+ params: [
17
+ { name: "smaPeriod", type: "number", required: false, default: 20, description: "SMA lookback period" },
18
+ { name: "bollingerStdDev", type: "number", required: false, default: 2, description: "Bollinger Bands std dev multiplier" },
19
+ { name: "entryThreshold", type: "number", required: false, default: 0, description: "Extra threshold beyond band (0 = band touch)" },
20
+ { name: "size", type: "number", required: true, description: "Position size" },
21
+ ],
22
+ };
23
+ }
24
+ get smaPeriod() { return Number(this._config.smaPeriod ?? 20); }
25
+ get bollingerStdDev() { return Number(this._config.bollingerStdDev ?? 2); }
26
+ get entryThreshold() { return Number(this._config.entryThreshold ?? 0); }
27
+ get size() { return Number(this._config.size ?? 0.1); }
28
+ async init(ctx, _snapshot) {
29
+ this._config = ctx.config;
30
+ ctx.state.set("position", null); // null | { side, entry }
31
+ ctx.log(` [MEAN-REV] Ready | sma=${this.smaPeriod} stdDev=${this.bollingerStdDev} threshold=${this.entryThreshold}`);
32
+ }
33
+ async onTick(ctx, snapshot) {
34
+ const actions = [];
35
+ const klines = snapshot.klines;
36
+ if (klines.length < this.smaPeriod) {
37
+ ctx.log(` [MEAN-REV] Insufficient klines (${klines.length}/${this.smaPeriod})`);
38
+ return [];
39
+ }
40
+ const closes = klines.map(k => parseFloat(k.close));
41
+ const price = snapshot.price;
42
+ // Compute indicators
43
+ const smaValues = calculateSMA(closes, this.smaPeriod);
44
+ const currentSMA = smaValues.length > 0 ? smaValues[smaValues.length - 1] : price;
45
+ const bands = calculateBollingerBands(closes, this.smaPeriod, this.bollingerStdDev);
46
+ const upperBand = bands.upper.length > 0 ? bands.upper[bands.upper.length - 1] : price;
47
+ const lowerBand = bands.lower.length > 0 ? bands.lower[bands.lower.length - 1] : price;
48
+ const position = ctx.state.get("position");
49
+ // If no position, check for entry signals
50
+ if (!position) {
51
+ // Buy signal: price at or below lower band (- threshold)
52
+ if (price <= lowerBand - this.entryThreshold) {
53
+ ctx.state.set("position", { side: "buy", entry: price });
54
+ ctx.log(` [MEAN-REV] Long entry @ $${price.toFixed(2)} (lower band $${lowerBand.toFixed(2)}, mean $${currentSMA.toFixed(2)})`);
55
+ actions.push({
56
+ type: "place_order",
57
+ side: "buy",
58
+ price: price.toFixed(2),
59
+ size: String(this.size),
60
+ orderType: "market",
61
+ });
62
+ }
63
+ // Sell signal: price at or above upper band (+ threshold)
64
+ else if (price >= upperBand + this.entryThreshold) {
65
+ ctx.state.set("position", { side: "sell", entry: price });
66
+ ctx.log(` [MEAN-REV] Short entry @ $${price.toFixed(2)} (upper band $${upperBand.toFixed(2)}, mean $${currentSMA.toFixed(2)})`);
67
+ actions.push({
68
+ type: "place_order",
69
+ side: "sell",
70
+ price: price.toFixed(2),
71
+ size: String(this.size),
72
+ orderType: "market",
73
+ });
74
+ }
75
+ return actions;
76
+ }
77
+ // Manage existing position — exit when price returns to mean
78
+ if (position.side === "buy" && price >= currentSMA) {
79
+ ctx.log(` [MEAN-REV] Close long @ $${price.toFixed(2)} (mean $${currentSMA.toFixed(2)})`);
80
+ ctx.state.set("position", null);
81
+ actions.push({
82
+ type: "place_order",
83
+ side: "sell",
84
+ price: price.toFixed(2),
85
+ size: String(this.size),
86
+ orderType: "market",
87
+ reduceOnly: true,
88
+ });
89
+ }
90
+ else if (position.side === "sell" && price <= currentSMA) {
91
+ ctx.log(` [MEAN-REV] Close short @ $${price.toFixed(2)} (mean $${currentSMA.toFixed(2)})`);
92
+ ctx.state.set("position", null);
93
+ actions.push({
94
+ type: "place_order",
95
+ side: "buy",
96
+ price: price.toFixed(2),
97
+ size: String(this.size),
98
+ orderType: "market",
99
+ reduceOnly: true,
100
+ });
101
+ }
102
+ return actions;
103
+ }
104
+ async onStop(ctx) {
105
+ const position = ctx.state.get("position");
106
+ if (!position)
107
+ return [];
108
+ return [{
109
+ type: "place_order",
110
+ side: position.side === "buy" ? "sell" : "buy",
111
+ price: "0",
112
+ size: String(this.size),
113
+ orderType: "market",
114
+ reduceOnly: true,
115
+ }];
116
+ }
117
+ }
118
+ registerStrategy("mean-reversion", (_config) => new MeanReversionStrategy());
@@ -0,0 +1,36 @@
1
+ import type { Strategy, StrategyContext, StrategyAction, EnrichedSnapshot } from "../strategy-types.js";
2
+ /**
3
+ * Momentum breakout strategy.
4
+ * Entry: price breaks above N-period high (buy) or below N-period low (sell)
5
+ * with volume confirmation (volume > avgVolume * multiplier).
6
+ * Stop: ATR-based trailing stop.
7
+ * Take profit: risk/reward ratio based.
8
+ */
9
+ export declare class MomentumBreakoutStrategy implements Strategy {
10
+ readonly name = "momentum-breakout";
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 lookbackPeriod();
29
+ private get volumeMultiplier();
30
+ private get atrMultiplier();
31
+ private get riskReward();
32
+ private get size();
33
+ init(ctx: StrategyContext, _snapshot: EnrichedSnapshot): Promise<void>;
34
+ onTick(ctx: StrategyContext, snapshot: EnrichedSnapshot): Promise<StrategyAction[]>;
35
+ onStop(ctx: StrategyContext): Promise<StrategyAction[]>;
36
+ }
@@ -0,0 +1,159 @@
1
+ import { registerStrategy } from "../strategy-registry.js";
2
+ import { calculateATR, calculateSMA } from "../indicators.js";
3
+ /**
4
+ * Momentum breakout strategy.
5
+ * Entry: price breaks above N-period high (buy) or below N-period low (sell)
6
+ * with volume confirmation (volume > avgVolume * multiplier).
7
+ * Stop: ATR-based trailing stop.
8
+ * Take profit: risk/reward ratio based.
9
+ */
10
+ export class MomentumBreakoutStrategy {
11
+ name = "momentum-breakout";
12
+ _config = {};
13
+ describe() {
14
+ return {
15
+ description: "Momentum breakout with volume confirmation and ATR trailing stop",
16
+ params: [
17
+ { name: "lookbackPeriod", type: "number", required: false, default: 20, description: "Periods for high/low channel" },
18
+ { name: "volumeMultiplier", type: "number", required: false, default: 1.5, description: "Volume must exceed avg * this" },
19
+ { name: "atrMultiplier", type: "number", required: false, default: 2, description: "ATR multiplier for stop distance" },
20
+ { name: "riskReward", type: "number", required: false, default: 2, description: "Take profit = stop distance * this" },
21
+ { name: "size", type: "number", required: true, description: "Position size" },
22
+ ],
23
+ };
24
+ }
25
+ get lookbackPeriod() { return Number(this._config.lookbackPeriod ?? 20); }
26
+ get volumeMultiplier() { return Number(this._config.volumeMultiplier ?? 1.5); }
27
+ get atrMultiplier() { return Number(this._config.atrMultiplier ?? 2); }
28
+ get riskReward() { return Number(this._config.riskReward ?? 2); }
29
+ get size() { return Number(this._config.size ?? 0.1); }
30
+ async init(ctx, _snapshot) {
31
+ this._config = ctx.config;
32
+ ctx.state.set("position", null); // null | { side, entry, stop, tp }
33
+ ctx.log(` [BREAKOUT] Ready | lookback=${this.lookbackPeriod} volMult=${this.volumeMultiplier} atrMult=${this.atrMultiplier} rr=${this.riskReward}`);
34
+ }
35
+ async onTick(ctx, snapshot) {
36
+ const actions = [];
37
+ const klines = snapshot.klines;
38
+ if (klines.length < this.lookbackPeriod + 1) {
39
+ ctx.log(` [BREAKOUT] Insufficient klines (${klines.length}/${this.lookbackPeriod + 1})`);
40
+ return [];
41
+ }
42
+ const closes = klines.map(k => parseFloat(k.close));
43
+ const highs = klines.map(k => parseFloat(k.high));
44
+ const lows = klines.map(k => parseFloat(k.low));
45
+ const volumes = klines.map(k => parseFloat(k.volume));
46
+ const price = snapshot.price;
47
+ // Compute indicators
48
+ const candles = klines.map(k => ({
49
+ high: parseFloat(k.high),
50
+ low: parseFloat(k.low),
51
+ close: parseFloat(k.close),
52
+ }));
53
+ const atrValues = calculateATR(candles, this.lookbackPeriod);
54
+ const currentATR = atrValues.length > 0 ? atrValues[atrValues.length - 1] : 0;
55
+ // N-period high/low (excluding current bar)
56
+ const lookbackHighs = highs.slice(-this.lookbackPeriod - 1, -1);
57
+ const lookbackLows = lows.slice(-this.lookbackPeriod - 1, -1);
58
+ const channelHigh = Math.max(...lookbackHighs);
59
+ const channelLow = Math.min(...lookbackLows);
60
+ // Volume confirmation
61
+ const avgVolSma = calculateSMA(volumes, this.lookbackPeriod);
62
+ const avgVolume = avgVolSma.length > 0 ? avgVolSma[avgVolSma.length - 1] : 0;
63
+ const currentVolume = volumes[volumes.length - 1];
64
+ const volumeConfirmed = avgVolume > 0 && currentVolume > avgVolume * this.volumeMultiplier;
65
+ const position = ctx.state.get("position");
66
+ // If no position, look for breakout entry
67
+ if (!position) {
68
+ if (price > channelHigh && volumeConfirmed && currentATR > 0) {
69
+ const stopDistance = currentATR * this.atrMultiplier;
70
+ const stop = price - stopDistance;
71
+ const tp = price + stopDistance * this.riskReward;
72
+ ctx.state.set("position", { side: "buy", entry: price, stop, tp });
73
+ ctx.log(` [BREAKOUT] Long breakout @ $${price.toFixed(2)} | stop $${stop.toFixed(2)} | tp $${tp.toFixed(2)}`);
74
+ actions.push({
75
+ type: "place_order",
76
+ side: "buy",
77
+ price: price.toFixed(2),
78
+ size: String(this.size),
79
+ orderType: "market",
80
+ });
81
+ }
82
+ else if (price < channelLow && volumeConfirmed && currentATR > 0) {
83
+ const stopDistance = currentATR * this.atrMultiplier;
84
+ const stop = price + stopDistance;
85
+ const tp = price - stopDistance * this.riskReward;
86
+ ctx.state.set("position", { side: "sell", entry: price, stop, tp });
87
+ ctx.log(` [BREAKOUT] Short breakout @ $${price.toFixed(2)} | stop $${stop.toFixed(2)} | tp $${tp.toFixed(2)}`);
88
+ actions.push({
89
+ type: "place_order",
90
+ side: "sell",
91
+ price: price.toFixed(2),
92
+ size: String(this.size),
93
+ orderType: "market",
94
+ });
95
+ }
96
+ return actions;
97
+ }
98
+ // Manage existing position — trailing stop + take profit
99
+ if (position.side === "buy") {
100
+ // Trail stop up
101
+ const newStop = price - currentATR * this.atrMultiplier;
102
+ if (newStop > position.stop) {
103
+ position.stop = newStop;
104
+ ctx.state.set("position", position);
105
+ }
106
+ // Check exit
107
+ if (price <= position.stop || price >= position.tp) {
108
+ const reason = price <= position.stop ? "stop" : "take-profit";
109
+ ctx.log(` [BREAKOUT] Close long (${reason}) @ $${price.toFixed(2)}`);
110
+ ctx.state.set("position", null);
111
+ actions.push({
112
+ type: "place_order",
113
+ side: "sell",
114
+ price: price.toFixed(2),
115
+ size: String(this.size),
116
+ orderType: "market",
117
+ reduceOnly: true,
118
+ });
119
+ }
120
+ }
121
+ else {
122
+ // Trail stop down for short
123
+ const newStop = price + currentATR * this.atrMultiplier;
124
+ if (newStop < position.stop) {
125
+ position.stop = newStop;
126
+ ctx.state.set("position", position);
127
+ }
128
+ if (price >= position.stop || price <= position.tp) {
129
+ const reason = price >= position.stop ? "stop" : "take-profit";
130
+ ctx.log(` [BREAKOUT] Close short (${reason}) @ $${price.toFixed(2)}`);
131
+ ctx.state.set("position", null);
132
+ actions.push({
133
+ type: "place_order",
134
+ side: "buy",
135
+ price: price.toFixed(2),
136
+ size: String(this.size),
137
+ orderType: "market",
138
+ reduceOnly: true,
139
+ });
140
+ }
141
+ }
142
+ return actions;
143
+ }
144
+ async onStop(ctx) {
145
+ const position = ctx.state.get("position");
146
+ if (!position)
147
+ return [];
148
+ // Close position on stop
149
+ return [{
150
+ type: "place_order",
151
+ side: position.side === "buy" ? "sell" : "buy",
152
+ price: "0",
153
+ size: String(this.size),
154
+ orderType: "market",
155
+ reduceOnly: true,
156
+ }];
157
+ }
158
+ }
159
+ registerStrategy("momentum-breakout", (_config) => new MomentumBreakoutStrategy());
@@ -0,0 +1,25 @@
1
+ import type { Strategy, StrategyContext, StrategyAction, EnrichedSnapshot } from "../strategy-types.js";
2
+ export declare class RegimeMmStrategy implements Strategy {
3
+ readonly name = "regime-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,111 @@
1
+ import { registerStrategy } from "../strategy-registry.js";
2
+ import { detectRegime } from "../indicators.js";
3
+ /** Per-regime parameter multipliers */
4
+ const REGIME_PARAMS = {
5
+ "low-vol-trending": { spreadMult: 0.8, sizeMult: 1.0, bias: "trend" },
6
+ "low-vol-ranging": { spreadMult: 0.8, sizeMult: 1.0, bias: "symmetric" },
7
+ "high-vol-trending": { spreadMult: 2.0, sizeMult: 0.5, bias: "trend" },
8
+ "high-vol-ranging": { spreadMult: 2.0, sizeMult: 0.7, bias: "aggressive-revert" },
9
+ };
10
+ export class RegimeMmStrategy {
11
+ name = "regime-mm";
12
+ describe() {
13
+ return {
14
+ description: "Volatility-adaptive market making using regime detection",
15
+ params: [
16
+ { name: "baseSpread", type: "number", required: true, description: "Base spread in basis points" },
17
+ { name: "size", type: "number", required: true, description: "Base order size" },
18
+ { name: "regimeWindow", type: "number", required: false, default: 20, description: "Lookback period for regime detection" },
19
+ ],
20
+ };
21
+ }
22
+ get params() {
23
+ return this._config;
24
+ }
25
+ _config = {};
26
+ async init(ctx, _snapshot) {
27
+ this._config = ctx.config;
28
+ ctx.state.set("inventory", 0);
29
+ ctx.state.set("currentRegime", "low-vol-ranging");
30
+ const p = this.params;
31
+ ctx.log(` [REGIME-MM] baseSpread=${p.baseSpread}bps, size=${p.size}, window=${p.regimeWindow || 20}`);
32
+ }
33
+ async onTick(ctx, snapshot) {
34
+ const params = this.params;
35
+ const actions = [];
36
+ actions.push({ type: "cancel_all" });
37
+ // Detect current regime from kline close prices
38
+ const window = params.regimeWindow || 20;
39
+ let regime = "low-vol-ranging";
40
+ if (snapshot.klines.length >= window) {
41
+ const closes = snapshot.klines.map(k => parseFloat(k.close));
42
+ regime = detectRegime(closes, window);
43
+ }
44
+ const prevRegime = ctx.state.get("currentRegime");
45
+ if (regime !== prevRegime) {
46
+ ctx.log(` [REGIME-MM] Regime shift: ${prevRegime} -> ${regime}`);
47
+ ctx.state.set("currentRegime", regime);
48
+ }
49
+ const rp = REGIME_PARAMS[regime];
50
+ const mid = snapshot.price;
51
+ const spreadFrac = (params.baseSpread / 10000) * rp.spreadMult;
52
+ const size = params.size * rp.sizeMult;
53
+ const inventory = ctx.state.get("inventory") ?? 0;
54
+ // Trend bias: compute slope direction from recent prices
55
+ let trendBias = 0;
56
+ if (rp.bias === "trend" && snapshot.klines.length >= 5) {
57
+ const recent = snapshot.klines.slice(-5).map(k => parseFloat(k.close));
58
+ const slope = (recent[recent.length - 1] - recent[0]) / recent[0];
59
+ trendBias = slope * mid * 0.5; // shift quotes in trend direction
60
+ }
61
+ // Aggressive inventory reversion for high-vol-ranging
62
+ let inventoryShift = 0;
63
+ if (rp.bias === "aggressive-revert" && inventory !== 0) {
64
+ inventoryShift = -inventory * spreadFrac * mid * 2;
65
+ }
66
+ else if (inventory !== 0) {
67
+ // Standard inventory management
68
+ inventoryShift = -inventory * spreadFrac * mid * 0.5;
69
+ }
70
+ const bidPrice = mid * (1 - spreadFrac / 2) + trendBias + inventoryShift;
71
+ const askPrice = mid * (1 + spreadFrac / 2) + trendBias + inventoryShift;
72
+ // Adjust sizes: reduce on heavy side
73
+ const maxInv = params.size * 10;
74
+ const invRatio = maxInv > 0 ? Math.abs(inventory) / maxInv : 0;
75
+ const sizePenalty = Math.min(invRatio, 0.8);
76
+ const bidSize = inventory > 0 ? size * (1 - sizePenalty) : size;
77
+ const askSize = inventory < 0 ? size * (1 - sizePenalty) : size;
78
+ if (bidSize > 0 && bidPrice > 0) {
79
+ actions.push({
80
+ type: "place_order",
81
+ side: "buy",
82
+ price: bidPrice.toFixed(6),
83
+ size: String(bidSize),
84
+ orderType: "limit",
85
+ });
86
+ }
87
+ if (askSize > 0 && askPrice > 0) {
88
+ actions.push({
89
+ type: "place_order",
90
+ side: "sell",
91
+ price: askPrice.toFixed(6),
92
+ size: String(askSize),
93
+ orderType: "limit",
94
+ });
95
+ }
96
+ // Update inventory estimate
97
+ const prevMid = ctx.state.get("prevMid") ?? mid;
98
+ if (mid > prevMid) {
99
+ ctx.state.set("inventory", inventory + size * 0.3);
100
+ }
101
+ else if (mid < prevMid) {
102
+ ctx.state.set("inventory", inventory - size * 0.3);
103
+ }
104
+ ctx.state.set("prevMid", mid);
105
+ return actions;
106
+ }
107
+ async onStop(_ctx) {
108
+ return [{ type: "cancel_all" }];
109
+ }
110
+ }
111
+ registerStrategy("regime-mm", (_config) => new RegimeMmStrategy());
@@ -0,0 +1,25 @@
1
+ import type { Strategy, StrategyContext, StrategyAction, EnrichedSnapshot } from "../strategy-types.js";
2
+ export declare class RfqAgentStrategy implements Strategy {
3
+ readonly name = "rfq-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,71 @@
1
+ import { registerStrategy } from "../strategy-registry.js";
2
+ export class RfqAgentStrategy {
3
+ name = "rfq-agent";
4
+ _config = {};
5
+ get params() {
6
+ return {
7
+ totalSize: Number(this._config.totalSize ?? 1),
8
+ sliceSize: Number(this._config.sliceSize ?? 0.1),
9
+ spreadBps: Number(this._config.spreadBps ?? 10),
10
+ delayMs: Number(this._config.delayMs ?? 1000),
11
+ };
12
+ }
13
+ describe() {
14
+ return {
15
+ description: "Iceberg-style large order execution: splits into slices at wider spreads to reduce market impact",
16
+ params: [
17
+ { name: "totalSize", type: "number", required: true, description: "Total order size to execute" },
18
+ { name: "sliceSize", type: "number", required: true, description: "Size of each individual slice" },
19
+ { name: "spreadBps", type: "number", required: false, default: 10, description: "Spread offset per slice in basis points" },
20
+ { name: "delayMs", type: "number", required: false, default: 1000, description: "Minimum ms between slices" },
21
+ ],
22
+ };
23
+ }
24
+ async init(ctx, _snapshot) {
25
+ this._config = ctx.config;
26
+ const { totalSize, sliceSize, spreadBps, delayMs } = this.params;
27
+ ctx.state.set("remainingSize", totalSize);
28
+ ctx.state.set("lastSliceTime", 0);
29
+ const slices = Math.ceil(totalSize / sliceSize);
30
+ ctx.log(` [RFQ-AGENT] totalSize=${totalSize} sliceSize=${sliceSize} ` +
31
+ `spread=${spreadBps}bps delayMs=${delayMs} (~${slices} slices)`);
32
+ }
33
+ async onTick(ctx, snapshot) {
34
+ const { sliceSize, spreadBps, delayMs } = this.params;
35
+ const remainingSize = ctx.state.get("remainingSize");
36
+ const lastSliceTime = ctx.state.get("lastSliceTime");
37
+ if (remainingSize <= 0) {
38
+ ctx.log(" [RFQ-AGENT] All slices placed — execution complete");
39
+ return [{ type: "noop" }];
40
+ }
41
+ const now = Date.now();
42
+ if (now - lastSliceTime < delayMs) {
43
+ return [{ type: "noop" }];
44
+ }
45
+ const thisSlice = Math.min(sliceSize, remainingSize);
46
+ const spreadFraction = spreadBps / 10000;
47
+ // Determine side from config, default buy; offer at wider spread to reduce impact
48
+ const side = ctx.config.side ?? "buy";
49
+ const price = side === "buy"
50
+ ? snapshot.price * (1 - spreadFraction) // bid below mid
51
+ : snapshot.price * (1 + spreadFraction); // ask above mid
52
+ const newRemaining = remainingSize - thisSlice;
53
+ ctx.state.set("remainingSize", newRemaining);
54
+ ctx.state.set("lastSliceTime", now);
55
+ ctx.log(` [RFQ-AGENT] Placing slice ${thisSlice.toFixed(6)} @ ${price.toFixed(6)} ` +
56
+ `(remaining: ${newRemaining.toFixed(6)})`);
57
+ return [
58
+ {
59
+ type: "place_order",
60
+ side,
61
+ price: price.toFixed(6),
62
+ size: thisSlice.toFixed(6),
63
+ orderType: "limit",
64
+ },
65
+ ];
66
+ }
67
+ async onStop(_ctx) {
68
+ return [{ type: "cancel_all" }];
69
+ }
70
+ }
71
+ registerStrategy("rfq-agent", (_config) => new RfqAgentStrategy());
@@ -0,0 +1,25 @@
1
+ import type { Strategy, StrategyContext, StrategyAction, EnrichedSnapshot } from "../strategy-types.js";
2
+ export declare class SimpleMmStrategy implements Strategy {
3
+ readonly name = "simple-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,76 @@
1
+ import { registerStrategy } from "../strategy-registry.js";
2
+ export class SimpleMmStrategy {
3
+ name = "simple-mm";
4
+ describe() {
5
+ return {
6
+ description: "Symmetric bid/ask market making at fixed spread",
7
+ params: [
8
+ { name: "spread", type: "number", required: true, description: "Spread in basis points" },
9
+ { name: "size", type: "number", required: true, description: "Order size per level" },
10
+ { name: "levels", type: "number", required: false, default: 1, description: "Number of levels each side" },
11
+ ],
12
+ };
13
+ }
14
+ get params() {
15
+ return this._config;
16
+ }
17
+ _config = {};
18
+ async init(ctx, _snapshot) {
19
+ this._config = ctx.config;
20
+ ctx.state.set("inventory", 0);
21
+ ctx.log(` [SIMPLE-MM] spread=${this.params.spread}bps, size=${this.params.size}, levels=${this.params.levels}`);
22
+ }
23
+ async onTick(ctx, snapshot) {
24
+ const { spread, size, levels } = this.params;
25
+ const actions = [];
26
+ const inventory = ctx.state.get("inventory") ?? 0;
27
+ // Cancel all existing orders, then re-quote
28
+ actions.push({ type: "cancel_all" });
29
+ const mid = snapshot.price;
30
+ const spreadFraction = spread / 10000; // bps to fraction
31
+ for (let i = 1; i <= (levels || 1); i++) {
32
+ const offset = (spreadFraction / 2) * i;
33
+ const bidPrice = mid * (1 - offset);
34
+ const askPrice = mid * (1 + offset);
35
+ // Simple inventory skew: reduce size on heavy side
36
+ const inventorySkew = Math.min(Math.abs(inventory) / (size * levels * 2), 0.8);
37
+ const bidSize = inventory > 0 ? size * (1 - inventorySkew) : size;
38
+ const askSize = inventory < 0 ? size * (1 - inventorySkew) : size;
39
+ if (bidSize > 0) {
40
+ actions.push({
41
+ type: "place_order",
42
+ side: "buy",
43
+ price: bidPrice.toFixed(6),
44
+ size: String(bidSize),
45
+ orderType: "limit",
46
+ });
47
+ }
48
+ if (askSize > 0) {
49
+ actions.push({
50
+ type: "place_order",
51
+ side: "sell",
52
+ price: askPrice.toFixed(6),
53
+ size: String(askSize),
54
+ orderType: "limit",
55
+ });
56
+ }
57
+ }
58
+ // Track approximate inventory from fills (simplified: assume fills at each tick)
59
+ // Real inventory tracking would use adapter.getPositions()
60
+ const prevMid = ctx.state.get("prevMid") ?? mid;
61
+ if (mid > prevMid) {
62
+ // Price moved up: likely bid was filled
63
+ ctx.state.set("inventory", inventory + size);
64
+ }
65
+ else if (mid < prevMid) {
66
+ // Price moved down: likely ask was filled
67
+ ctx.state.set("inventory", inventory - size);
68
+ }
69
+ ctx.state.set("prevMid", mid);
70
+ return actions;
71
+ }
72
+ async onStop(_ctx) {
73
+ return [{ type: "cancel_all" }];
74
+ }
75
+ }
76
+ registerStrategy("simple-mm", (_config) => new SimpleMmStrategy());
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Strategy registry for bot engine.
3
+ * Strategies register here — engine uses registry to resolve by name.
4
+ */
5
+ import type { Strategy } from "./strategy-types.js";
6
+ export type StrategyFactory = (config: Record<string, unknown>) => Strategy;
7
+ export declare function registerStrategy(name: string, factory: StrategyFactory): void;
8
+ export declare function getStrategy(name: string): StrategyFactory | undefined;
9
+ export declare function listStrategies(): string[];