dexbot 1.4.17 → 1.4.19
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/CHANGELOG.md +17 -0
- package/README.md +1 -0
- package/analysis/README.md +43 -43
- package/analysis/ama_fitting/README.md +8 -8
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/bot_fitting/README.md +4 -4
- package/analysis/bot_fitting/backtest_ama_sweep.ts +1 -1
- package/analysis/bot_usage/discover_bot_accounts.ts +11 -11
- package/analysis/tradingview/README.md +4 -4
- package/analysis/trend_detection/DYNAMIC_WEIGHT_RESEARCH.md +2 -2
- package/analysis/trend_detection/SIGNAL_DOCUMENTATION.md +1 -1
- package/analysis/trend_detection/package.json +1 -1
- package/claw/README.md +45 -45
- package/claw/docs/AI_BOT_LIBRARY_API.md +6 -6
- package/claw/examples/short_mpa_bts_strategy.ts +3 -3
- package/claw/package.json +21 -21
- package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
- package/claw/runtimes/openclaw-plugin/package.json +1 -1
- package/claw/scripts/claw_bridge.ts +2 -2
- package/claw/scripts/claw_skill_md.ts +1 -1
- package/claw/skills/launcher-ops/references/launcher-workflow.md +5 -5
- package/claw/skills/margin-trading/references/honest-asset-list.md +1 -1
- package/claw/skills/memu-memory/SKILL.md +3 -3
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/dist/analysis/ama_fitting/analyze_ama_price_changes.d.ts +3 -0
- package/dist/analysis/ama_fitting/analyze_ama_price_changes.d.ts.map +1 -0
- package/dist/analysis/ama_fitting/analyze_ama_price_changes.js +148 -0
- package/dist/analysis/ama_fitting/analyze_ama_price_changes.js.map +1 -0
- package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.d.ts +12 -0
- package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.d.ts.map +1 -0
- package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.js +389 -0
- package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.js.map +1 -0
- package/dist/analysis/ama_fitting/calibrate_convergence_er.d.ts +3 -0
- package/dist/analysis/ama_fitting/calibrate_convergence_er.d.ts.map +1 -0
- package/dist/analysis/ama_fitting/calibrate_convergence_er.js +200 -0
- package/dist/analysis/ama_fitting/calibrate_convergence_er.js.map +1 -0
- package/dist/analysis/ama_fitting/fetch_lp_candles.d.ts +3 -0
- package/dist/analysis/ama_fitting/fetch_lp_candles.d.ts.map +1 -0
- package/dist/analysis/ama_fitting/fetch_lp_candles.js +213 -0
- package/dist/analysis/ama_fitting/fetch_lp_candles.js.map +1 -0
- package/dist/analysis/ama_fitting/generate_unified_comparison_chart.d.ts +25 -0
- package/dist/analysis/ama_fitting/generate_unified_comparison_chart.d.ts.map +1 -0
- package/dist/analysis/ama_fitting/generate_unified_comparison_chart.js +247 -0
- package/dist/analysis/ama_fitting/generate_unified_comparison_chart.js.map +1 -0
- package/dist/analysis/ama_fitting/optimizer_high_resolution.d.ts +35 -0
- package/dist/analysis/ama_fitting/optimizer_high_resolution.d.ts.map +1 -0
- package/dist/analysis/ama_fitting/optimizer_high_resolution.js +782 -0
- package/dist/analysis/ama_fitting/optimizer_high_resolution.js.map +1 -0
- package/dist/analysis/analyze_derivatives.d.ts +86 -0
- package/dist/analysis/analyze_derivatives.d.ts.map +1 -0
- package/dist/analysis/analyze_derivatives.js +285 -0
- package/dist/analysis/analyze_derivatives.js.map +1 -0
- package/dist/analysis/analyze_dynamic_weight.d.ts +14 -0
- package/dist/analysis/analyze_dynamic_weight.d.ts.map +1 -0
- package/dist/analysis/analyze_dynamic_weight.js +197 -0
- package/dist/analysis/analyze_dynamic_weight.js.map +1 -0
- package/dist/analysis/analyze_kalman.d.ts +13 -0
- package/dist/analysis/analyze_kalman.d.ts.map +1 -0
- package/dist/analysis/analyze_kalman.js +118 -0
- package/dist/analysis/analyze_kalman.js.map +1 -0
- package/dist/analysis/analyze_regime.d.ts +20 -0
- package/dist/analysis/analyze_regime.d.ts.map +1 -0
- package/dist/analysis/analyze_regime.js +139 -0
- package/dist/analysis/analyze_regime.js.map +1 -0
- package/dist/analysis/analyze_regime_windows.d.ts +19 -0
- package/dist/analysis/analyze_regime_windows.d.ts.map +1 -0
- package/dist/analysis/analyze_regime_windows.js +414 -0
- package/dist/analysis/analyze_regime_windows.js.map +1 -0
- package/dist/analysis/analyze_risk_profile.d.ts +3 -0
- package/dist/analysis/analyze_risk_profile.d.ts.map +1 -0
- package/dist/analysis/analyze_risk_profile.js +186 -0
- package/dist/analysis/analyze_risk_profile.js.map +1 -0
- package/dist/analysis/analyze_trade_heatmap.d.ts +3 -0
- package/dist/analysis/analyze_trade_heatmap.d.ts.map +1 -0
- package/dist/analysis/analyze_trade_heatmap.js +358 -0
- package/dist/analysis/analyze_trade_heatmap.js.map +1 -0
- package/dist/analysis/analyze_volatility.d.ts +22 -0
- package/dist/analysis/analyze_volatility.d.ts.map +1 -0
- package/dist/analysis/analyze_volatility.js +165 -0
- package/dist/analysis/analyze_volatility.js.map +1 -0
- package/dist/analysis/bot_fitting/backtest_ama_sweep.d.ts +91 -0
- package/dist/analysis/bot_fitting/backtest_ama_sweep.d.ts.map +1 -0
- package/dist/analysis/bot_fitting/backtest_ama_sweep.js +792 -0
- package/dist/analysis/bot_fitting/backtest_ama_sweep.js.map +1 -0
- package/dist/analysis/bot_fitting/backtest_bot_fitting.d.ts +2 -0
- package/dist/analysis/bot_fitting/backtest_bot_fitting.d.ts.map +1 -0
- package/dist/analysis/bot_fitting/backtest_bot_fitting.js +380 -0
- package/dist/analysis/bot_fitting/backtest_bot_fitting.js.map +1 -0
- package/dist/analysis/bot_fitting/shared_utils.d.ts +26 -0
- package/dist/analysis/bot_fitting/shared_utils.d.ts.map +1 -0
- package/dist/analysis/bot_fitting/shared_utils.js +42 -0
- package/dist/analysis/bot_fitting/shared_utils.js.map +1 -0
- package/dist/analysis/bot_key_utils.d.ts +11 -0
- package/dist/analysis/bot_key_utils.d.ts.map +1 -0
- package/dist/analysis/bot_key_utils.js +153 -0
- package/dist/analysis/bot_key_utils.js.map +1 -0
- package/dist/analysis/bot_usage/discover_bot_accounts.d.ts +3 -0
- package/dist/analysis/bot_usage/discover_bot_accounts.d.ts.map +1 -0
- package/dist/analysis/bot_usage/discover_bot_accounts.js +499 -0
- package/dist/analysis/bot_usage/discover_bot_accounts.js.map +1 -0
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +531 -0
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -0
- package/dist/analysis/bot_usage/kibana_bot_queries.js +399 -0
- package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -0
- package/dist/analysis/chart_css.d.ts +17 -0
- package/dist/analysis/chart_css.d.ts.map +1 -0
- package/dist/analysis/chart_css.js +49 -0
- package/dist/analysis/chart_css.js.map +1 -0
- package/dist/analysis/chart_ui.d.ts +45 -0
- package/dist/analysis/chart_ui.d.ts.map +1 -0
- package/dist/analysis/chart_ui.js +97 -0
- package/dist/analysis/chart_ui.js.map +1 -0
- package/dist/analysis/chart_utils.d.ts +27 -0
- package/dist/analysis/chart_utils.d.ts.map +1 -0
- package/dist/analysis/chart_utils.js +153 -0
- package/dist/analysis/chart_utils.js.map +1 -0
- package/dist/analysis/derivative_chart_generator.d.ts +12 -0
- package/dist/analysis/derivative_chart_generator.d.ts.map +1 -0
- package/dist/analysis/derivative_chart_generator.js +891 -0
- package/dist/analysis/derivative_chart_generator.js.map +1 -0
- package/dist/analysis/math_utils.d.ts +22 -0
- package/dist/analysis/math_utils.d.ts.map +1 -0
- package/dist/analysis/math_utils.js +43 -0
- package/dist/analysis/math_utils.js.map +1 -0
- package/dist/analysis/price_sources.d.ts +40 -0
- package/dist/analysis/price_sources.d.ts.map +1 -0
- package/dist/analysis/price_sources.js +94 -0
- package/dist/analysis/price_sources.js.map +1 -0
- package/dist/analysis/resolve_source.d.ts +25 -0
- package/dist/analysis/resolve_source.d.ts.map +1 -0
- package/dist/analysis/resolve_source.js +69 -0
- package/dist/analysis/resolve_source.js.map +1 -0
- package/dist/analysis/trade_profitability.d.ts +115 -0
- package/dist/analysis/trade_profitability.d.ts.map +1 -0
- package/dist/analysis/trade_profitability.js +1240 -0
- package/dist/analysis/trade_profitability.js.map +1 -0
- package/dist/analysis/tradingview/analyze_tradingview.d.ts +31 -0
- package/dist/analysis/tradingview/analyze_tradingview.d.ts.map +1 -0
- package/dist/analysis/tradingview/analyze_tradingview.js +146 -0
- package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -0
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts +5 -0
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts.map +1 -0
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +1714 -0
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js.map +1 -0
- package/dist/analysis/trend_detection/derivative_analyzer.d.ts +247 -0
- package/dist/analysis/trend_detection/derivative_analyzer.d.ts.map +1 -0
- package/dist/analysis/trend_detection/derivative_analyzer.js +901 -0
- package/dist/analysis/trend_detection/derivative_analyzer.js.map +1 -0
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts +3 -0
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts.map +1 -0
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js +1361 -0
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js.map +1 -0
- package/dist/analysis/trend_detection/hurst_analyzer.d.ts +6 -0
- package/dist/analysis/trend_detection/hurst_analyzer.d.ts.map +1 -0
- package/dist/analysis/trend_detection/hurst_analyzer.js +7 -0
- package/dist/analysis/trend_detection/hurst_analyzer.js.map +1 -0
- package/dist/analysis/trend_detection/kalman_chart_generator.d.ts +3 -0
- package/dist/analysis/trend_detection/kalman_chart_generator.d.ts.map +1 -0
- package/dist/analysis/trend_detection/kalman_chart_generator.js +360 -0
- package/dist/analysis/trend_detection/kalman_chart_generator.js.map +1 -0
- package/dist/analysis/trend_detection/kalman_trend_analyzer.d.ts +8 -0
- package/dist/analysis/trend_detection/kalman_trend_analyzer.d.ts.map +1 -0
- package/dist/analysis/trend_detection/kalman_trend_analyzer.js +8 -0
- package/dist/analysis/trend_detection/kalman_trend_analyzer.js.map +1 -0
- package/dist/analysis/trend_detection/kalman_velocity_smoothing.d.ts +6 -0
- package/dist/analysis/trend_detection/kalman_velocity_smoothing.d.ts.map +1 -0
- package/dist/analysis/trend_detection/kalman_velocity_smoothing.js +7 -0
- package/dist/analysis/trend_detection/kalman_velocity_smoothing.js.map +1 -0
- package/dist/analysis/trend_detection/permutation_entropy_analyzer.d.ts +6 -0
- package/dist/analysis/trend_detection/permutation_entropy_analyzer.d.ts.map +1 -0
- package/dist/analysis/trend_detection/permutation_entropy_analyzer.js +7 -0
- package/dist/analysis/trend_detection/permutation_entropy_analyzer.js.map +1 -0
- package/dist/analysis/trend_detection/regime_chart_generator.d.ts +3 -0
- package/dist/analysis/trend_detection/regime_chart_generator.d.ts.map +1 -0
- package/dist/analysis/trend_detection/regime_chart_generator.js +310 -0
- package/dist/analysis/trend_detection/regime_chart_generator.js.map +1 -0
- package/dist/analysis/trend_detection/tests/test_kalman_trend.d.ts +2 -0
- package/dist/analysis/trend_detection/tests/test_kalman_trend.d.ts.map +1 -0
- package/dist/analysis/trend_detection/tests/test_kalman_trend.js +128 -0
- package/dist/analysis/trend_detection/tests/test_kalman_trend.js.map +1 -0
- package/dist/analysis/trend_detection/tests/test_kalman_velocity_smoothing.d.ts +2 -0
- package/dist/analysis/trend_detection/tests/test_kalman_velocity_smoothing.d.ts.map +1 -0
- package/dist/analysis/trend_detection/tests/test_kalman_velocity_smoothing.js +37 -0
- package/dist/analysis/trend_detection/tests/test_kalman_velocity_smoothing.js.map +1 -0
- package/dist/analysis/trend_detection/volatility_chart_generator.d.ts +3 -0
- package/dist/analysis/trend_detection/volatility_chart_generator.d.ts.map +1 -0
- package/dist/analysis/trend_detection/volatility_chart_generator.js +646 -0
- package/dist/analysis/trend_detection/volatility_chart_generator.js.map +1 -0
- package/dist/bot.js +1 -1
- package/dist/bot.js.map +1 -1
- package/dist/market_adapter/ama_signal_runner.js +1 -1
- package/dist/market_adapter/ama_signal_runner.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js +1 -1
- package/dist/modules/account_bots.d.ts +1 -0
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +27 -5
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/constants.d.ts +1 -0
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +12 -3
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +22 -0
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +27 -1
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +48 -1
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +168 -4
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +6 -27
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/strategy.d.ts.map +1 -1
- package/dist/modules/order/strategy.js +6 -15
- package/dist/modules/order/strategy.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +29 -1
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +46 -1
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts +2 -0
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +30 -2
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/runtime_settings.d.ts +1 -0
- package/dist/modules/runtime_settings.d.ts.map +1 -1
- package/dist/modules/runtime_settings.js +13 -2
- package/dist/modules/runtime_settings.js.map +1 -1
- package/dist/scripts/generate_lp_chart.js +1 -1
- package/dist/scripts/test-credit-renewal.js +1 -1
- package/docs/DEXBOT_COMPARISON.md +3 -3
- package/docs/EVOLUTION.md +26 -95
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +1 -1
- package/docs/GRID_RECALCULATION.md +2 -2
- package/docs/LIFECYCLE.md +207 -0
- package/docs/README.md +9 -1
- package/docs/developer_guide.md +4 -3
- package/market_adapter/README.md +23 -23
- package/package.json +18 -21
- package/scripts/README.md +12 -12
- package/scripts/bots +5 -7
- package/scripts/clear-market-adapter.sh +1 -1
- package/scripts/dexbot +5 -7
- package/scripts/keys +5 -7
- package/scripts/pm2 +5 -7
- package/scripts/unlock +5 -7
- package/scripts/update.js +6 -7
- package/scripts/repo-stats.html +0 -480
|
@@ -0,0 +1,792 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
|
|
5
|
+
import { calculateAMA } from '../../market_adapter/core/strategies/ama.js';
|
|
6
|
+
import { range } from '../math_utils.js';
|
|
7
|
+
import { parseListOrRange, loadLpData, fmt } from './shared_utils.js';
|
|
8
|
+
import { getStorage } from '../../modules/storage/index.js';
|
|
9
|
+
const { readJSON, writeJSON } = getStorage();
|
|
10
|
+
'use strict';
|
|
11
|
+
/**
|
|
12
|
+
* AMA SWEEP BACKTEST — persistent grid simulation
|
|
13
|
+
*
|
|
14
|
+
* Models the real bot mechanics:
|
|
15
|
+
* - Orders sit at FIXED chain prices until canceled or filled
|
|
16
|
+
* - When AMA drifts past reposition threshold, grid re-centers
|
|
17
|
+
* - Grid compression: AMA shift pushes one side's orders closer to market
|
|
18
|
+
* - Order sizing depends on capital, ratio (range width), and weight profile
|
|
19
|
+
* - Three weight profiles: valley, neutral, mountain (symmetric buy/sell)
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* tsx analysis/bot_fitting/backtest_ama_sweep.ts --data <path-to-lp-candles.json>
|
|
23
|
+
* tsx analysis/bot_fitting/backtest_ama_sweep.ts --data <path-to-lp-candles.json> --spread 4:16:1 --increment 0.5:4:0.25
|
|
24
|
+
*/
|
|
25
|
+
const DEFAULT_MAX_ORDERS = 20; // matches bot default activeOrders per side
|
|
26
|
+
const DEFAULT_FEE_ROUNDTRIP_PCT = 0.20;
|
|
27
|
+
const DEFAULT_MIN_SPREAD_FACTOR = 2.1;
|
|
28
|
+
const DEFAULT_CAPITAL = 10000; // notional units per side
|
|
29
|
+
const DEFAULT_BTS_CREATE_FEE = 0.48260;
|
|
30
|
+
const DEFAULT_BTS_CANCEL_FEE = 0.00482;
|
|
31
|
+
const DEFAULT_BTS_MAKER_CREATE_FACTOR = 0.10;
|
|
32
|
+
const DEFAULT_TX_FEE_PRICE = 1.0;
|
|
33
|
+
// Weight profiles (symmetric for both sides)
|
|
34
|
+
// valley: heavier at edges (outer levels), lighter near center
|
|
35
|
+
// neutral: equal across all levels
|
|
36
|
+
// mountain: heavier near center, lighter at edges
|
|
37
|
+
const WEIGHT_PROFILES = {
|
|
38
|
+
valley: -0.8, // negative = invert decay → outer levels get more
|
|
39
|
+
neutral: 0, // flat = equal distribution
|
|
40
|
+
mountain: 1.5, // strong decay → inner levels get more
|
|
41
|
+
};
|
|
42
|
+
// Search grid defaults — centered around bot defaults (spread=2%, increment=0.5%)
|
|
43
|
+
// Spread = distance from center to first order on each side (half the bid-ask gap)
|
|
44
|
+
// Increment = distance between successive orders on the same side
|
|
45
|
+
const DEFAULT_SPREAD_VALUES = [...range(0.5, 4, 0.25), ...range(5, 12, 1)];
|
|
46
|
+
const DEFAULT_INCREMENT_VALUES = [...range(0.2, 2, 0.1), ...range(2.5, 8, 0.5)];
|
|
47
|
+
const DEFAULT_RATIO_VALUES = [1.05, 1.1, 1.15, 1.2, 1.3, 1.5, 2, 3, 5, 10];
|
|
48
|
+
// Reposition threshold: AMA must move this fraction from last grid center to trigger re-center
|
|
49
|
+
const DEFAULT_REPOSITION_PCT = 2.5;
|
|
50
|
+
function parseArgs() {
|
|
51
|
+
const args = process.argv.slice(2);
|
|
52
|
+
const out = {
|
|
53
|
+
dataPath: null,
|
|
54
|
+
resultsPath: null,
|
|
55
|
+
spreadValues: DEFAULT_SPREAD_VALUES,
|
|
56
|
+
incrementValues: DEFAULT_INCREMENT_VALUES,
|
|
57
|
+
ratioValues: DEFAULT_RATIO_VALUES,
|
|
58
|
+
maxOrders: DEFAULT_MAX_ORDERS,
|
|
59
|
+
feeRoundtripPct: DEFAULT_FEE_ROUNDTRIP_PCT,
|
|
60
|
+
minSpreadFactor: DEFAULT_MIN_SPREAD_FACTOR,
|
|
61
|
+
capital: DEFAULT_CAPITAL,
|
|
62
|
+
repositionPct: DEFAULT_REPOSITION_PCT,
|
|
63
|
+
btsCreateFee: DEFAULT_BTS_CREATE_FEE,
|
|
64
|
+
btsCancelFee: DEFAULT_BTS_CANCEL_FEE,
|
|
65
|
+
makerCreateFactor: DEFAULT_BTS_MAKER_CREATE_FACTOR,
|
|
66
|
+
txFeePrice: DEFAULT_TX_FEE_PRICE,
|
|
67
|
+
topN: 15,
|
|
68
|
+
};
|
|
69
|
+
for (let i = 0; i < args.length; i++) {
|
|
70
|
+
const arg = args[i];
|
|
71
|
+
const val = args[i + 1];
|
|
72
|
+
if (arg === '--help' || arg === '-h') {
|
|
73
|
+
printHelp();
|
|
74
|
+
process.exit(0);
|
|
75
|
+
}
|
|
76
|
+
if (!val)
|
|
77
|
+
continue;
|
|
78
|
+
switch (arg) {
|
|
79
|
+
case '--data':
|
|
80
|
+
out.dataPath = path.resolve(val);
|
|
81
|
+
i++;
|
|
82
|
+
break;
|
|
83
|
+
case '--results':
|
|
84
|
+
out.resultsPath = path.resolve(val);
|
|
85
|
+
i++;
|
|
86
|
+
break;
|
|
87
|
+
case '--spread':
|
|
88
|
+
out.spreadValues = parseListOrRange(val, DEFAULT_SPREAD_VALUES);
|
|
89
|
+
i++;
|
|
90
|
+
break;
|
|
91
|
+
case '--increment':
|
|
92
|
+
out.incrementValues = parseListOrRange(val, DEFAULT_INCREMENT_VALUES);
|
|
93
|
+
i++;
|
|
94
|
+
break;
|
|
95
|
+
case '--ratio':
|
|
96
|
+
out.ratioValues = parseListOrRange(val, DEFAULT_RATIO_VALUES);
|
|
97
|
+
i++;
|
|
98
|
+
break;
|
|
99
|
+
case '--max-orders':
|
|
100
|
+
out.maxOrders = Number(val);
|
|
101
|
+
i++;
|
|
102
|
+
break;
|
|
103
|
+
case '--fee':
|
|
104
|
+
out.feeRoundtripPct = Number(val);
|
|
105
|
+
i++;
|
|
106
|
+
break;
|
|
107
|
+
case '--min-spread-factor':
|
|
108
|
+
out.minSpreadFactor = Number(val);
|
|
109
|
+
i++;
|
|
110
|
+
break;
|
|
111
|
+
case '--capital':
|
|
112
|
+
out.capital = Number(val);
|
|
113
|
+
i++;
|
|
114
|
+
break;
|
|
115
|
+
case '--reposition':
|
|
116
|
+
out.repositionPct = Number(val);
|
|
117
|
+
i++;
|
|
118
|
+
break;
|
|
119
|
+
case '--bts-create-fee':
|
|
120
|
+
out.btsCreateFee = Number(val);
|
|
121
|
+
i++;
|
|
122
|
+
break;
|
|
123
|
+
case '--bts-cancel-fee':
|
|
124
|
+
out.btsCancelFee = Number(val);
|
|
125
|
+
i++;
|
|
126
|
+
break;
|
|
127
|
+
case '--maker-create-factor':
|
|
128
|
+
out.makerCreateFactor = Number(val);
|
|
129
|
+
i++;
|
|
130
|
+
break;
|
|
131
|
+
case '--tx-fee-price':
|
|
132
|
+
out.txFeePrice = Number(val);
|
|
133
|
+
i++;
|
|
134
|
+
break;
|
|
135
|
+
case '--top':
|
|
136
|
+
out.topN = Number(val);
|
|
137
|
+
i++;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!out.dataPath) {
|
|
142
|
+
throw new Error('--data <path-to-lp-candles.json> is required');
|
|
143
|
+
}
|
|
144
|
+
if (!out.resultsPath) {
|
|
145
|
+
throw new Error('--results <path-to-optimization-results.json> is required');
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
function printHelp() {
|
|
150
|
+
console.log('AMA Sweep Backtest — persistent grid simulation with weight profiles');
|
|
151
|
+
console.log('');
|
|
152
|
+
console.log('Usage:');
|
|
153
|
+
console.log(' node dist/analysis/bot_fitting/backtest_ama_sweep.js [options]');
|
|
154
|
+
console.log('');
|
|
155
|
+
console.log('Options:');
|
|
156
|
+
console.log(' --data <path> LP candle JSON');
|
|
157
|
+
console.log(' --results <path> AMA optimizer results JSON');
|
|
158
|
+
console.log(' --spread <spec> Spread values (% ): 1:10:0.5 or 2,4,8');
|
|
159
|
+
console.log(' --increment <spec> Increment values (%): 0.5:5:0.5 or 1,2,3');
|
|
160
|
+
console.log(' --ratio <spec> Max/min ratio: 1.5,2,3,5');
|
|
161
|
+
console.log(' --max-orders <n> Max orders per side (default: 20, actual limited by ratio+increment)');
|
|
162
|
+
console.log(' --fee <pct> Round-trip fee % (default: 0.20)');
|
|
163
|
+
console.log(' --min-spread-factor <n> Spread >= factor * increment (default: 2.1)');
|
|
164
|
+
console.log(' --capital <n> Notional capital per side (default: 10000)');
|
|
165
|
+
console.log(' --reposition <pct> AMA drift % to trigger re-center (default: 2.5)');
|
|
166
|
+
console.log(' --bts-create-fee <n> BTS create fee (default: 0.48260)');
|
|
167
|
+
console.log(' --bts-cancel-fee <n> BTS cancel fee (default: 0.00482)');
|
|
168
|
+
console.log(' --maker-create-factor Maker share of create fee (default: 0.10)');
|
|
169
|
+
console.log(' --tx-fee-price <n> Convert BTS fees into backtest units (default: 1.0)');
|
|
170
|
+
console.log(' --top <n> Show top N results (default: 15)');
|
|
171
|
+
}
|
|
172
|
+
function loadAmaStrategies(resultsPath) {
|
|
173
|
+
const json = readJSON(resultsPath);
|
|
174
|
+
const amas = json.meta?.amas;
|
|
175
|
+
if (!amas)
|
|
176
|
+
throw new Error('No meta.amas found in results file.');
|
|
177
|
+
const out = [];
|
|
178
|
+
for (const [key, val] of Object.entries(amas)) {
|
|
179
|
+
const v = val;
|
|
180
|
+
if (!v || !Number.isFinite(v.er))
|
|
181
|
+
continue;
|
|
182
|
+
out.push({ id: key, name: v.label || key, er: v.er, fast: v.fast, slow: v.slow });
|
|
183
|
+
}
|
|
184
|
+
out.sort((a, b) => a.id.localeCompare(b.id));
|
|
185
|
+
if (out.length === 0)
|
|
186
|
+
throw new Error('No valid AMA strategies found');
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
// ── Order sizing with weight profiles ────────────────────────────────────────
|
|
190
|
+
/**
|
|
191
|
+
* Allocate capital across N levels using weight profile.
|
|
192
|
+
* weight > 0: mountain (more near center, exponential decay outward)
|
|
193
|
+
* weight = 0: neutral (equal)
|
|
194
|
+
* weight < 0: valley (more at edges, inverted decay)
|
|
195
|
+
*
|
|
196
|
+
* @param {number} totalFunds Capital for this side
|
|
197
|
+
* @param {number} n Number of levels
|
|
198
|
+
* @param {number} weight Profile weight factor
|
|
199
|
+
* @param {number} incrementFactor Increment as fraction (e.g. 0.02 for 2%)
|
|
200
|
+
* @returns {number[]} Size per level, index 0 = closest to center
|
|
201
|
+
*/
|
|
202
|
+
function allocateFundsByWeights(totalFunds, n, weight, incrementFactor) {
|
|
203
|
+
if (n <= 0)
|
|
204
|
+
return [];
|
|
205
|
+
if (weight === 0) {
|
|
206
|
+
const sz = totalFunds / n;
|
|
207
|
+
return new Array(n).fill(sz);
|
|
208
|
+
}
|
|
209
|
+
const base = 1 - incrementFactor;
|
|
210
|
+
const absWeight = Math.abs(weight);
|
|
211
|
+
const raw = new Array(n);
|
|
212
|
+
for (let i = 0; i < n; i++) {
|
|
213
|
+
// i=0 is closest to center
|
|
214
|
+
raw[i] = Math.pow(base, i * absWeight);
|
|
215
|
+
}
|
|
216
|
+
if (weight < 0) {
|
|
217
|
+
// Valley: reverse so outer levels (high i) get the large weights
|
|
218
|
+
raw.reverse();
|
|
219
|
+
}
|
|
220
|
+
const total = raw.reduce((s, w) => s + w, 0) || 1;
|
|
221
|
+
return raw.map((w) => (w / total) * totalFunds);
|
|
222
|
+
}
|
|
223
|
+
// ── Persistent grid simulation ───────────────────────────────────────────────
|
|
224
|
+
/**
|
|
225
|
+
* Build a fresh grid centered at `center`.
|
|
226
|
+
*
|
|
227
|
+
* Grid placement (matches real bot):
|
|
228
|
+
* Level k (1-based):
|
|
229
|
+
* offset_k = spreadPct/2/100 + (k-1) * incrementPct
|
|
230
|
+
* buyPrice = center * (1 - offset_k)
|
|
231
|
+
* sellPrice = center * (1 + offset_k)
|
|
232
|
+
*
|
|
233
|
+
* So spread controls the dead zone around center (no orders within spread/2),
|
|
234
|
+
* and increment is the gap between successive orders on the same side.
|
|
235
|
+
*
|
|
236
|
+
* Returns arrays of buy and sell order objects with fixed chain prices and sizes.
|
|
237
|
+
*/
|
|
238
|
+
function buildGrid(center, params, capitalPerSide, weightFactor) {
|
|
239
|
+
const { incrementPct, maxMinRatio, maxOrders, spreadPct } = params;
|
|
240
|
+
const minBound = center / maxMinRatio;
|
|
241
|
+
const maxBound = center * maxMinRatio;
|
|
242
|
+
const halfSpread = spreadPct / 200; // as fraction
|
|
243
|
+
const buys = [];
|
|
244
|
+
const sells = [];
|
|
245
|
+
// Fill as many levels as fit within ratio bounds (up to maxOrders cap)
|
|
246
|
+
for (let k = 1; k <= maxOrders; k++) {
|
|
247
|
+
const offset = halfSpread + (k - 1) * incrementPct;
|
|
248
|
+
if (offset >= 0.95)
|
|
249
|
+
continue;
|
|
250
|
+
const buyPrice = center * (1 - offset);
|
|
251
|
+
const sellPrice = center * (1 + offset);
|
|
252
|
+
// Bounds check — skip levels outside ratio range
|
|
253
|
+
if (buyPrice < minBound || sellPrice > maxBound)
|
|
254
|
+
continue;
|
|
255
|
+
buys.push({ level: k, price: buyPrice, filledBar: -1, size: 0 });
|
|
256
|
+
sells.push({ level: k, price: sellPrice, filledBar: -1, size: 0 });
|
|
257
|
+
}
|
|
258
|
+
// Size allocation — index 0 = closest to center
|
|
259
|
+
const buySizes = allocateFundsByWeights(capitalPerSide, buys.length, weightFactor, incrementPct);
|
|
260
|
+
const sellSizes = allocateFundsByWeights(capitalPerSide, sells.length, weightFactor, incrementPct);
|
|
261
|
+
buys.forEach((o, i) => { o.size = buySizes[i] || 0; });
|
|
262
|
+
sells.forEach((o, i) => { o.size = sellSizes[i] || 0; });
|
|
263
|
+
return { buys, sells };
|
|
264
|
+
}
|
|
265
|
+
function closeFilledInventoryAtPrice(openBuys, openSells, exitPrice, feeRoundtripPct) {
|
|
266
|
+
if (!Number.isFinite(exitPrice) || exitPrice <= 0) {
|
|
267
|
+
return { grossUnits: 0, profitUnits: 0, closedOrders: 0 };
|
|
268
|
+
}
|
|
269
|
+
let grossUnits = 0;
|
|
270
|
+
let profitUnits = 0;
|
|
271
|
+
let closedOrders = 0;
|
|
272
|
+
for (const order of openBuys.values()) {
|
|
273
|
+
if (!order?.filled || !Number.isFinite(order.price) || order.price <= 0)
|
|
274
|
+
continue;
|
|
275
|
+
if (!Number.isFinite(order.size) || order.size <= 0)
|
|
276
|
+
continue;
|
|
277
|
+
const grossPct = (exitPrice / order.price - 1) * 100;
|
|
278
|
+
const netPct = grossPct - feeRoundtripPct;
|
|
279
|
+
grossUnits += order.size * (grossPct / 100);
|
|
280
|
+
profitUnits += order.size * (netPct / 100);
|
|
281
|
+
closedOrders++;
|
|
282
|
+
}
|
|
283
|
+
for (const order of openSells.values()) {
|
|
284
|
+
if (!order?.filled || !Number.isFinite(order.price) || order.price <= 0)
|
|
285
|
+
continue;
|
|
286
|
+
if (!Number.isFinite(order.size) || order.size <= 0)
|
|
287
|
+
continue;
|
|
288
|
+
const grossPct = (order.price / exitPrice - 1) * 100;
|
|
289
|
+
const netPct = grossPct - feeRoundtripPct;
|
|
290
|
+
grossUnits += order.size * (grossPct / 100);
|
|
291
|
+
profitUnits += order.size * (netPct / 100);
|
|
292
|
+
closedOrders++;
|
|
293
|
+
}
|
|
294
|
+
return { grossUnits, profitUnits, closedOrders };
|
|
295
|
+
}
|
|
296
|
+
function countCancelableOrders(openBuys, openSells) {
|
|
297
|
+
let count = 0;
|
|
298
|
+
for (const order of openBuys.values()) {
|
|
299
|
+
if (!order?.filled)
|
|
300
|
+
count++;
|
|
301
|
+
}
|
|
302
|
+
for (const order of openSells.values()) {
|
|
303
|
+
if (!order?.filled)
|
|
304
|
+
count++;
|
|
305
|
+
}
|
|
306
|
+
return count;
|
|
307
|
+
}
|
|
308
|
+
function simulatePersistentGrid(candles, amaValues, params, weightName, weightFactor) {
|
|
309
|
+
const { spreadPct, incrementPct, maxMinRatio, maxOrders, feeRoundtripPct, capital, repositionThreshold, btsCreateFee, btsCancelFee, makerCreateFactor, txFeePrice } = params;
|
|
310
|
+
const skip = Math.max(20, Math.floor(candles.length * 0.1));
|
|
311
|
+
const capitalPerSide = capital;
|
|
312
|
+
// State
|
|
313
|
+
let gridCenter = amaValues[skip];
|
|
314
|
+
let grid = buildGrid(gridCenter, params, capitalPerSide, weightFactor);
|
|
315
|
+
let lastRepositionBar = skip;
|
|
316
|
+
// Open orders: maps level -> { price, size, bar, side }
|
|
317
|
+
const openBuys = new Map();
|
|
318
|
+
const openSells = new Map();
|
|
319
|
+
// Place initial orders
|
|
320
|
+
for (const o of grid.buys)
|
|
321
|
+
openBuys.set(o.level, { price: o.price, size: o.size, bar: skip });
|
|
322
|
+
for (const o of grid.sells)
|
|
323
|
+
openSells.set(o.level, { price: o.price, size: o.size, bar: skip });
|
|
324
|
+
let matchedPairs = 0;
|
|
325
|
+
let totalProfitUnits = 0; // profit in capital units (size * netPct)
|
|
326
|
+
let totalGrossUnits = 0;
|
|
327
|
+
let touchedOrders = 0;
|
|
328
|
+
let canceledOnReposition = 0;
|
|
329
|
+
let repositionCount = 0;
|
|
330
|
+
let peakOpenOrders = 0;
|
|
331
|
+
let imbalanceSum = 0;
|
|
332
|
+
let imbalanceSamples = 0;
|
|
333
|
+
let matchedOpenDurationBars = 0;
|
|
334
|
+
let gridAgeSumBars = 0;
|
|
335
|
+
let maxGridAgeBars = 0;
|
|
336
|
+
let centerDriftSumPct = 0;
|
|
337
|
+
let maxCenterDriftPct = 0;
|
|
338
|
+
let nearThresholdBars = 0;
|
|
339
|
+
let triggerDriftSumPct = 0;
|
|
340
|
+
let runningProfit = 0;
|
|
341
|
+
let peakEquityProfit = 0;
|
|
342
|
+
let maxDrawdown = 0;
|
|
343
|
+
// Track inventory risk: net exposure from filled-but-unmatched orders
|
|
344
|
+
let inventoryExposure = 0; // positive = long (bought), negative = short (sold)
|
|
345
|
+
let maxInventoryExposure = 0;
|
|
346
|
+
const liveBars = candles.length - skip - 1;
|
|
347
|
+
const ordersPerSide = grid.buys.length; // actual orders placed per side (clipped by ratio)
|
|
348
|
+
const makerCreateFeeBts = btsCreateFee * makerCreateFactor;
|
|
349
|
+
const newOrdersPerReposition = ordersPerSide * 2;
|
|
350
|
+
for (let i = skip + 1; i < candles.length; i++) {
|
|
351
|
+
const ama = amaValues[i];
|
|
352
|
+
const hi = candles[i].high;
|
|
353
|
+
const lo = candles[i].low;
|
|
354
|
+
const gridAgeBars = i - lastRepositionBar;
|
|
355
|
+
// ── Reposition check: AMA drifted too far from grid center ──────
|
|
356
|
+
const drift = Math.abs(ama - gridCenter) / gridCenter;
|
|
357
|
+
const driftPct = drift * 100;
|
|
358
|
+
centerDriftSumPct += driftPct;
|
|
359
|
+
if (driftPct > maxCenterDriftPct)
|
|
360
|
+
maxCenterDriftPct = driftPct;
|
|
361
|
+
gridAgeSumBars += gridAgeBars;
|
|
362
|
+
if (gridAgeBars > maxGridAgeBars)
|
|
363
|
+
maxGridAgeBars = gridAgeBars;
|
|
364
|
+
if (drift >= repositionThreshold * 0.5)
|
|
365
|
+
nearThresholdBars++;
|
|
366
|
+
if (drift >= repositionThreshold) {
|
|
367
|
+
triggerDriftSumPct += driftPct;
|
|
368
|
+
canceledOnReposition += countCancelableOrders(openBuys, openSells);
|
|
369
|
+
repositionCount++;
|
|
370
|
+
const exitPrice = Number.isFinite(ama) && ama > 0 ? ama : candles[i].close;
|
|
371
|
+
const forcedClose = closeFilledInventoryAtPrice(openBuys, openSells, exitPrice, feeRoundtripPct);
|
|
372
|
+
totalGrossUnits += forcedClose.grossUnits;
|
|
373
|
+
totalProfitUnits += forcedClose.profitUnits;
|
|
374
|
+
runningProfit += forcedClose.profitUnits;
|
|
375
|
+
// Unmatched inventory is realized on reposition (position closed at market)
|
|
376
|
+
inventoryExposure = 0;
|
|
377
|
+
openBuys.clear();
|
|
378
|
+
openSells.clear();
|
|
379
|
+
// Re-center grid
|
|
380
|
+
gridCenter = ama;
|
|
381
|
+
grid = buildGrid(gridCenter, params, capitalPerSide, weightFactor);
|
|
382
|
+
lastRepositionBar = i;
|
|
383
|
+
for (const o of grid.buys)
|
|
384
|
+
openBuys.set(o.level, { price: o.price, size: o.size, bar: i });
|
|
385
|
+
for (const o of grid.sells)
|
|
386
|
+
openSells.set(o.level, { price: o.price, size: o.size, bar: i });
|
|
387
|
+
}
|
|
388
|
+
// ── Track imbalance & peak ──────────────────────────────────────
|
|
389
|
+
const currentOpen = openBuys.size + openSells.size;
|
|
390
|
+
if (currentOpen > peakOpenOrders)
|
|
391
|
+
peakOpenOrders = currentOpen;
|
|
392
|
+
imbalanceSum += Math.abs(openBuys.size - openSells.size);
|
|
393
|
+
imbalanceSamples++;
|
|
394
|
+
// ── Check fills on PERSISTENT orders (fixed chain prices) ───────
|
|
395
|
+
// Only check UNFILLED orders (skip already-filled pending match)
|
|
396
|
+
const filledBuysThisBar = [];
|
|
397
|
+
const filledSellsThisBar = [];
|
|
398
|
+
for (const [lvl, order] of openBuys) {
|
|
399
|
+
if (!order.filled && lo <= order.price) {
|
|
400
|
+
filledBuysThisBar.push({ lvl, order });
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
for (const [lvl, order] of openSells) {
|
|
404
|
+
if (!order.filled && hi >= order.price) {
|
|
405
|
+
filledSellsThisBar.push({ lvl, order });
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Process buy fills
|
|
409
|
+
for (const fb of filledBuysThisBar) {
|
|
410
|
+
touchedOrders++;
|
|
411
|
+
inventoryExposure += fb.order.size; // bought → long exposure
|
|
412
|
+
// Mark as filled, waiting for paired sell at same level
|
|
413
|
+
openBuys.set(fb.lvl, { ...fb.order, filled: true, filledBar: i, side: 'buy' });
|
|
414
|
+
}
|
|
415
|
+
// Process sell fills
|
|
416
|
+
for (const fs of filledSellsThisBar) {
|
|
417
|
+
touchedOrders++;
|
|
418
|
+
inventoryExposure -= fs.order.size; // sold → short exposure
|
|
419
|
+
// Mark as filled, waiting for paired buy at same level
|
|
420
|
+
openSells.set(fs.lvl, { ...fs.order, filled: true, filledBar: i, side: 'sell' });
|
|
421
|
+
}
|
|
422
|
+
// Match completed pairs: both buy AND sell at same level are filled
|
|
423
|
+
for (let k = 1; k <= maxOrders; k++) {
|
|
424
|
+
const buyEntry = openBuys.get(k);
|
|
425
|
+
const sellEntry = openSells.get(k);
|
|
426
|
+
if (!buyEntry?.filled || !sellEntry?.filled)
|
|
427
|
+
continue;
|
|
428
|
+
// Matched pair!
|
|
429
|
+
openBuys.delete(k);
|
|
430
|
+
openSells.delete(k);
|
|
431
|
+
matchedPairs++;
|
|
432
|
+
const duration = Math.abs(sellEntry.filledBar - buyEntry.filledBar);
|
|
433
|
+
matchedOpenDurationBars += duration;
|
|
434
|
+
const avgSize = (buyEntry.size + sellEntry.size) / 2;
|
|
435
|
+
const pairGrossPct = (sellEntry.price / buyEntry.price - 1) * 100;
|
|
436
|
+
const pairNetPct = pairGrossPct - feeRoundtripPct;
|
|
437
|
+
const profitUnits = avgSize * (pairNetPct / 100);
|
|
438
|
+
totalGrossUnits += avgSize * (pairGrossPct / 100);
|
|
439
|
+
totalProfitUnits += profitUnits;
|
|
440
|
+
runningProfit += profitUnits;
|
|
441
|
+
}
|
|
442
|
+
// Track max inventory exposure
|
|
443
|
+
const absExposure = Math.abs(inventoryExposure);
|
|
444
|
+
if (absExposure > maxInventoryExposure)
|
|
445
|
+
maxInventoryExposure = absExposure;
|
|
446
|
+
// ── Drawdown tracking ───────────────────────────────────────────
|
|
447
|
+
const markPrice = Number.isFinite(candles[i].close) && candles[i].close > 0
|
|
448
|
+
? candles[i].close
|
|
449
|
+
: ((Number.isFinite(ama) && ama > 0) ? ama : gridCenter);
|
|
450
|
+
const unrealized = closeFilledInventoryAtPrice(openBuys, openSells, markPrice, feeRoundtripPct).profitUnits;
|
|
451
|
+
const equityProfit = runningProfit + unrealized;
|
|
452
|
+
if (equityProfit > peakEquityProfit)
|
|
453
|
+
peakEquityProfit = equityProfit;
|
|
454
|
+
const dd = peakEquityProfit - equityProfit;
|
|
455
|
+
if (dd > maxDrawdown)
|
|
456
|
+
maxDrawdown = dd;
|
|
457
|
+
}
|
|
458
|
+
const fillEfficiency = touchedOrders > 0 ? (matchedPairs / touchedOrders) * 100 : 0;
|
|
459
|
+
const pairsPerDay = liveBars > 0 ? matchedPairs / (liveBars / 24) : 0;
|
|
460
|
+
const avgOpenDurationBars = matchedPairs > 0 ? matchedOpenDurationBars / matchedPairs : 0;
|
|
461
|
+
const avgImbalance = imbalanceSamples > 0 ? imbalanceSum / imbalanceSamples : 0;
|
|
462
|
+
const avgProfitPerPair = matchedPairs > 0 ? totalProfitUnits / matchedPairs : 0;
|
|
463
|
+
const profitPerCapital = capital > 0 ? totalProfitUnits / (capital * 2) : 0; // total capital = 2 sides
|
|
464
|
+
const maxDrawdownPct = capital > 0 ? (maxDrawdown / (capital * 2)) * 100 : 0;
|
|
465
|
+
const avgGridAgeBars = liveBars > 0 ? gridAgeSumBars / liveBars : 0;
|
|
466
|
+
const avgCenterDriftPct = liveBars > 0 ? centerDriftSumPct / liveBars : 0;
|
|
467
|
+
const nearThresholdBarsPct = liveBars > 0 ? (nearThresholdBars / liveBars) * 100 : 0;
|
|
468
|
+
const avgTriggerDriftPct = repositionCount > 0 ? triggerDriftSumPct / repositionCount : 0;
|
|
469
|
+
const avgCancelOrdersPerReposition = repositionCount > 0 ? canceledOnReposition / repositionCount : 0;
|
|
470
|
+
const estimatedFeePerRepositionBts = newOrdersPerReposition * makerCreateFeeBts + avgCancelOrdersPerReposition * btsCancelFee;
|
|
471
|
+
const totalRepositionFeesBts = repositionCount * estimatedFeePerRepositionBts;
|
|
472
|
+
const feePerDayBts = liveBars > 0 ? totalRepositionFeesBts / (liveBars / 24) : 0;
|
|
473
|
+
const totalRepositionFeeUnits = totalRepositionFeesBts * txFeePrice;
|
|
474
|
+
const netProfitUnits = totalProfitUnits - totalRepositionFeeUnits;
|
|
475
|
+
const netProfitPerCapital = capital > 0 ? netProfitUnits / (capital * 2) : 0;
|
|
476
|
+
// Score: profit per capital scaled by activity (log pairs to avoid pure frequency chasing)
|
|
477
|
+
const activityBonus = matchedPairs > 0 ? Math.log10(matchedPairs) : null;
|
|
478
|
+
const grossScore = activityBonus == null ? -Infinity : (profitPerCapital * 100 * activityBonus - maxDrawdownPct * 0.5);
|
|
479
|
+
const netScore = activityBonus == null ? -Infinity : (netProfitPerCapital * 100 * activityBonus - maxDrawdownPct * 0.5);
|
|
480
|
+
const score = netScore;
|
|
481
|
+
return {
|
|
482
|
+
weightName,
|
|
483
|
+
spreadPct,
|
|
484
|
+
incrementPct: incrementPct * 100, // store as %
|
|
485
|
+
maxMinRatio,
|
|
486
|
+
matchedPairs,
|
|
487
|
+
touchedOrders,
|
|
488
|
+
fillEfficiency,
|
|
489
|
+
totalProfitUnits,
|
|
490
|
+
totalGrossUnits,
|
|
491
|
+
profitPerCapital,
|
|
492
|
+
pairsPerDay,
|
|
493
|
+
avgOpenDurationBars,
|
|
494
|
+
avgProfitPerPair,
|
|
495
|
+
peakOpenOrders,
|
|
496
|
+
avgImbalance,
|
|
497
|
+
canceledOnReposition,
|
|
498
|
+
repositionCount,
|
|
499
|
+
maxDrawdown,
|
|
500
|
+
maxDrawdownPct,
|
|
501
|
+
maxInventoryExposure,
|
|
502
|
+
avgGridAgeBars,
|
|
503
|
+
maxGridAgeBars,
|
|
504
|
+
avgCenterDriftPct,
|
|
505
|
+
maxCenterDriftPct,
|
|
506
|
+
nearThresholdBarsPct,
|
|
507
|
+
avgTriggerDriftPct,
|
|
508
|
+
makerCreateFeeBts,
|
|
509
|
+
btsCancelFee,
|
|
510
|
+
avgCancelOrdersPerReposition,
|
|
511
|
+
estimatedFeePerRepositionBts,
|
|
512
|
+
totalRepositionFeesBts,
|
|
513
|
+
feePerDayBts,
|
|
514
|
+
netProfitUnits,
|
|
515
|
+
netProfitPerCapital,
|
|
516
|
+
ordersPerSide,
|
|
517
|
+
grossScore,
|
|
518
|
+
netScore,
|
|
519
|
+
score,
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
// ── Per-AMA sweep logic (runs in main thread or worker) ─────────────────────
|
|
523
|
+
function sweepOneAma(strategy, candles, closes, weightEntries, cfg) {
|
|
524
|
+
const amaValues = calculateAMA(closes, { erPeriod: strategy.er, fastPeriod: strategy.fast, slowPeriod: strategy.slow });
|
|
525
|
+
let best = null;
|
|
526
|
+
const top5 = [];
|
|
527
|
+
const allSims = [];
|
|
528
|
+
let evaluated = 0;
|
|
529
|
+
const minSpreadFactor = Number.isFinite(cfg.minSpreadFactor) && cfg.minSpreadFactor > 0 ? cfg.minSpreadFactor : null;
|
|
530
|
+
for (const spreadPct of cfg.spreadValues) {
|
|
531
|
+
for (const incrementPctRaw of cfg.incrementValues) {
|
|
532
|
+
if (spreadPct < cfg.feeRoundtripPct + 0.01)
|
|
533
|
+
continue;
|
|
534
|
+
if (minSpreadFactor != null && spreadPct < (incrementPctRaw * minSpreadFactor))
|
|
535
|
+
continue;
|
|
536
|
+
for (const maxMinRatio of cfg.ratioValues) {
|
|
537
|
+
for (const [weightName, weightFactor] of weightEntries) {
|
|
538
|
+
evaluated++;
|
|
539
|
+
const sim = simulatePersistentGrid(candles, amaValues, {
|
|
540
|
+
spreadPct,
|
|
541
|
+
incrementPct: incrementPctRaw / 100,
|
|
542
|
+
maxMinRatio,
|
|
543
|
+
maxOrders: cfg.maxOrders,
|
|
544
|
+
feeRoundtripPct: cfg.feeRoundtripPct,
|
|
545
|
+
capital: cfg.capital,
|
|
546
|
+
repositionThreshold: cfg.repositionPct / 100,
|
|
547
|
+
btsCreateFee: cfg.btsCreateFee,
|
|
548
|
+
btsCancelFee: cfg.btsCancelFee,
|
|
549
|
+
makerCreateFactor: cfg.makerCreateFactor,
|
|
550
|
+
txFeePrice: cfg.txFeePrice,
|
|
551
|
+
}, weightName, weightFactor);
|
|
552
|
+
if (!best || sim.score > best.score)
|
|
553
|
+
best = sim;
|
|
554
|
+
if (sim.matchedPairs > 0) {
|
|
555
|
+
const t5key = `${sim.spreadPct}|${sim.incrementPct}|${sim.maxMinRatio}|${sim.weightName}`;
|
|
556
|
+
const existing = top5.findIndex((t) => `${t.spreadPct}|${t.incrementPct}|${t.maxMinRatio}|${t.weightName}` === t5key);
|
|
557
|
+
if (existing < 0) {
|
|
558
|
+
top5.push(sim);
|
|
559
|
+
top5.sort((a, b) => b.score - a.score);
|
|
560
|
+
if (top5.length > 5)
|
|
561
|
+
top5.length = 5;
|
|
562
|
+
}
|
|
563
|
+
allSims.push(sim);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return { strategy, best, top5, allSims, evaluated };
|
|
570
|
+
}
|
|
571
|
+
// ── Worker thread handler ───────────────────────────────────────────────────
|
|
572
|
+
if (!isMainThread) {
|
|
573
|
+
const { strategy, candles, closes, weightEntries, cfg } = workerData;
|
|
574
|
+
const result = sweepOneAma(strategy, candles, closes, weightEntries, cfg);
|
|
575
|
+
parentPort.postMessage(result);
|
|
576
|
+
process.exit(0);
|
|
577
|
+
}
|
|
578
|
+
// ── Parallel dispatch (main thread) ─────────────────────────────────────────
|
|
579
|
+
function runParallel(strategies, candles, closes, weightEntries, cfg) {
|
|
580
|
+
const numCpus = Math.min(os.cpus().length, strategies.length);
|
|
581
|
+
console.log(` Workers: ${numCpus} threads (${os.cpus().length} CPUs available)\n`);
|
|
582
|
+
return Promise.all(strategies.map((strategy) => {
|
|
583
|
+
return new Promise((resolve, reject) => {
|
|
584
|
+
const worker = new Worker(__filename, {
|
|
585
|
+
workerData: { strategy, candles, closes, weightEntries, cfg },
|
|
586
|
+
});
|
|
587
|
+
worker.on('message', resolve);
|
|
588
|
+
worker.on('error', reject);
|
|
589
|
+
worker.on('exit', (code) => {
|
|
590
|
+
if (code !== 0)
|
|
591
|
+
reject(new Error(`Worker exited with code ${code}`));
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
}));
|
|
595
|
+
}
|
|
596
|
+
// ── Main ────────────────────────────────────────────────────────────────────
|
|
597
|
+
async function run() {
|
|
598
|
+
const cfg = parseArgs();
|
|
599
|
+
const loaded = loadLpData(cfg.dataPath);
|
|
600
|
+
const candles = loaded.candles;
|
|
601
|
+
const closes = candles.map((c) => c.close);
|
|
602
|
+
const strategies = loadAmaStrategies(cfg.resultsPath);
|
|
603
|
+
const weightEntries = Object.entries(WEIGHT_PROFILES);
|
|
604
|
+
const totalCombos = cfg.spreadValues.length * cfg.incrementValues.length *
|
|
605
|
+
cfg.ratioValues.length * weightEntries.length;
|
|
606
|
+
console.log('================================================================================');
|
|
607
|
+
console.log(' AMA SWEEP BACKTEST — persistent grid + weight profiles');
|
|
608
|
+
console.log('================================================================================');
|
|
609
|
+
console.log(` Data: ${path.basename(cfg.dataPath)} (${candles.length} candles, ~${(candles.length / 24).toFixed(0)} days)`);
|
|
610
|
+
console.log(` AMAs: ${strategies.map((s) => s.id).join(', ')}`);
|
|
611
|
+
console.log(` Weights: ${weightEntries.map(([n, w]) => `${n}(${w})`).join(', ')}`);
|
|
612
|
+
console.log(` Spread: ${cfg.spreadValues[0]}..${cfg.spreadValues[cfg.spreadValues.length - 1]}% (${cfg.spreadValues.length})`);
|
|
613
|
+
console.log(` Increment: ${cfg.incrementValues[0]}..${cfg.incrementValues[cfg.incrementValues.length - 1]}% (${cfg.incrementValues.length})`);
|
|
614
|
+
console.log(` Ratio: ${cfg.ratioValues[0]}..${cfg.ratioValues[cfg.ratioValues.length - 1]} (${cfg.ratioValues.length})`);
|
|
615
|
+
console.log(` Max orders: ${cfg.maxOrders}/side (actual count from ratio+increment) | Capital: ${cfg.capital}/side | Fee: ${cfg.feeRoundtripPct}%`);
|
|
616
|
+
console.log(` Spread floor: > fee (${cfg.feeRoundtripPct}%)`);
|
|
617
|
+
console.log(` Reposition: ${cfg.repositionPct}% AMA drift from grid center`);
|
|
618
|
+
console.log(` Tx model: create=${fmt(cfg.btsCreateFee, 5)} BTS, cancel=${fmt(cfg.btsCancelFee, 5)} BTS, maker=${fmt(cfg.makerCreateFactor * 100, 1)}%, 1 BTS=${fmt(cfg.txFeePrice, 2)} units`);
|
|
619
|
+
console.log(` Combos/AMA: ${totalCombos} | Total: ${totalCombos * strategies.length}\n`);
|
|
620
|
+
// ── Run AMA sweeps in parallel (one worker per AMA strategy) ──────
|
|
621
|
+
const byAma = [];
|
|
622
|
+
const allResults = [];
|
|
623
|
+
const workerResults = await runParallel(strategies, candles, closes, weightEntries, cfg);
|
|
624
|
+
for (const wr of workerResults) {
|
|
625
|
+
byAma.push(wr);
|
|
626
|
+
for (const sim of wr.allSims) {
|
|
627
|
+
allResults.push({ strategy: wr.strategy, sim });
|
|
628
|
+
}
|
|
629
|
+
const b = wr.best;
|
|
630
|
+
const bestLabel = b && b.matchedPairs > 0
|
|
631
|
+
? `score=${fmt(b.score, 1)} pairs=${b.matchedPairs} net/cap=${fmt(b.netProfitPerCapital * 100, 1)}% gross=${fmt(b.profitPerCapital * 100, 1)}%`
|
|
632
|
+
: 'no fills';
|
|
633
|
+
process.stdout.write(` ${wr.strategy.id} (ER=${wr.strategy.er}, F=${wr.strategy.fast}, S=${wr.strategy.slow}): ${wr.evaluated} combos, ${bestLabel}\n`);
|
|
634
|
+
}
|
|
635
|
+
// ── Per-AMA best ────────────────────────────────────────────────────────
|
|
636
|
+
console.log('\n================================================================================');
|
|
637
|
+
console.log(' BEST PARAMS PER AMA');
|
|
638
|
+
console.log('================================================================================');
|
|
639
|
+
console.log('AMA | wt | spr% | inc% | ratio | nOrd | pairs | net/cap | gross/cap | drift | fee/d | score');
|
|
640
|
+
console.log('------+-------+-------+-------+-------+------+--------+---------+-----------+-------+-------+------');
|
|
641
|
+
for (const row of byAma) {
|
|
642
|
+
const b = row.best;
|
|
643
|
+
if (!b || b.matchedPairs === 0) {
|
|
644
|
+
console.log(`${row.strategy.id.padEnd(5)} | (no fills)`);
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
console.log(`${row.strategy.id.padEnd(5)} | ` +
|
|
648
|
+
`${b.weightName.padEnd(5)} | ` +
|
|
649
|
+
`${fmt(b.spreadPct, 1).padStart(5)} | ` +
|
|
650
|
+
`${fmt(b.incrementPct, 1).padStart(5)} | ` +
|
|
651
|
+
`${fmt(b.maxMinRatio, 2).padStart(5)} | ` +
|
|
652
|
+
`${String(b.ordersPerSide).padStart(4)} | ` +
|
|
653
|
+
`${String(b.matchedPairs).padStart(6)} | ` +
|
|
654
|
+
`${fmt(b.netProfitPerCapital * 100, 2).padStart(7)}% | ` +
|
|
655
|
+
`${fmt(b.profitPerCapital * 100, 2).padStart(10)}% | ` +
|
|
656
|
+
`${fmt(b.avgCenterDriftPct, 2).padStart(5)}% | ` +
|
|
657
|
+
`${fmt(b.feePerDayBts, 2).padStart(5)} | ` +
|
|
658
|
+
`${fmt(b.score, 1).padStart(6)}`);
|
|
659
|
+
}
|
|
660
|
+
// ── Top 5 per AMA ───────────────────────────────────────────────────────
|
|
661
|
+
for (const row of byAma) {
|
|
662
|
+
if (row.top5.length === 0)
|
|
663
|
+
continue;
|
|
664
|
+
console.log(`\n ${row.strategy.id} — Top 5:`);
|
|
665
|
+
console.log(' # | wt | spr% | inc% | ratio | nOrd | pairs | net/cap | drift | gAge | fee/d | score');
|
|
666
|
+
console.log(' --+-------+-------+-------+-------+------+--------+---------+-------+------+-------+------');
|
|
667
|
+
row.top5.forEach((b, idx) => {
|
|
668
|
+
console.log(` ${idx + 1} | ` +
|
|
669
|
+
`${b.weightName.padEnd(5)} | ` +
|
|
670
|
+
`${fmt(b.spreadPct, 1).padStart(5)} | ` +
|
|
671
|
+
`${fmt(b.incrementPct, 1).padStart(5)} | ` +
|
|
672
|
+
`${fmt(b.maxMinRatio, 2).padStart(5)} | ` +
|
|
673
|
+
`${String(b.ordersPerSide).padStart(4)} | ` +
|
|
674
|
+
`${String(b.matchedPairs).padStart(6)} | ` +
|
|
675
|
+
`${fmt(b.netProfitPerCapital * 100, 2).padStart(7)}% | ` +
|
|
676
|
+
`${fmt(b.avgCenterDriftPct, 2).padStart(5)}% | ` +
|
|
677
|
+
`${fmt(b.avgGridAgeBars, 0).padStart(4)} | ` +
|
|
678
|
+
`${fmt(b.feePerDayBts, 2).padStart(5)} | ` +
|
|
679
|
+
`${fmt(b.score, 1).padStart(6)}`);
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
// ── Global ranking (deduplicated) ───────────────────────────────────────
|
|
683
|
+
allResults.sort((a, b) => b.sim.score - a.sim.score);
|
|
684
|
+
const seen = new Set();
|
|
685
|
+
const deduped = [];
|
|
686
|
+
for (const r of allResults) {
|
|
687
|
+
const key = `${r.strategy.id}|${r.sim.spreadPct}|${r.sim.incrementPct}|${r.sim.maxMinRatio}|${r.sim.weightName}`;
|
|
688
|
+
if (seen.has(key))
|
|
689
|
+
continue;
|
|
690
|
+
seen.add(key);
|
|
691
|
+
deduped.push(r);
|
|
692
|
+
}
|
|
693
|
+
if (deduped.length === 0) {
|
|
694
|
+
console.log('\n NO CONFIGURATIONS PRODUCED ANY MATCHED PAIRS.');
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const topN = Math.min(cfg.topN, deduped.length);
|
|
698
|
+
console.log(`\n================================================================================`);
|
|
699
|
+
console.log(` GLOBAL TOP ${topN}`);
|
|
700
|
+
console.log('================================================================================');
|
|
701
|
+
console.log('# | AMA | wt | spr% | inc% | ratio | nOrd | pairs | net/cap | drift | fee/d | score');
|
|
702
|
+
console.log('---+-------+-------+-------+-------+-------+------+--------+---------+-------+-------+------');
|
|
703
|
+
for (let i = 0; i < topN; i++) {
|
|
704
|
+
const { strategy, sim } = deduped[i];
|
|
705
|
+
console.log(`${String(i + 1).padStart(2)} | ` +
|
|
706
|
+
`${strategy.id.padEnd(5)} | ` +
|
|
707
|
+
`${sim.weightName.padEnd(5)} | ` +
|
|
708
|
+
`${fmt(sim.spreadPct, 1).padStart(5)} | ` +
|
|
709
|
+
`${fmt(sim.incrementPct, 1).padStart(5)} | ` +
|
|
710
|
+
`${fmt(sim.maxMinRatio, 2).padStart(5)} | ` +
|
|
711
|
+
`${String(sim.ordersPerSide).padStart(4)} | ` +
|
|
712
|
+
`${String(sim.matchedPairs).padStart(6)} | ` +
|
|
713
|
+
`${fmt(sim.netProfitPerCapital * 100, 2).padStart(7)}% | ` +
|
|
714
|
+
`${fmt(sim.avgCenterDriftPct, 2).padStart(5)}% | ` +
|
|
715
|
+
`${fmt(sim.feePerDayBts, 2).padStart(5)} | ` +
|
|
716
|
+
`${fmt(sim.score, 1).padStart(6)}`);
|
|
717
|
+
}
|
|
718
|
+
// ── Winner ──────────────────────────────────────────────────────────────
|
|
719
|
+
const winner = deduped[0];
|
|
720
|
+
console.log(`\n================================================================================`);
|
|
721
|
+
console.log(` WINNER`);
|
|
722
|
+
console.log('================================================================================');
|
|
723
|
+
console.log(` AMA: ${winner.strategy.id} (ER=${winner.strategy.er}, Fast=${winner.strategy.fast}, Slow=${winner.strategy.slow})`);
|
|
724
|
+
console.log(` Weight: ${winner.sim.weightName} (${WEIGHT_PROFILES[winner.sim.weightName]})`);
|
|
725
|
+
console.log(` Spread: ${fmt(winner.sim.spreadPct, 1)}%`);
|
|
726
|
+
console.log(` Increment: ${fmt(winner.sim.incrementPct, 1)}%`);
|
|
727
|
+
console.log(` Ratio: ${fmt(winner.sim.maxMinRatio, 1)}x`);
|
|
728
|
+
console.log(` Pairs: ${winner.sim.matchedPairs} (${fmt(winner.sim.pairsPerDay, 2)}/day)`);
|
|
729
|
+
console.log(` Fill eff: ${fmt(winner.sim.fillEfficiency, 1)}%`);
|
|
730
|
+
console.log(` Profit/cap: gross ${fmt(winner.sim.profitPerCapital * 100, 2)}% | net ${fmt(winner.sim.netProfitPerCapital * 100, 2)}%`);
|
|
731
|
+
console.log(` Net profit: ${fmt(winner.sim.netProfitUnits, 0)} units after ${fmt(winner.sim.totalRepositionFeesBts, 1)} BTS reposition fees`);
|
|
732
|
+
console.log(` Avg/pair: ${fmt(winner.sim.avgProfitPerPair, 2)} units`);
|
|
733
|
+
console.log(` Max DD: ${fmt(winner.sim.maxDrawdownPct, 2)}%`);
|
|
734
|
+
console.log(` Repositions: ${winner.sim.repositionCount}`);
|
|
735
|
+
console.log(` Grid age: avg ${fmt(winner.sim.avgGridAgeBars, 1)} bars | max ${fmt(winner.sim.maxGridAgeBars, 0)} bars`);
|
|
736
|
+
console.log(` Drift: avg ${fmt(winner.sim.avgCenterDriftPct, 2)}% | max ${fmt(winner.sim.maxCenterDriftPct, 2)}% | near-threshold ${fmt(winner.sim.nearThresholdBarsPct, 1)}%`);
|
|
737
|
+
console.log(` Tx burn: ${fmt(winner.sim.estimatedFeePerRepositionBts, 4)} BTS/reposition | ${fmt(winner.sim.feePerDayBts, 2)} BTS/day`);
|
|
738
|
+
console.log(` Score: ${fmt(winner.sim.score, 2)}`);
|
|
739
|
+
// ── Save JSON ───────────────────────────────────────────────────────────
|
|
740
|
+
const outName = `ama_sweep_results_${path.basename(cfg.dataPath, '.json')}.json`;
|
|
741
|
+
const outPath = path.join(__dirname, outName);
|
|
742
|
+
writeJSON(outPath, {
|
|
743
|
+
meta: {
|
|
744
|
+
generatedAt: new Date().toISOString(),
|
|
745
|
+
dataPath: path.relative(process.cwd(), cfg.dataPath),
|
|
746
|
+
resultsPath: path.relative(process.cwd(), cfg.resultsPath),
|
|
747
|
+
candles: candles.length,
|
|
748
|
+
days: candles.length / 24,
|
|
749
|
+
maxOrders: cfg.maxOrders,
|
|
750
|
+
capitalPerSide: cfg.capital,
|
|
751
|
+
feeRoundtripPct: cfg.feeRoundtripPct,
|
|
752
|
+
repositionPct: cfg.repositionPct,
|
|
753
|
+
btsCreateFee: cfg.btsCreateFee,
|
|
754
|
+
btsCancelFee: cfg.btsCancelFee,
|
|
755
|
+
makerCreateFactor: cfg.makerCreateFactor,
|
|
756
|
+
txFeePrice: cfg.txFeePrice,
|
|
757
|
+
weightProfiles: WEIGHT_PROFILES,
|
|
758
|
+
search: {
|
|
759
|
+
spreadValues: cfg.spreadValues,
|
|
760
|
+
incrementValues: cfg.incrementValues,
|
|
761
|
+
ratioValues: cfg.ratioValues,
|
|
762
|
+
minSpreadFactor: cfg.minSpreadFactor,
|
|
763
|
+
combosPerAma: totalCombos,
|
|
764
|
+
totalCombos: totalCombos * strategies.length,
|
|
765
|
+
},
|
|
766
|
+
scoring: 'netProfitPerCapital * 100 * log10(max(1, matchedPairs)) - maxDrawdownPct * 0.5',
|
|
767
|
+
},
|
|
768
|
+
strategies,
|
|
769
|
+
perAma: byAma.map((row) => ({
|
|
770
|
+
strategy: row.strategy,
|
|
771
|
+
evaluated: row.evaluated,
|
|
772
|
+
best: row.best,
|
|
773
|
+
top5: row.top5,
|
|
774
|
+
})),
|
|
775
|
+
globalTop: deduped.slice(0, Math.max(cfg.topN, 20)).map(({ strategy, sim }) => ({
|
|
776
|
+
ama: strategy.id,
|
|
777
|
+
amaParams: { er: strategy.er, fast: strategy.fast, slow: strategy.slow },
|
|
778
|
+
...sim,
|
|
779
|
+
})),
|
|
780
|
+
winner: {
|
|
781
|
+
ama: winner.strategy.id,
|
|
782
|
+
amaParams: { er: winner.strategy.er, fast: winner.strategy.fast, slow: winner.strategy.slow },
|
|
783
|
+
...winner.sim,
|
|
784
|
+
},
|
|
785
|
+
});
|
|
786
|
+
console.log(`\nSaved: ${path.relative(process.cwd(), outPath)}`);
|
|
787
|
+
}
|
|
788
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
789
|
+
run().catch((err) => { console.error(err); process.exit(1); });
|
|
790
|
+
}
|
|
791
|
+
export { WEIGHT_PROFILES, allocateFundsByWeights, buildGrid, closeFilledInventoryAtPrice, countCancelableOrders, simulatePersistentGrid, sweepOneAma };
|
|
792
|
+
//# sourceMappingURL=backtest_ama_sweep.js.map
|