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.
- package/README.md +7 -2
- package/dist/api/public/hyperliquid.d.ts +2 -2
- package/dist/api/public/hyperliquid.js +0 -10
- package/dist/api/public/lighter.d.ts +2 -1
- package/dist/api/public/pacifica.d.ts +2 -1
- package/dist/arb/index.d.ts +7 -4
- package/dist/arb/index.js +5 -4
- package/dist/arb/sizing.d.ts +1 -1
- package/dist/arb/state.d.ts +1 -1
- package/dist/arb/state.js +4 -3
- package/dist/arb/utils.d.ts +0 -10
- package/dist/arb/utils.js +0 -42
- package/dist/bot/apex/guard.d.ts +26 -0
- package/dist/bot/apex/guard.js +86 -0
- package/dist/bot/apex/index.d.ts +6 -0
- package/dist/bot/apex/index.js +4 -0
- package/dist/bot/apex/pulse.d.ts +22 -0
- package/dist/bot/apex/pulse.js +107 -0
- package/dist/bot/apex/radar.d.ts +17 -0
- package/dist/bot/apex/radar.js +150 -0
- package/dist/bot/conditions.d.ts +1 -1
- package/dist/bot/config.d.ts +6 -1
- package/dist/bot/config.js +2 -2
- package/dist/bot/engine.d.ts +23 -2
- package/dist/bot/engine.js +331 -351
- package/dist/bot/index.d.ts +16 -0
- package/dist/bot/index.js +10 -0
- package/dist/bot/indicators.d.ts +29 -0
- package/dist/bot/indicators.js +144 -0
- package/dist/bot/reflect.d.ts +34 -0
- package/dist/bot/reflect.js +155 -0
- package/dist/bot/strategies/aggressive-taker.d.ts +36 -0
- package/dist/bot/strategies/aggressive-taker.js +125 -0
- package/dist/bot/strategies/apex-strategy.d.ts +36 -0
- package/dist/bot/strategies/apex-strategy.js +185 -0
- package/dist/bot/strategies/avellaneda-mm.d.ts +27 -0
- package/dist/bot/strategies/avellaneda-mm.js +111 -0
- package/dist/bot/strategies/basis-arb.d.ts +27 -0
- package/dist/bot/strategies/basis-arb.js +97 -0
- package/dist/bot/strategies/claude-agent.d.ts +31 -0
- package/dist/bot/strategies/claude-agent.js +155 -0
- package/dist/bot/strategies/dca-strategy.d.ts +25 -0
- package/dist/bot/strategies/dca-strategy.js +59 -0
- package/dist/bot/strategies/engine-mm.d.ts +29 -0
- package/dist/bot/strategies/engine-mm.js +141 -0
- package/dist/bot/strategies/funding-arb-strategy.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-strategy.js +168 -0
- package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
- package/dist/bot/strategies/funding-arb-v2.js +222 -0
- package/dist/bot/strategies/funding-auto.d.ts +1 -0
- package/dist/bot/strategies/funding-auto.js +572 -0
- package/dist/bot/strategies/grid-mm.d.ts +25 -0
- package/dist/bot/strategies/grid-mm.js +99 -0
- package/dist/bot/strategies/grid-strategy.d.ts +39 -0
- package/dist/bot/strategies/grid-strategy.js +176 -0
- package/dist/bot/strategies/hedge-agent.d.ts +25 -0
- package/dist/bot/strategies/hedge-agent.js +79 -0
- package/dist/bot/strategies/liquidation-mm.d.ts +25 -0
- package/dist/bot/strategies/liquidation-mm.js +108 -0
- package/dist/bot/strategies/mean-reversion.d.ts +35 -0
- package/dist/bot/strategies/mean-reversion.js +118 -0
- package/dist/bot/strategies/momentum-breakout.d.ts +36 -0
- package/dist/bot/strategies/momentum-breakout.js +159 -0
- package/dist/bot/strategies/regime-mm.d.ts +25 -0
- package/dist/bot/strategies/regime-mm.js +111 -0
- package/dist/bot/strategies/rfq-agent.d.ts +25 -0
- package/dist/bot/strategies/rfq-agent.js +71 -0
- package/dist/bot/strategies/simple-mm.d.ts +25 -0
- package/dist/bot/strategies/simple-mm.js +76 -0
- package/dist/bot/strategy-registry.d.ts +9 -0
- package/dist/bot/strategy-registry.js +14 -0
- package/dist/bot/strategy-types.d.ts +64 -0
- package/dist/bot/strategy-types.js +5 -0
- package/dist/bot/trade-journal.d.ts +27 -0
- package/dist/bot/trade-journal.js +53 -0
- package/dist/bot/tui/BotDashboard.d.ts +56 -0
- package/dist/bot/tui/BotDashboard.js +94 -0
- package/dist/bot/tui/index.d.ts +6 -0
- package/dist/bot/tui/index.js +8 -0
- package/dist/bridge-engine.d.ts +0 -1
- package/dist/bridge-engine.js +0 -1
- package/dist/cache.d.ts +0 -1
- package/dist/cache.js +0 -1
- package/dist/commands/account.d.ts +1 -1
- package/dist/commands/account.js +94 -91
- package/dist/commands/agent.d.ts +1 -1
- package/dist/commands/arb/index.d.ts +1 -1
- package/dist/commands/arb/index.js +1 -1
- package/dist/commands/arb-auto.d.ts +1 -1
- package/dist/commands/arb-auto.js +5 -3
- package/dist/commands/arb.d.ts +2 -1
- package/dist/commands/arb.js +2 -0
- package/dist/commands/bot.d.ts +1 -1
- package/dist/commands/bot.js +236 -15
- package/dist/commands/bridge.js +1 -1
- package/dist/commands/dashboard.d.ts +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/funds.d.ts +1 -1
- package/dist/commands/funds.js +34 -21
- package/dist/commands/history.d.ts +1 -1
- package/dist/commands/history.js +10 -5
- package/dist/commands/manage.d.ts +2 -3
- package/dist/commands/manage.js +11 -18
- package/dist/commands/market.d.ts +1 -1
- package/dist/commands/market.js +26 -17
- package/dist/commands/plan.d.ts +1 -1
- package/dist/commands/rebalance.d.ts +1 -1
- package/dist/commands/rebalance.js +8 -9
- package/dist/commands/risk.d.ts +1 -1
- package/dist/commands/settings.d.ts +1 -1
- package/dist/commands/settings.js +3 -1
- package/dist/commands/trade.d.ts +1 -1
- package/dist/commands/trade.js +42 -55
- package/dist/commands/wallet.d.ts +0 -2
- package/dist/commands/wallet.js +0 -5
- package/dist/constants.d.ts +0 -3
- package/dist/constants.js +0 -4
- package/dist/cross-chain-margin.d.ts +1 -1
- package/dist/dashboard/index.d.ts +4 -0
- package/dist/dashboard/index.js +3 -0
- package/dist/dashboard/server.d.ts +1 -1
- package/dist/dashboard/server.js +1 -1
- package/dist/dashboard/ws-feeds.d.ts +1 -1
- package/dist/event-stream.d.ts +1 -1
- package/dist/exchanges/aster.d.ts +62 -0
- package/dist/exchanges/aster.js +420 -0
- package/dist/exchanges/capabilities.d.ts +74 -0
- package/dist/exchanges/capabilities.js +44 -0
- package/dist/exchanges/edgex.d.ts +62 -0
- package/dist/exchanges/edgex.js +552 -0
- package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
- package/dist/exchanges/hyperliquid-spot.js +20 -12
- package/dist/exchanges/hyperliquid.d.ts +8 -2
- package/dist/exchanges/hyperliquid.js +21 -6
- package/dist/exchanges/index.d.ts +11 -0
- package/dist/exchanges/index.js +10 -0
- package/dist/exchanges/interface.d.ts +3 -0
- package/dist/exchanges/lighter-spot.d.ts +13 -0
- package/dist/exchanges/lighter-spot.js +98 -26
- package/dist/exchanges/lighter.d.ts +9 -3
- package/dist/exchanges/lighter.js +18 -8
- package/dist/exchanges/nado.d.ts +52 -0
- package/dist/exchanges/nado.js +223 -0
- package/dist/exchanges/pacifica.d.ts +7 -1
- package/dist/exchanges/pacifica.js +17 -2
- package/dist/exchanges/registry.d.ts +13 -0
- package/dist/exchanges/registry.js +37 -0
- package/dist/exchanges/spot-interface.d.ts +9 -0
- package/dist/funding/rates.js +1 -1
- package/dist/index.js +2 -6
- package/dist/jobs.js +1 -1
- package/dist/liquidity.d.ts +1 -2
- package/dist/liquidity.js +12 -5
- package/dist/mcp-server.js +4 -2
- package/dist/pacifica/constants.d.ts +0 -3
- package/dist/pacifica/constants.js +0 -4
- package/dist/pacifica/types/account.d.ts +0 -8
- package/dist/pacifica/types/lake.d.ts +0 -6
- package/dist/pacifica/types/position.d.ts +0 -12
- package/dist/pacifica/types/ws.d.ts +0 -24
- package/dist/pacifica/types/ws.js +1 -41
- package/dist/plan-executor.d.ts +1 -1
- package/dist/rebalance.d.ts +1 -7
- package/dist/rebalance.js +0 -6
- package/dist/risk.d.ts +1 -1
- package/dist/risk.js +3 -1
- package/dist/smart-order.d.ts +1 -1
- package/dist/strategies/dca.d.ts +1 -1
- package/dist/strategies/funding-arb.d.ts +1 -1
- package/dist/strategies/funding-arb.js +193 -181
- package/dist/strategies/grid.d.ts +1 -1
- package/dist/strategies/grid.js +1 -1
- package/dist/strategies/index.d.ts +12 -0
- package/dist/strategies/index.js +7 -0
- package/dist/strategies/split-order.d.ts +1 -1
- package/dist/strategies/trailing-stop.d.ts +1 -1
- package/dist/strategies/twap.d.ts +1 -1
- package/dist/trade-validator.d.ts +1 -1
- package/package.json +7 -1
- package/skills/perp-cli/SKILL.md +20 -0
- 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[];
|