perp-cli 0.6.3 → 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 +106 -75
  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,16 @@
1
+ export { loadBotConfig, quickGridConfig, quickDCAConfig, } from "./config.js";
2
+ export type { ConditionType, Condition, RiskConfig, GridStrategyParams, DCAStrategyParams, FundingArbStrategyParams, GenericStrategyParams, StrategyParams, BotConfig, } from "./config.js";
3
+ export { runBot } from "./engine.js";
4
+ export type { BotLog, BotOutputMode } from "./engine.js";
5
+ export { PRESETS, getPreset, getPresetsByStrategy } from "./presets.js";
6
+ export type { Preset } from "./presets.js";
7
+ export { evaluateCondition, evaluateAllConditions, getMarketSnapshot, calculateRSI } from "./conditions.js";
8
+ export type { MarketSnapshot } from "./conditions.js";
9
+ export * from "./indicators.js";
10
+ export * from "./strategy-types.js";
11
+ export { registerStrategy, getStrategy, listStrategies } from "./strategy-registry.js";
12
+ export type { StrategyFactory } from "./strategy-registry.js";
13
+ export { appendJournal, readJournal, clearJournal } from "./trade-journal.js";
14
+ export type { JournalEntry } from "./trade-journal.js";
15
+ export { analyzePerformance, suggestAdjustments } from "./reflect.js";
16
+ export type { ReflectReport } from "./reflect.js";
@@ -0,0 +1,10 @@
1
+ // bot/ barrel — public API
2
+ export { loadBotConfig, quickGridConfig, quickDCAConfig, } from "./config.js";
3
+ export { runBot } from "./engine.js";
4
+ export { PRESETS, getPreset, getPresetsByStrategy } from "./presets.js";
5
+ export { evaluateCondition, evaluateAllConditions, getMarketSnapshot, calculateRSI } from "./conditions.js";
6
+ export * from "./indicators.js";
7
+ export * from "./strategy-types.js";
8
+ export { registerStrategy, getStrategy, listStrategies } from "./strategy-registry.js";
9
+ export { appendJournal, readJournal, clearJournal } from "./trade-journal.js";
10
+ export { analyzePerformance, suggestAdjustments } from "./reflect.js";
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Technical indicator library for trading strategies.
3
+ * Pure functions — no side effects, no API calls.
4
+ */
5
+ /** Calculate Exponential Moving Average */
6
+ export declare function calculateEMA(prices: number[], period: number): number[];
7
+ /** Calculate Simple Moving Average */
8
+ export declare function calculateSMA(prices: number[], period: number): number[];
9
+ /** Calculate Volume Weighted Average Price */
10
+ export declare function calculateVWAP(candles: {
11
+ close: number;
12
+ volume: number;
13
+ }[]): number;
14
+ /** Calculate Average True Range */
15
+ export declare function calculateATR(candles: {
16
+ high: number;
17
+ low: number;
18
+ close: number;
19
+ }[], period: number): number[];
20
+ /** Calculate Bollinger Bands */
21
+ export declare function calculateBollingerBands(prices: number[], period: number, stdDevMultiplier?: number): {
22
+ upper: number[];
23
+ middle: number[];
24
+ lower: number[];
25
+ };
26
+ /** Calculate RSI (extracted from conditions.ts) */
27
+ export declare function calculateRSI(closes: number[], period?: number): number;
28
+ /** Detect market regime: low-vol-trending, low-vol-ranging, high-vol-trending, high-vol-ranging */
29
+ export declare function detectRegime(prices: number[], period?: number): "low-vol-trending" | "low-vol-ranging" | "high-vol-trending" | "high-vol-ranging";
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Technical indicator library for trading strategies.
3
+ * Pure functions — no side effects, no API calls.
4
+ */
5
+ /** Calculate Exponential Moving Average */
6
+ export function calculateEMA(prices, period) {
7
+ if (prices.length < period)
8
+ return [];
9
+ const k = 2 / (period + 1);
10
+ const ema = [];
11
+ // SMA for first value
12
+ let sum = 0;
13
+ for (let i = 0; i < period; i++)
14
+ sum += prices[i];
15
+ ema.push(sum / period);
16
+ // EMA for rest
17
+ for (let i = period; i < prices.length; i++) {
18
+ ema.push(prices[i] * k + ema[ema.length - 1] * (1 - k));
19
+ }
20
+ return ema;
21
+ }
22
+ /** Calculate Simple Moving Average */
23
+ export function calculateSMA(prices, period) {
24
+ if (prices.length < period)
25
+ return [];
26
+ const sma = [];
27
+ for (let i = period - 1; i < prices.length; i++) {
28
+ let sum = 0;
29
+ for (let j = i - period + 1; j <= i; j++)
30
+ sum += prices[j];
31
+ sma.push(sum / period);
32
+ }
33
+ return sma;
34
+ }
35
+ /** Calculate Volume Weighted Average Price */
36
+ export function calculateVWAP(candles) {
37
+ let cumVolPrice = 0;
38
+ let cumVol = 0;
39
+ for (const c of candles) {
40
+ cumVolPrice += c.close * c.volume;
41
+ cumVol += c.volume;
42
+ }
43
+ return cumVol > 0 ? cumVolPrice / cumVol : 0;
44
+ }
45
+ /** Calculate Average True Range */
46
+ export function calculateATR(candles, period) {
47
+ if (candles.length < 2)
48
+ return [];
49
+ const tr = [];
50
+ for (let i = 1; i < candles.length; i++) {
51
+ const h = candles[i].high;
52
+ const l = candles[i].low;
53
+ const pc = candles[i - 1].close;
54
+ tr.push(Math.max(h - l, Math.abs(h - pc), Math.abs(l - pc)));
55
+ }
56
+ // Wilder smoothing for ATR
57
+ if (tr.length < period)
58
+ return [];
59
+ const atr = [];
60
+ let sum = 0;
61
+ for (let i = 0; i < period; i++)
62
+ sum += tr[i];
63
+ atr.push(sum / period);
64
+ for (let i = period; i < tr.length; i++) {
65
+ atr.push((atr[atr.length - 1] * (period - 1) + tr[i]) / period);
66
+ }
67
+ return atr;
68
+ }
69
+ /** Calculate Bollinger Bands */
70
+ export function calculateBollingerBands(prices, period, stdDevMultiplier = 2) {
71
+ const middle = calculateSMA(prices, period);
72
+ const upper = [];
73
+ const lower = [];
74
+ for (let i = 0; i < middle.length; i++) {
75
+ const sliceStart = i;
76
+ const slice = prices.slice(sliceStart, sliceStart + period);
77
+ const mean = middle[i];
78
+ const variance = slice.reduce((s, v) => s + (v - mean) ** 2, 0) / period;
79
+ const stdDev = Math.sqrt(variance);
80
+ upper.push(mean + stdDevMultiplier * stdDev);
81
+ lower.push(mean - stdDevMultiplier * stdDev);
82
+ }
83
+ return { upper, middle, lower };
84
+ }
85
+ /** Calculate RSI (extracted from conditions.ts) */
86
+ export function calculateRSI(closes, period = 14) {
87
+ if (closes.length < period + 1)
88
+ return 50; // neutral default
89
+ let gainSum = 0;
90
+ let lossSum = 0;
91
+ for (let i = 1; i <= period; i++) {
92
+ const diff = closes[i] - closes[i - 1];
93
+ if (diff > 0)
94
+ gainSum += diff;
95
+ else
96
+ lossSum -= diff;
97
+ }
98
+ let avgGain = gainSum / period;
99
+ let avgLoss = lossSum / period;
100
+ // Wilder smoothing
101
+ for (let i = period + 1; i < closes.length; i++) {
102
+ const diff = closes[i] - closes[i - 1];
103
+ avgGain = (avgGain * (period - 1) + (diff > 0 ? diff : 0)) / period;
104
+ avgLoss = (avgLoss * (period - 1) + (diff < 0 ? -diff : 0)) / period;
105
+ }
106
+ if (avgLoss === 0)
107
+ return 100;
108
+ const rs = avgGain / avgLoss;
109
+ return 100 - 100 / (1 + rs);
110
+ }
111
+ /** Detect market regime: low-vol-trending, low-vol-ranging, high-vol-trending, high-vol-ranging */
112
+ export function detectRegime(prices, period = 20) {
113
+ if (prices.length < period)
114
+ return "low-vol-ranging";
115
+ const recent = prices.slice(-period);
116
+ // Volatility: std dev of returns
117
+ const returns = [];
118
+ for (let i = 1; i < recent.length; i++) {
119
+ returns.push((recent[i] - recent[i - 1]) / recent[i - 1]);
120
+ }
121
+ const meanReturn = returns.reduce((s, r) => s + r, 0) / returns.length;
122
+ const variance = returns.reduce((s, r) => s + (r - meanReturn) ** 2, 0) / returns.length;
123
+ const vol = Math.sqrt(variance);
124
+ // Trend: slope of linear regression
125
+ const n = recent.length;
126
+ let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
127
+ for (let i = 0; i < n; i++) {
128
+ sumX += i;
129
+ sumY += recent[i];
130
+ sumXY += i * recent[i];
131
+ sumX2 += i * i;
132
+ }
133
+ const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
134
+ const normalizedSlope = Math.abs(slope / (sumY / n));
135
+ const isHighVol = vol > 0.02; // >2% daily vol
136
+ const isTrending = normalizedSlope > 0.001;
137
+ if (isHighVol && isTrending)
138
+ return "high-vol-trending";
139
+ if (isHighVol && !isTrending)
140
+ return "high-vol-ranging";
141
+ if (!isHighVol && isTrending)
142
+ return "low-vol-trending";
143
+ return "low-vol-ranging";
144
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * REFLECT: Performance analysis + auto-parameter adjustment
3
+ * Metrics: win rate, avg win/loss, fee drag ratio, direction split, holding periods
4
+ * Auto-adjust: within guardrail bounds (e.g., spread ±20%, size ±30%)
5
+ */
6
+ import type { JournalEntry } from "./trade-journal.js";
7
+ export interface ReflectReport {
8
+ period: {
9
+ from: number;
10
+ to: number;
11
+ days: number;
12
+ };
13
+ totalTrades: number;
14
+ winRate: number;
15
+ avgWin: number;
16
+ avgLoss: number;
17
+ profitFactor: number;
18
+ feeDragRatio: number;
19
+ directionSplit: {
20
+ long: number;
21
+ short: number;
22
+ };
23
+ avgHoldingPeriodMs: number;
24
+ bestStrategy: string;
25
+ worstStrategy: string;
26
+ suggestions: string[];
27
+ }
28
+ export declare function analyzePerformance(entries: JournalEntry[], periodDays?: number): ReflectReport;
29
+ export declare function suggestAdjustments(report: ReflectReport): {
30
+ param: string;
31
+ current: number;
32
+ suggested: number;
33
+ reason: string;
34
+ }[];
@@ -0,0 +1,155 @@
1
+ /**
2
+ * REFLECT: Performance analysis + auto-parameter adjustment
3
+ * Metrics: win rate, avg win/loss, fee drag ratio, direction split, holding periods
4
+ * Auto-adjust: within guardrail bounds (e.g., spread ±20%, size ±30%)
5
+ */
6
+ export function analyzePerformance(entries, periodDays = 30) {
7
+ const now = Date.now();
8
+ const from = now - periodDays * 24 * 60 * 60 * 1000;
9
+ const to = now;
10
+ const inPeriod = entries.filter((e) => e.timestamp >= from && e.timestamp <= to);
11
+ if (inPeriod.length === 0) {
12
+ return {
13
+ period: { from, to, days: periodDays },
14
+ totalTrades: 0,
15
+ winRate: 0,
16
+ avgWin: 0,
17
+ avgLoss: 0,
18
+ profitFactor: 0,
19
+ feeDragRatio: 0,
20
+ directionSplit: { long: 0, short: 0 },
21
+ avgHoldingPeriodMs: 0,
22
+ bestStrategy: "",
23
+ worstStrategy: "",
24
+ suggestions: ["No trades in the selected period."],
25
+ };
26
+ }
27
+ // PnL-based metrics (only entries with pnl defined)
28
+ const withPnl = inPeriod.filter((e) => e.pnl !== undefined);
29
+ const wins = withPnl.filter((e) => (e.pnl ?? 0) > 0);
30
+ const losses = withPnl.filter((e) => (e.pnl ?? 0) <= 0);
31
+ const winRate = withPnl.length > 0 ? wins.length / withPnl.length : 0;
32
+ const avgWin = wins.length > 0 ? wins.reduce((s, e) => s + (e.pnl ?? 0), 0) / wins.length : 0;
33
+ const avgLoss = losses.length > 0 ? losses.reduce((s, e) => s + (e.pnl ?? 0), 0) / losses.length : 0;
34
+ const totalGross = wins.reduce((s, e) => s + (e.pnl ?? 0), 0);
35
+ const totalLoss = Math.abs(losses.reduce((s, e) => s + (e.pnl ?? 0), 0));
36
+ const profitFactor = totalLoss > 0 ? totalGross / totalLoss : totalGross > 0 ? Infinity : 0;
37
+ // Fee drag: total fees / gross profit
38
+ const totalFees = inPeriod.reduce((s, e) => s + (e.fees ?? 0), 0);
39
+ const feeDragRatio = totalGross > 0 ? totalFees / totalGross : 0;
40
+ // Direction split
41
+ const longs = inPeriod.filter((e) => e.side === "buy").length;
42
+ const shorts = inPeriod.filter((e) => e.side === "sell").length;
43
+ const total = inPeriod.length;
44
+ const directionSplit = {
45
+ long: total > 0 ? longs / total : 0,
46
+ short: total > 0 ? shorts / total : 0,
47
+ };
48
+ // Holding period
49
+ const withHolding = inPeriod.filter((e) => e.holdingPeriodMs !== undefined);
50
+ const avgHoldingPeriodMs = withHolding.length > 0
51
+ ? withHolding.reduce((s, e) => s + (e.holdingPeriodMs ?? 0), 0) / withHolding.length
52
+ : 0;
53
+ // Per-strategy PnL
54
+ const strategyPnl = new Map();
55
+ for (const e of withPnl) {
56
+ strategyPnl.set(e.strategy, (strategyPnl.get(e.strategy) ?? 0) + (e.pnl ?? 0));
57
+ }
58
+ let bestStrategy = "";
59
+ let worstStrategy = "";
60
+ if (strategyPnl.size > 0) {
61
+ let bestPnl = -Infinity;
62
+ let worstPnl = Infinity;
63
+ for (const [strat, pnl] of strategyPnl) {
64
+ if (pnl > bestPnl) {
65
+ bestPnl = pnl;
66
+ bestStrategy = strat;
67
+ }
68
+ if (pnl < worstPnl) {
69
+ worstPnl = pnl;
70
+ worstStrategy = strat;
71
+ }
72
+ }
73
+ }
74
+ const suggestions = buildSuggestions({ winRate, avgWin, avgLoss, profitFactor, feeDragRatio, directionSplit, avgHoldingPeriodMs, totalTrades: inPeriod.length });
75
+ return {
76
+ period: { from, to, days: periodDays },
77
+ totalTrades: inPeriod.length,
78
+ winRate,
79
+ avgWin,
80
+ avgLoss,
81
+ profitFactor,
82
+ feeDragRatio,
83
+ directionSplit,
84
+ avgHoldingPeriodMs,
85
+ bestStrategy,
86
+ worstStrategy,
87
+ suggestions,
88
+ };
89
+ }
90
+ function buildSuggestions(metrics) {
91
+ const suggestions = [];
92
+ if (metrics.winRate < 0.4) {
93
+ suggestions.push("Win rate below 40% — consider tightening entry conditions or widening spread.");
94
+ }
95
+ if (metrics.profitFactor < 1.0 && metrics.totalTrades >= 10) {
96
+ suggestions.push("Profit factor below 1.0 — strategy is net losing; review sizing or stop-loss levels.");
97
+ }
98
+ if (metrics.feeDragRatio > 0.3) {
99
+ suggestions.push("Fee drag exceeds 30% of gross profit — reduce order frequency or increase minimum profit threshold.");
100
+ }
101
+ if (metrics.avgWin > 0 && metrics.avgLoss < 0 && Math.abs(metrics.avgLoss) > metrics.avgWin * 2) {
102
+ suggestions.push("Average loss is more than 2× average win — improve risk/reward ratio or tighten stop-loss.");
103
+ }
104
+ if (metrics.directionSplit.long > 0.8) {
105
+ suggestions.push("Strategy is heavily long-biased (>80%) — consider hedging short exposure.");
106
+ }
107
+ if (metrics.directionSplit.short > 0.8) {
108
+ suggestions.push("Strategy is heavily short-biased (>80%) — verify market regime alignment.");
109
+ }
110
+ if (metrics.avgHoldingPeriodMs > 24 * 60 * 60 * 1000) {
111
+ suggestions.push("Average holding period exceeds 24h — review if overnight risk is compensated.");
112
+ }
113
+ if (suggestions.length === 0) {
114
+ suggestions.push("Performance looks healthy. Keep monitoring.");
115
+ }
116
+ return suggestions;
117
+ }
118
+ export function suggestAdjustments(report) {
119
+ const adjustments = [];
120
+ // Spread adjustment: tighten if win rate is high, widen if low
121
+ if (report.winRate < 0.4 && report.totalTrades >= 10) {
122
+ adjustments.push({
123
+ param: "spread_bps",
124
+ current: 10,
125
+ suggested: Math.round(10 * 1.2), // +20% guardrail
126
+ reason: "Low win rate — widening spread to improve fill quality.",
127
+ });
128
+ }
129
+ else if (report.winRate > 0.65 && report.profitFactor > 1.5) {
130
+ adjustments.push({
131
+ param: "spread_bps",
132
+ current: 10,
133
+ suggested: Math.round(10 * 0.8), // -20% guardrail
134
+ reason: "Strong win rate — tightening spread to capture more flow.",
135
+ });
136
+ }
137
+ // Size adjustment: reduce if fee drag is high or profit factor < 1
138
+ if (report.feeDragRatio > 0.3 || report.profitFactor < 1.0) {
139
+ adjustments.push({
140
+ param: "size_usd",
141
+ current: 100,
142
+ suggested: Math.round(100 * 0.7), // -30% guardrail
143
+ reason: "High fee drag or negative profit factor — reducing size to limit losses.",
144
+ });
145
+ }
146
+ else if (report.profitFactor > 2.0 && report.winRate > 0.55) {
147
+ adjustments.push({
148
+ param: "size_usd",
149
+ current: 100,
150
+ suggested: Math.round(100 * 1.3), // +30% guardrail
151
+ reason: "Strong performance — scaling up size within guardrail bounds.",
152
+ });
153
+ }
154
+ return adjustments;
155
+ }
@@ -0,0 +1,36 @@
1
+ import type { Strategy, StrategyContext, StrategyAction, EnrichedSnapshot } from "../strategy-types.js";
2
+ /**
3
+ * Aggressive taker strategy.
4
+ * Crosses spread with directional bias from EMA crossover signals.
5
+ * - Short EMA cross above long EMA = buy (bullish).
6
+ * - Short EMA cross below long EMA = sell (bearish).
7
+ * Size modulation: larger in strong trend, smaller in weak.
8
+ */
9
+ export declare class AggressiveTakerStrategy implements Strategy {
10
+ readonly name = "aggressive-taker";
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 shortEmaPeriod();
29
+ private get longEmaPeriod();
30
+ private get baseSize();
31
+ private get maxSize();
32
+ private get minSignalStrength();
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,125 @@
1
+ import { registerStrategy } from "../strategy-registry.js";
2
+ import { calculateEMA } from "../indicators.js";
3
+ /**
4
+ * Aggressive taker strategy.
5
+ * Crosses spread with directional bias from EMA crossover signals.
6
+ * - Short EMA cross above long EMA = buy (bullish).
7
+ * - Short EMA cross below long EMA = sell (bearish).
8
+ * Size modulation: larger in strong trend, smaller in weak.
9
+ */
10
+ export class AggressiveTakerStrategy {
11
+ name = "aggressive-taker";
12
+ _config = {};
13
+ describe() {
14
+ return {
15
+ description: "Aggressive market taker with EMA crossover signals and size modulation",
16
+ params: [
17
+ { name: "shortEmaPeriod", type: "number", required: false, default: 9, description: "Short EMA period" },
18
+ { name: "longEmaPeriod", type: "number", required: false, default: 21, description: "Long EMA period" },
19
+ { name: "baseSize", type: "number", required: true, description: "Base position size" },
20
+ { name: "maxSize", type: "number", required: false, default: 0, description: "Max position size (0 = 3x base)" },
21
+ { name: "minSignalStrength", type: "number", required: false, default: 0.001, description: "Min EMA divergence ratio to act" },
22
+ ],
23
+ };
24
+ }
25
+ get shortEmaPeriod() { return Number(this._config.shortEmaPeriod ?? 9); }
26
+ get longEmaPeriod() { return Number(this._config.longEmaPeriod ?? 21); }
27
+ get baseSize() { return Number(this._config.baseSize ?? 0.1); }
28
+ get maxSize() { return Number(this._config.maxSize ?? 0) || this.baseSize * 3; }
29
+ get minSignalStrength() { return Number(this._config.minSignalStrength ?? 0.001); }
30
+ async init(ctx, _snapshot) {
31
+ this._config = ctx.config;
32
+ ctx.state.set("position", null); // null | { side, entry, size }
33
+ ctx.state.set("prevSignal", 0); // -1 sell, 0 neutral, 1 buy
34
+ ctx.log(` [TAKER] Ready | shortEMA=${this.shortEmaPeriod} longEMA=${this.longEmaPeriod} base=${this.baseSize} max=${this.maxSize}`);
35
+ }
36
+ async onTick(ctx, snapshot) {
37
+ const actions = [];
38
+ const klines = snapshot.klines;
39
+ if (klines.length < this.longEmaPeriod + 1) {
40
+ ctx.log(` [TAKER] Insufficient klines (${klines.length}/${this.longEmaPeriod + 1})`);
41
+ return [];
42
+ }
43
+ const closes = klines.map(k => parseFloat(k.close));
44
+ const price = snapshot.price;
45
+ // Compute EMAs
46
+ const shortEma = calculateEMA(closes, this.shortEmaPeriod);
47
+ const longEma = calculateEMA(closes, this.longEmaPeriod);
48
+ if (shortEma.length === 0 || longEma.length === 0)
49
+ return [];
50
+ const currentShort = shortEma[shortEma.length - 1];
51
+ const currentLong = longEma[longEma.length - 1];
52
+ // Signal strength = normalized divergence
53
+ const divergence = (currentShort - currentLong) / currentLong;
54
+ const signalStrength = Math.abs(divergence);
55
+ // Determine signal: +1 bullish, -1 bearish, 0 neutral
56
+ let signal = 0;
57
+ if (divergence > this.minSignalStrength)
58
+ signal = 1;
59
+ else if (divergence < -this.minSignalStrength)
60
+ signal = -1;
61
+ const prevSignal = ctx.state.get("prevSignal");
62
+ const position = ctx.state.get("position");
63
+ // Size modulation: scale between baseSize and maxSize based on signal strength
64
+ const sizeRange = this.maxSize - this.baseSize;
65
+ // Cap strength at 5x minSignalStrength for scaling
66
+ const strengthCapped = Math.min(signalStrength / (this.minSignalStrength * 5), 1);
67
+ const orderSize = this.baseSize + sizeRange * strengthCapped;
68
+ // Signal changed — trade
69
+ if (signal !== 0 && signal !== prevSignal) {
70
+ // Close existing position if flipping
71
+ if (position) {
72
+ ctx.log(` [TAKER] Close ${position.side} @ $${price.toFixed(2)}`);
73
+ actions.push({
74
+ type: "place_order",
75
+ side: position.side === "buy" ? "sell" : "buy",
76
+ price: price.toFixed(2),
77
+ size: String(position.size),
78
+ orderType: "market",
79
+ reduceOnly: true,
80
+ });
81
+ }
82
+ // Open new position
83
+ const side = signal > 0 ? "buy" : "sell";
84
+ const roundedSize = parseFloat(orderSize.toFixed(6));
85
+ ctx.state.set("position", { side, entry: price, size: roundedSize });
86
+ ctx.log(` [TAKER] ${side.toUpperCase()} @ $${price.toFixed(2)} size=${roundedSize} strength=${signalStrength.toFixed(4)}`);
87
+ actions.push({
88
+ type: "place_order",
89
+ side,
90
+ price: price.toFixed(2),
91
+ size: String(roundedSize),
92
+ orderType: "market",
93
+ });
94
+ }
95
+ // Neutral signal — close if we have a position
96
+ if (signal === 0 && position) {
97
+ ctx.log(` [TAKER] Signal neutral, closing ${position.side} @ $${price.toFixed(2)}`);
98
+ actions.push({
99
+ type: "place_order",
100
+ side: position.side === "buy" ? "sell" : "buy",
101
+ price: price.toFixed(2),
102
+ size: String(position.size),
103
+ orderType: "market",
104
+ reduceOnly: true,
105
+ });
106
+ ctx.state.set("position", null);
107
+ }
108
+ ctx.state.set("prevSignal", signal);
109
+ return actions;
110
+ }
111
+ async onStop(ctx) {
112
+ const position = ctx.state.get("position");
113
+ if (!position)
114
+ return [];
115
+ return [{
116
+ type: "place_order",
117
+ side: position.side === "buy" ? "sell" : "buy",
118
+ price: "0",
119
+ size: String(position.size),
120
+ orderType: "market",
121
+ reduceOnly: true,
122
+ }];
123
+ }
124
+ }
125
+ registerStrategy("aggressive-taker", (_config) => new AggressiveTakerStrategy());
@@ -0,0 +1,36 @@
1
+ import type { Strategy, StrategyContext, StrategyAction, EnrichedSnapshot } from "../strategy-types.js";
2
+ export declare class ApexStrategy implements Strategy {
3
+ readonly name = "apex";
4
+ private cfg;
5
+ private guardConfig;
6
+ private slots;
7
+ private pulseHistory;
8
+ private dailyLoss;
9
+ describe(): {
10
+ description: string;
11
+ params: ({
12
+ name: string;
13
+ type: "number";
14
+ required: boolean;
15
+ default: number;
16
+ description: string;
17
+ } | {
18
+ name: string;
19
+ type: "string";
20
+ required: boolean;
21
+ default: string;
22
+ description: string;
23
+ } | {
24
+ name: string;
25
+ type: "number";
26
+ required: boolean;
27
+ description: string;
28
+ default?: undefined;
29
+ })[];
30
+ };
31
+ init(ctx: StrategyContext, _snapshot: EnrichedSnapshot): Promise<void>;
32
+ onTick(ctx: StrategyContext, snapshot: EnrichedSnapshot): Promise<StrategyAction[]>;
33
+ onStop(ctx: StrategyContext): Promise<StrategyAction[]>;
34
+ private calculateRoe;
35
+ private determineSide;
36
+ }