dexbot 1.6.4 → 1.6.6
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 +75 -3
- package/README.md +109 -40
- package/analysis/README.md +19 -17
- package/analysis/account_resolver.ts +1 -11
- package/analysis/analyze_dynamic_weight.ts +9 -3
- package/analysis/bot_usage/kibana_bot_queries.ts +1 -1
- package/analysis/chain_pool.ts +1 -1
- package/analysis/fills_source.ts +0 -6
- package/analysis/grid_correction_check.ts +20 -26
- package/analysis/resolve_source.ts +1 -1
- package/analysis/trade_profitability.ts +2 -5
- package/analysis/tradingview/analyze_tradingview.ts +5 -0
- package/analysis/tradingview/tradingview_uplot_chart_generator.ts +2 -2
- package/analysis/trend_detection/DYNAMIC_WEIGHT_RESEARCH.md +35 -2
- package/analysis/trend_detection/README.md +1 -3
- package/analysis/trend_detection/dynamic_weight_chart_generator.ts +82 -10
- package/analysis/trend_detection/package.json +1 -1
- package/claw/modules/dexbot_profiles.ts +4 -3
- package/claw/package.json +1 -1
- package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
- package/claw/runtimes/openclaw-plugin/package.json +1 -1
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/claw/tests/test_dexbot_profiles.ts +18 -1
- package/dist/analysis/account_resolver.d.ts +1 -4
- package/dist/analysis/account_resolver.d.ts.map +1 -1
- package/dist/analysis/account_resolver.js +1 -1
- package/dist/analysis/account_resolver.js.map +1 -1
- package/dist/analysis/analyze_dynamic_weight.js +9 -3
- package/dist/analysis/analyze_dynamic_weight.js.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +1 -2
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -1
- package/dist/analysis/chain_pool.d.ts +1 -3
- package/dist/analysis/chain_pool.d.ts.map +1 -1
- package/dist/analysis/chain_pool.js +1 -1
- package/dist/analysis/chain_pool.js.map +1 -1
- package/dist/analysis/fills_source.d.ts +1 -51
- package/dist/analysis/fills_source.d.ts.map +1 -1
- package/dist/analysis/fills_source.js +1 -1
- package/dist/analysis/fills_source.js.map +1 -1
- package/dist/analysis/grid_correction_check.d.ts +2 -3
- package/dist/analysis/grid_correction_check.d.ts.map +1 -1
- package/dist/analysis/grid_correction_check.js +18 -26
- package/dist/analysis/grid_correction_check.js.map +1 -1
- package/dist/analysis/resolve_source.d.ts +1 -1
- package/dist/analysis/resolve_source.d.ts.map +1 -1
- package/dist/analysis/trade_profitability.d.ts.map +1 -1
- package/dist/analysis/trade_profitability.js +2 -7
- package/dist/analysis/trade_profitability.js.map +1 -1
- package/dist/analysis/tradingview/analyze_tradingview.d.ts.map +1 -1
- package/dist/analysis/tradingview/analyze_tradingview.js +5 -0
- package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -1
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +2 -2
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts.map +1 -1
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js +82 -10
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js.map +1 -1
- package/dist/dexbot.d.ts.map +1 -1
- package/dist/dexbot.js +85 -140
- package/dist/dexbot.js.map +1 -1
- package/dist/market_adapter/core/asymmetric_bounds.d.ts +2 -2
- package/dist/market_adapter/core/asymmetric_bounds.d.ts.map +1 -1
- package/dist/market_adapter/core/asymmetric_bounds.js +15 -12
- package/dist/market_adapter/core/asymmetric_bounds.js.map +1 -1
- package/dist/market_adapter/core/kibana_candles.d.ts +1 -15
- package/dist/market_adapter/core/kibana_candles.d.ts.map +1 -1
- package/dist/market_adapter/core/kibana_candles.js +1 -1
- package/dist/market_adapter/core/kibana_candles.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts +1 -35
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.js +10 -54
- package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.d.ts +1 -8
- package/dist/market_adapter/inputs/kibana_feed_source.d.ts.map +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.js +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.js.map +1 -1
- package/dist/market_adapter/inputs/window_cache.d.ts +2 -63
- package/dist/market_adapter/inputs/window_cache.d.ts.map +1 -1
- package/dist/market_adapter/inputs/window_cache.js +22 -102
- package/dist/market_adapter/inputs/window_cache.js.map +1 -1
- package/dist/market_adapter/interval_utils.d.ts +1 -1
- package/dist/market_adapter/interval_utils.js +1 -1
- package/dist/modules/account_bots.d.ts +70 -6
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +353 -149
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/bitshares-native/crypto/ecc.browser.d.ts.map +1 -1
- package/dist/modules/bitshares-native/crypto/ecc.browser.js +0 -31
- package/dist/modules/bitshares-native/crypto/ecc.browser.js.map +1 -1
- package/dist/modules/bot_defaults.d.ts +98 -0
- package/dist/modules/bot_defaults.d.ts.map +1 -0
- package/dist/modules/bot_defaults.js +169 -0
- package/dist/modules/bot_defaults.js.map +1 -0
- package/dist/modules/bot_settings.d.ts.map +1 -1
- package/dist/modules/bot_settings.js +4 -1
- package/dist/modules/bot_settings.js.map +1 -1
- package/dist/modules/config.d.ts +1 -0
- package/dist/modules/config.d.ts.map +1 -1
- package/dist/modules/config.js +1 -0
- package/dist/modules/config.js.map +1 -1
- package/dist/modules/constants.d.ts +11 -2
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +40 -27
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/credit_runtime.d.ts +0 -2
- package/dist/modules/credit_runtime.d.ts.map +1 -1
- package/dist/modules/credit_runtime.js +1 -10
- package/dist/modules/credit_runtime.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +13 -10
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +0 -9
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +13 -0
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +13 -1
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +9 -4
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/launcher/adapter_requirement.js +1 -1
- package/dist/modules/launcher/launch_modes.d.ts +17 -1
- package/dist/modules/launcher/launch_modes.d.ts.map +1 -1
- package/dist/modules/launcher/launch_modes.js +18 -1
- package/dist/modules/launcher/launch_modes.js.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.js +8 -1
- package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
- package/dist/modules/market_adapter_whitelist.d.ts +40 -1
- package/dist/modules/market_adapter_whitelist.d.ts.map +1 -1
- package/dist/modules/market_adapter_whitelist.js +161 -11
- package/dist/modules/market_adapter_whitelist.js.map +1 -1
- package/dist/modules/order/accounting.d.ts +4 -18
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +4 -34
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/export.d.ts +9 -12
- package/dist/modules/order/export.d.ts.map +1 -1
- package/dist/modules/order/export.js +9 -12
- package/dist/modules/order/export.js.map +1 -1
- package/dist/modules/order/grid.d.ts +44 -40
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +66 -48
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile.js +111 -20
- package/dist/modules/order/grid_reconcile.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts +5 -3
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +18 -3
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/manager.d.ts +0 -8
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +3 -12
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +22 -7
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +103 -21
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts +5 -1
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +5 -2
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/withPoolRef.d.ts +12 -0
- package/dist/modules/order/utils/withPoolRef.d.ts.map +1 -1
- package/dist/modules/order/utils/withPoolRef.js +17 -0
- package/dist/modules/order/utils/withPoolRef.js.map +1 -1
- package/dist/modules/order/working_grid.d.ts +8 -13
- package/dist/modules/order/working_grid.d.ts.map +1 -1
- package/dist/modules/order/working_grid.js +8 -29
- package/dist/modules/order/working_grid.js.map +1 -1
- package/dist/modules/settings_merge.d.ts +10 -1
- package/dist/modules/settings_merge.d.ts.map +1 -1
- package/dist/modules/settings_merge.js +27 -17
- package/dist/modules/settings_merge.js.map +1 -1
- package/dist/modules/validate_profiles.d.ts.map +1 -1
- package/dist/modules/validate_profiles.js +4 -3
- package/dist/modules/validate_profiles.js.map +1 -1
- package/dist/scripts/chart_command.d.ts +48 -0
- package/dist/scripts/chart_command.d.ts.map +1 -0
- package/dist/scripts/chart_command.js +504 -0
- package/dist/scripts/chart_command.js.map +1 -0
- package/dist/scripts/dw.d.ts +3 -0
- package/dist/scripts/dw.d.ts.map +1 -0
- package/dist/scripts/dw.js +30 -0
- package/dist/scripts/dw.js.map +1 -0
- package/dist/scripts/sync-version.js +0 -22
- package/dist/scripts/sync-version.js.map +1 -1
- package/dist/scripts/tv.d.ts +1 -31
- package/dist/scripts/tv.d.ts.map +1 -1
- package/dist/scripts/tv.js +10 -463
- package/dist/scripts/tv.js.map +1 -1
- package/dist/scripts/update.js +99 -62
- package/dist/scripts/update.js.map +1 -1
- package/dist/scripts/update_dist_freshness.d.ts +58 -0
- package/dist/scripts/update_dist_freshness.d.ts.map +1 -0
- package/dist/scripts/update_dist_freshness.js +144 -0
- package/dist/scripts/update_dist_freshness.js.map +1 -0
- package/dist/unlock.js +1 -1
- package/dist/unlock.js.map +1 -1
- package/docs/BITSHARES_ONBOARDING.md +16 -10
- package/docs/COW_INVARIANTS.md +1 -0
- package/docs/DEXBOT_COMPARISON.md +11 -11
- package/docs/EVOLUTION.md +9 -7
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +4 -4
- package/docs/GRID_PRICE_INVARIANT.md +518 -0
- package/docs/GRID_RECALCULATION.md +20 -24
- package/docs/GRID_RECONCILE.md +62 -41
- package/docs/LOGGING.md +1 -0
- package/docs/ORDER_ENGINE_POST_1.0_RETROSPECTIVE.md +556 -0
- package/docs/README.md +18 -18
- package/docs/WORKFLOW.md +13 -8
- package/docs/architecture.md +42 -40
- package/docs/developer_guide.md +5 -8
- package/market_adapter/README.md +45 -65
- package/modules/README.md +3 -2
- package/package.json +7 -4
- package/scripts/README.md +18 -34
- package/scripts/reset-settings.sh +1 -1
- package/analysis/ama_fitting/package.json +0 -12
- package/analysis/analyze_derivatives.ts +0 -320
- package/analysis/derivative_chart_generator.ts +0 -874
- package/analysis/trend_detection/SIGNAL_DOCUMENTATION.md +0 -403
- package/analysis/trend_detection/derivative_analyzer.ts +0 -1059
- package/dist/analysis/analyze_derivatives.d.ts +0 -68
- package/dist/analysis/analyze_derivatives.d.ts.map +0 -1
- package/dist/analysis/analyze_derivatives.js +0 -287
- package/dist/analysis/analyze_derivatives.js.map +0 -1
- package/dist/analysis/derivative_chart_generator.d.ts +0 -12
- package/dist/analysis/derivative_chart_generator.d.ts.map +0 -1
- package/dist/analysis/derivative_chart_generator.js +0 -891
- package/dist/analysis/derivative_chart_generator.js.map +0 -1
- package/dist/analysis/trend_detection/derivative_analyzer.d.ts +0 -248
- package/dist/analysis/trend_detection/derivative_analyzer.d.ts.map +0 -1
- package/dist/analysis/trend_detection/derivative_analyzer.js +0 -910
- package/dist/analysis/trend_detection/derivative_analyzer.js.map +0 -1
- package/dist/scripts/generate_market_adapter_whitelist.d.ts +0 -15
- package/dist/scripts/generate_market_adapter_whitelist.d.ts.map +0 -1
- package/dist/scripts/generate_market_adapter_whitelist.js +0 -191
- package/dist/scripts/generate_market_adapter_whitelist.js.map +0 -1
|
@@ -49,12 +49,13 @@
|
|
|
49
49
|
* "active": true,
|
|
50
50
|
* "dryRun": false,
|
|
51
51
|
* "startPrice": "pool", // Price for order alignment: "pool", "book", or numeric
|
|
52
|
-
* "gridPrice":
|
|
53
|
-
* // "pool" / "book" = live pair price reference
|
|
52
|
+
* "gridPrice": "ama3", // Reference price for x-factor bounds (default "ama3"):
|
|
54
53
|
* // "ama"/"ama1".."ama4" = market adapter writes a center snapshot to
|
|
55
54
|
* // profiles/orders/<botKey>.dynamicgrid.json; grid reads the effective center on reset
|
|
56
|
-
* //
|
|
57
|
-
* //
|
|
55
|
+
* // "pool" / "book" = live pair price reference (legacy, editor no longer offers it)
|
|
56
|
+
* // <number> = fixed numeric reference (legacy)
|
|
57
|
+
* // null = use startPrice (legacy); also accepted: false, empty/blank,
|
|
58
|
+
* // and the "none"/"null"/"start"/"startprice"/"s"/"n"/"no"/"f"/"0" spellings
|
|
58
59
|
* "minPrice": "2x",
|
|
59
60
|
* "maxPrice": "2x",
|
|
60
61
|
* "incrementPercent": 0.5,
|
|
@@ -63,7 +64,8 @@
|
|
|
63
64
|
* "botFunds": { "sell": "100%", "buy": "100%" },
|
|
64
65
|
* "activeOrders": { "sell": 20, "buy": 20 },
|
|
65
66
|
* "reserveOrders": { "buy": 0, "sell": 0 }, // Edge reserves: extra live orders (buy: floor, sell: ceiling)
|
|
66
|
-
* "
|
|
67
|
+
* "poolRef": null, // Optional pinned pool ID for startPrice "pool"
|
|
68
|
+
* "debtPolicy": { ... }, // MPA/credit lending policy — docs/MPA_CREDIT_USAGE.md
|
|
67
69
|
* "min_BTS_value": 0, // Minimum BTS value threshold for operations
|
|
68
70
|
* }
|
|
69
71
|
* ]
|
|
@@ -87,11 +89,13 @@ import { path } from './path_api.js';
|
|
|
87
89
|
import { getStorage } from './storage/index.js';
|
|
88
90
|
import { ensureProfilesDirectory, readInput, sleep } from './order/utils/system.js';
|
|
89
91
|
import { setGlobalConsoleLevel, getGlobalConsoleLevel } from './order/logger.js';
|
|
90
|
-
import {
|
|
92
|
+
import { GRID_LIMITS, RANGE_QUALITY, MARKET_ADAPTER, NODE_MANAGEMENT, INCREMENT_BOUNDS, buildDefaultGeneralSettings } from './constants.js';
|
|
93
|
+
import { seedBotDraft, isUnsetGridPrice } from './bot_defaults.js';
|
|
91
94
|
import { PATHS } from './paths.js';
|
|
92
95
|
import { SETTINGS_FILE, readGeneralSettings, writeGeneralSettings } from './general_settings.js';
|
|
93
96
|
import { parseJsonWithComments } from './order/utils/system.js';
|
|
94
|
-
import { assertNoDuplicateBotKeys, loadSettingsFile } from './bot_settings.js';
|
|
97
|
+
import { assertNoDuplicateBotKeys, loadSettingsFile, normalizeBotEntry } from './bot_settings.js';
|
|
98
|
+
import { getWhitelistFlags, setWhitelistFlags, renameWhitelistEntry, removeWhitelistEntry, whitelistFile } from './market_adapter_whitelist.js';
|
|
95
99
|
import { BOT_LIVE_CONFIG_KEYS } from './runtime_settings.js';
|
|
96
100
|
import { mergeSettings } from './settings_merge.js';
|
|
97
101
|
import { getErrorMessage } from './utils/errors.js';
|
|
@@ -157,26 +161,9 @@ function saveBotsConfig(config, filePath) {
|
|
|
157
161
|
* @returns {Object} The loaded settings or default settings if the file doesn't exist.
|
|
158
162
|
*/
|
|
159
163
|
function loadGeneralSettings() {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
TIMING: { ...TIMING },
|
|
164
|
-
UPDATER: { ...UPDATER },
|
|
165
|
-
MARKET_ADAPTER: { ...MARKET_ADAPTER },
|
|
166
|
-
NODE_MANAGEMENT: { ...NODE_MANAGEMENT },
|
|
167
|
-
DEFAULT_CONFIG: { ...DEFAULT_CONFIG },
|
|
168
|
-
FILL_PROCESSING: { ...FILL_PROCESSING },
|
|
169
|
-
PIPELINE_TIMING: { ...PIPELINE_TIMING },
|
|
170
|
-
CREDENTIAL_PROMPTS: { ...CREDENTIAL_PROMPTS },
|
|
171
|
-
MAINTENANCE: { ...MAINTENANCE },
|
|
172
|
-
COW_PERFORMANCE: { ...COW_PERFORMANCE },
|
|
173
|
-
INCREMENT_BOUNDS: { ...INCREMENT_BOUNDS },
|
|
174
|
-
FEE_PARAMETERS: { ...FEE_PARAMETERS },
|
|
175
|
-
API_LIMITS: { ...API_LIMITS },
|
|
176
|
-
LOGGING_CONFIG: { ...LOGGING_CONFIG },
|
|
177
|
-
NATIVE_CLIENT: { ...NATIVE_CLIENT },
|
|
178
|
-
LAUNCHER: { ...LAUNCHER },
|
|
179
|
-
};
|
|
164
|
+
// Single canonical defaults document (modules/constants.ts) — same source
|
|
165
|
+
// the first-run generator and the local-overrides merge use.
|
|
166
|
+
const defaults = buildDefaultGeneralSettings();
|
|
180
167
|
const settings = readGeneralSettings({
|
|
181
168
|
fallback: null,
|
|
182
169
|
onError: (err) => {
|
|
@@ -190,6 +177,14 @@ function loadGeneralSettings() {
|
|
|
190
177
|
? configuredDeltaPercent
|
|
191
178
|
: MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT;
|
|
192
179
|
merged.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT = effectiveDeltaPercent;
|
|
180
|
+
// MARKET_ADAPTER validation for AMA_SLOPE_DELTA_THRESHOLD_PERCENT (the
|
|
181
|
+
// slope trigger factor: (value/100) x maxSlopePct). Same >0 guard so a
|
|
182
|
+
// hand-edited 0/negative/NaN can never disable the slope reset silently.
|
|
183
|
+
const configuredSlopeDeltaPercent = Number(merged.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT);
|
|
184
|
+
const effectiveSlopeDeltaPercent = Number.isFinite(configuredSlopeDeltaPercent) && configuredSlopeDeltaPercent > 0
|
|
185
|
+
? configuredSlopeDeltaPercent
|
|
186
|
+
: MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT;
|
|
187
|
+
merged.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT = effectiveSlopeDeltaPercent;
|
|
193
188
|
return merged;
|
|
194
189
|
}
|
|
195
190
|
/**
|
|
@@ -214,12 +209,32 @@ function listBots(bots) {
|
|
|
214
209
|
console.log(' (no bot entries defined yet)');
|
|
215
210
|
return;
|
|
216
211
|
}
|
|
217
|
-
bots.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
212
|
+
const rows = bots.map((bot, index) => ({
|
|
213
|
+
index: String(index + 1),
|
|
214
|
+
name: bot.name || `<unnamed-${index + 1}>`,
|
|
215
|
+
account: bot.preferredAccount || '?',
|
|
216
|
+
pair: `${bot.assetA || '?'}/${bot.assetB || '?'}`,
|
|
217
|
+
inactive: bot.active === false,
|
|
218
|
+
dryRun: !!bot.dryRun
|
|
219
|
+
}));
|
|
220
|
+
const indexWidth = Math.max(1, ...rows.map(r => r.index.length));
|
|
221
|
+
const nameWidth = Math.max('Name'.length, ...rows.map(r => r.name.length));
|
|
222
|
+
const accountWidth = Math.max('Account'.length, ...rows.map(r => r.account.length));
|
|
223
|
+
const pairWidth = Math.max('Pair'.length, ...rows.map(r => r.pair.length));
|
|
224
|
+
const header = [
|
|
225
|
+
'#'.padEnd(indexWidth),
|
|
226
|
+
'Name'.padEnd(nameWidth),
|
|
227
|
+
'Account'.padEnd(accountWidth),
|
|
228
|
+
'Pair'.padEnd(pairWidth)
|
|
229
|
+
].join(' ');
|
|
230
|
+
console.log(` ${COLORS.yellowBold}${header}${COLORS.reset}`);
|
|
231
|
+
for (const row of rows) {
|
|
232
|
+
const flags = `${row.inactive ? ` ${COLORS.red}[inactive]${COLORS.reset}` : ''}${row.dryRun ? ` ${COLORS.yellow}(dryRun)${COLORS.reset}` : ''}`;
|
|
233
|
+
console.log(` ${COLORS.gray}${row.index.padEnd(indexWidth)}${COLORS.reset} ` +
|
|
234
|
+
`${COLORS.green}${row.name.padEnd(nameWidth)}${COLORS.reset} ` +
|
|
235
|
+
`${COLORS.orange}${row.account.padEnd(accountWidth)}${COLORS.reset} ` +
|
|
236
|
+
`${COLORS.cyan}${row.pair.padEnd(pairWidth)}${COLORS.reset}${flags}`);
|
|
237
|
+
}
|
|
223
238
|
}
|
|
224
239
|
/**
|
|
225
240
|
* Prompts the user to select a bot from the list.
|
|
@@ -559,6 +574,13 @@ function isDynamicPriceSource(value) {
|
|
|
559
574
|
const lower = value.trim().toLowerCase();
|
|
560
575
|
return lower === 'pool' || lower === 'book' || /^ama(?:[1-4])?$/.test(lower);
|
|
561
576
|
}
|
|
577
|
+
/**
|
|
578
|
+
* AMA grid-price values: the four explicit presets ama1..ama4 plus bare
|
|
579
|
+
* "ama" (the pair's default preset). Bare "ama" is a valid green value but is
|
|
580
|
+
* deliberately not listed in the prompt label. These are the ONLY values that
|
|
581
|
+
* color green — pool/book/numeric/none stay accepted, just displayed red.
|
|
582
|
+
*/
|
|
583
|
+
const AMA_GRID_PRICE_PATTERN = /^ama(?:[1-4])?$/;
|
|
562
584
|
/**
|
|
563
585
|
* Colors a start-price value for display: green when it is a dynamic source
|
|
564
586
|
* ("pool"/"book"), red when it is a fixed numeric price (no live rescaling).
|
|
@@ -573,26 +595,67 @@ function colorStartPriceValue(value) {
|
|
|
573
595
|
return `${COLORS.red}${text}${COLORS.reset}`;
|
|
574
596
|
}
|
|
575
597
|
/**
|
|
576
|
-
* Colors a grid-price value for display:
|
|
577
|
-
* ("
|
|
578
|
-
*
|
|
579
|
-
* start
|
|
598
|
+
* Colors a grid-price value for display: GREEN only for AMA values
|
|
599
|
+
* ("ama"/"ama1".."ama4"), RED for everything else — pool/book references,
|
|
600
|
+
* numeric values and any unset grid price (null, false, blank/whitespace, and
|
|
601
|
+
* the "none"/"s"/"no"/"f"/"start" aliases) which delegates to startPrice and is
|
|
602
|
+
* therefore labeled "startPrice", always in red: the delegation itself is the
|
|
603
|
+
* thing the editor wants replaced by an AMA preset.
|
|
580
604
|
* @param {*} value - The grid price value to color.
|
|
581
|
-
* @param {*} [startPrice] - The bot's current startPrice for null resolution.
|
|
582
605
|
* @returns {string} ANSI-colored value string.
|
|
583
606
|
*/
|
|
584
|
-
function colorGridPriceValue(value
|
|
585
|
-
if (value
|
|
586
|
-
|
|
587
|
-
? `${COLORS.green}startPrice${COLORS.reset}`
|
|
588
|
-
: `${COLORS.red}startPrice${COLORS.reset}`;
|
|
589
|
-
return label;
|
|
607
|
+
function colorGridPriceValue(value) {
|
|
608
|
+
if (isUnsetGridPrice(value)) {
|
|
609
|
+
return `${COLORS.red}startPrice${COLORS.reset}`;
|
|
590
610
|
}
|
|
591
611
|
const text = String(value);
|
|
592
|
-
if (
|
|
612
|
+
if (AMA_GRID_PRICE_PATTERN.test(text.trim().toLowerCase()))
|
|
593
613
|
return `${COLORS.green}${text}${COLORS.reset}`;
|
|
594
614
|
return `${COLORS.red}${text}${COLORS.reset}`;
|
|
595
615
|
}
|
|
616
|
+
/**
|
|
617
|
+
* True when a startPrice value selects the pair's automatically-resolved pool
|
|
618
|
+
* (the "default" pool source, as opposed to a hand-pinned poolRef).
|
|
619
|
+
* @param {*} value - A startPrice value (or any value to test).
|
|
620
|
+
* @returns {boolean}
|
|
621
|
+
*/
|
|
622
|
+
function isPoolStartPrice(value) {
|
|
623
|
+
return String(value ?? '').trim().toLowerCase() === 'pool';
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* True when the draft's startPrice resolves to the pair's default pool and no
|
|
627
|
+
* poolRef is pinned, so the runtime auto-selects it for order alignment.
|
|
628
|
+
* Deliberately NOT keyed on gridPrice === "pool": anchoring the grid reference
|
|
629
|
+
* on the live market price is discouraged and stays red in the GridPrice
|
|
630
|
+
* field, so the Pool label must not read as a green healthy default for that
|
|
631
|
+
* case.
|
|
632
|
+
* @param {*} data - The bot draft.
|
|
633
|
+
* @returns {boolean}
|
|
634
|
+
*/
|
|
635
|
+
function startPriceUsesDefaultPool(data) {
|
|
636
|
+
return isPoolStartPrice(data?.startPrice);
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Colors the pool label for the editor summary. An unset poolRef does NOT
|
|
640
|
+
* mean "no pool": when startPrice is "pool" the runtime auto-selects the
|
|
641
|
+
* pair's default pool, so it reads green `default`. Otherwise the pin is
|
|
642
|
+
* inert, shown as grey `none` so it does not compete with live values (a
|
|
643
|
+
* gridPrice of "pool"/"book" is discouraged and stays red in its own field).
|
|
644
|
+
* Display only — the stored poolRef is never changed here.
|
|
645
|
+
* @param {*} data - The bot draft.
|
|
646
|
+
* @returns {string} ANSI-colored pinned pool ID, `default` (green), or `none` (grey).
|
|
647
|
+
*/
|
|
648
|
+
function formatPoolRefLabel(data) {
|
|
649
|
+
// A pin only applies to startPrice "pool"; show it as ignored otherwise
|
|
650
|
+
// so the summary never displays a value the editor no longer asks for.
|
|
651
|
+
if (data?.poolRef && !startPriceUsesDefaultPool(data))
|
|
652
|
+
return `${COLORS.gray}ignored${COLORS.reset}`;
|
|
653
|
+
if (data?.poolRef)
|
|
654
|
+
return String(data.poolRef);
|
|
655
|
+
if (startPriceUsesDefaultPool(data))
|
|
656
|
+
return `${COLORS.green}default${COLORS.reset}`;
|
|
657
|
+
return `${COLORS.gray}none${COLORS.reset}`;
|
|
658
|
+
}
|
|
596
659
|
/**
|
|
597
660
|
* Colors a boolean flag value for display: green when the flag is in its
|
|
598
661
|
* healthy state, red when it is not.
|
|
@@ -600,13 +663,15 @@ function colorGridPriceValue(value, startPrice) {
|
|
|
600
663
|
* @param {boolean} greenWhenTrue - True when `true` is the healthy state
|
|
601
664
|
* (e.g. Active), false when `false` is
|
|
602
665
|
* healthy (e.g. DryRun off).
|
|
666
|
+
* @param {string} [offColor] - Color for the unhealthy value (default red);
|
|
667
|
+
* pass a warn color for flags that are optional.
|
|
603
668
|
* @returns {string} ANSI-colored "true"/"false" string.
|
|
604
669
|
*/
|
|
605
|
-
function colorBooleanFlag(value, greenWhenTrue) {
|
|
670
|
+
function colorBooleanFlag(value, greenWhenTrue, offColor = COLORS.red) {
|
|
606
671
|
const isTrue = !!value;
|
|
607
672
|
const healthy = greenWhenTrue ? isTrue : !isTrue;
|
|
608
673
|
const text = String(isTrue);
|
|
609
|
-
return healthy ? `${COLORS.green}${text}${COLORS.reset}` : `${
|
|
674
|
+
return healthy ? `${COLORS.green}${text}${COLORS.reset}` : `${offColor}${text}${COLORS.reset}`;
|
|
610
675
|
}
|
|
611
676
|
/**
|
|
612
677
|
* Converts a cron string to a readable format (days delta and time).
|
|
@@ -892,20 +957,46 @@ async function askNumberOrPercentage(promptText, defaultValue) {
|
|
|
892
957
|
}
|
|
893
958
|
return parsed;
|
|
894
959
|
}
|
|
960
|
+
/**
|
|
961
|
+
* Parses a raw boolean answer.
|
|
962
|
+
* Accepted: y/yes/true/1/t → true, n/no/false/0/f → false (case-insensitive);
|
|
963
|
+
* empty input keeps the current/default value; anything else is rejected so
|
|
964
|
+
* the caller can re-prompt (the old `startsWith('y')` rule silently parsed
|
|
965
|
+
* "true" as false).
|
|
966
|
+
* @param {string} raw - Raw prompt input.
|
|
967
|
+
* @param {boolean} [defaultValue] - Value returned for empty input.
|
|
968
|
+
* @returns {{ ok: boolean, value?: boolean }} ok=false for unrecognized input.
|
|
969
|
+
*/
|
|
970
|
+
function parseBooleanInput(raw, defaultValue) {
|
|
971
|
+
const value = String(raw ?? '').trim().toLowerCase();
|
|
972
|
+
if (!value)
|
|
973
|
+
return { ok: true, value: !!defaultValue };
|
|
974
|
+
if (value === 'y' || value === 'yes' || value === 'true' || value === '1' || value === 't')
|
|
975
|
+
return { ok: true, value: true };
|
|
976
|
+
if (value === 'n' || value === 'no' || value === 'false' || value === '0' || value === 'f')
|
|
977
|
+
return { ok: true, value: false };
|
|
978
|
+
return { ok: false };
|
|
979
|
+
}
|
|
895
980
|
/**
|
|
896
981
|
* Prompts the user for a boolean value (Y/n).
|
|
982
|
+
* Enter keeps the current/default value; unrecognized input re-prompts with
|
|
983
|
+
* a hint instead of silently coercing. See parseBooleanInput for the exact
|
|
984
|
+
* accepted spellings.
|
|
897
985
|
* @param {string} promptText - The prompt text to display.
|
|
898
986
|
* @param {boolean} [defaultValue] - The default value to use if input is empty.
|
|
899
987
|
* @returns {Promise<boolean|string>} The boolean value or '\x1b' if ESC.
|
|
900
988
|
*/
|
|
901
989
|
async function askBoolean(promptText, defaultValue) {
|
|
902
990
|
const label = defaultValue ? 'Y/n' : 'y/N';
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
991
|
+
for (;;) {
|
|
992
|
+
const raw = (await readInput(`${promptText} (${label}): `)).trim().toLowerCase();
|
|
993
|
+
if (raw === '\x1b')
|
|
994
|
+
return '\x1b';
|
|
995
|
+
const parsed = parseBooleanInput(raw, !!defaultValue);
|
|
996
|
+
if (parsed.ok)
|
|
997
|
+
return parsed.value;
|
|
998
|
+
console.log('Please enter y/yes/true or n/no/false (Enter keeps the current value).');
|
|
999
|
+
}
|
|
909
1000
|
}
|
|
910
1001
|
/**
|
|
911
1002
|
* Prompts the user for the start price (numeric or "pool"/"book").
|
|
@@ -940,24 +1031,46 @@ async function askStartPrice(promptText, defaultValue) {
|
|
|
940
1031
|
console.log('Please enter "pool", "book", or a numeric value.');
|
|
941
1032
|
}
|
|
942
1033
|
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Spellings that clear a `poolRef` pin, leaving the runtime to auto-select
|
|
1036
|
+
* the pair's default pool when the price mode asks for one. `default`/`pool`/
|
|
1037
|
+
* `auto` are accepted as aliases because the 3) Price summary labels the
|
|
1038
|
+
* unpinned pool state `default`, so typing what the summary shows must not be
|
|
1039
|
+
* rejected.
|
|
1040
|
+
*/
|
|
1041
|
+
const POOL_REF_CLEAR_INPUTS = new Set(['none', 'clear', 'off', 'no', 'default', 'pool', 'auto']);
|
|
1042
|
+
/**
|
|
1043
|
+
* True when raw poolRef input is a clear/auto alias rather than a pool ID.
|
|
1044
|
+
* @param {*} value - Raw or normalized user input.
|
|
1045
|
+
* @returns {boolean}
|
|
1046
|
+
*/
|
|
1047
|
+
function isPoolRefClearInput(value) {
|
|
1048
|
+
return POOL_REF_CLEAR_INPUTS.has(String(value ?? '').trim().toLowerCase());
|
|
1049
|
+
}
|
|
943
1050
|
/**
|
|
944
1051
|
* Prompts the user for an optional pool ID to pin price derivation.
|
|
945
|
-
* Enter a pool ID (e.g. 48 or 1.19.48) to set/change the pin, or
|
|
946
|
-
* "none"
|
|
1052
|
+
* Enter a pool ID (e.g. 48 or 1.19.48) to set/change the pin, or one of the
|
|
1053
|
+
* clear aliases ("none", "clear", or "default"/"pool"/"auto") to remove it.
|
|
1054
|
+
* Blank input keeps the current value. When no pin is set, the bracketed
|
|
1055
|
+
* default shows what the runtime will actually use: `default` when startPrice
|
|
1056
|
+
* is "pool" (the pair's auto-selected pool), otherwise `none` (inert) — this
|
|
1057
|
+
* mirrors the 3) Price summary label instead of always reading `none`.
|
|
947
1058
|
* @param {string} promptText - The prompt text to display.
|
|
948
1059
|
* @param {string|null|undefined} [currentValue] - The current poolRef value.
|
|
1060
|
+
* @param {boolean} [usesDefaultPool=false] - True when startPrice is "pool",
|
|
1061
|
+
* so an unpinned value renders as `default` rather than `none`.
|
|
949
1062
|
* @returns {Promise<string|null|symbol>} Pool ID, null (cleared), or '\x1b' on ESC.
|
|
950
1063
|
*/
|
|
951
|
-
async function askPoolRef(promptText, currentValue) {
|
|
1064
|
+
async function askPoolRef(promptText, currentValue, usesDefaultPool = false) {
|
|
952
1065
|
while (true) {
|
|
953
|
-
const
|
|
954
|
-
const
|
|
1066
|
+
const fallbackLabel = usesDefaultPool ? 'default' : 'none';
|
|
1067
|
+
const suffix = currentValue ? ` [${currentValue}]` : ` [${fallbackLabel}]`;
|
|
1068
|
+
const raw = (await readInput(`${promptText}${suffix}: `)).trim();
|
|
955
1069
|
if (raw === '\x1b')
|
|
956
1070
|
return '\x1b';
|
|
957
1071
|
if (!raw)
|
|
958
1072
|
return currentValue || null;
|
|
959
|
-
|
|
960
|
-
if (lower === 'none' || lower === 'clear' || lower === 'off' || lower === 'no') {
|
|
1073
|
+
if (isPoolRefClearInput(raw)) {
|
|
961
1074
|
return null;
|
|
962
1075
|
}
|
|
963
1076
|
const normalized = raw.startsWith('1.19.') ? raw : `1.19.${raw}`;
|
|
@@ -969,38 +1082,49 @@ async function askPoolRef(promptText, currentValue) {
|
|
|
969
1082
|
}
|
|
970
1083
|
}
|
|
971
1084
|
/**
|
|
972
|
-
* Prompts the user for the grid price mode
|
|
1085
|
+
* Prompts the user for the grid price mode.
|
|
1086
|
+
* The label lists ama1..ama4; bare "ama" is accepted as well (green) but not
|
|
1087
|
+
* listed. The live colorizer echoes AMA input green and every other value
|
|
1088
|
+
* (pool/book/number/none) red — red only flags "not an AMA value", it does not
|
|
1089
|
+
* reject: pool, book, numeric references, none/null (delegate to startPrice)
|
|
1090
|
+
* and bare "ama" all stay accepted, exactly as before. Only input that is not
|
|
1091
|
+
* a value at all is rejected, with a red error line.
|
|
973
1092
|
* @param {string} promptText - The prompt text to display.
|
|
974
|
-
* @param {string} [defaultValue] - The
|
|
975
|
-
* @
|
|
976
|
-
*
|
|
977
|
-
* @returns {Promise<string>} The grid price mode or '\x1b' if ESC.
|
|
1093
|
+
* @param {string} [defaultValue] - The current value, offered as the Enter default.
|
|
1094
|
+
* @returns {Promise<any>} The normalized grid-price value, the unchanged
|
|
1095
|
+
* current value on Enter, or '\x1b' if ESC.
|
|
978
1096
|
*/
|
|
979
|
-
async function askGridPriceMode(promptText, defaultValue
|
|
1097
|
+
async function askGridPriceMode(promptText, defaultValue) {
|
|
980
1098
|
while (true) {
|
|
981
|
-
const coloredDefault = defaultValue
|
|
982
|
-
|
|
983
|
-
: colorGridPriceValue(
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1099
|
+
const coloredDefault = colorGridPriceValue(defaultValue ?? null);
|
|
1100
|
+
const raw = await readInput(`${promptText} [${coloredDefault}]: `, {
|
|
1101
|
+
colorize: (input) => colorGridPriceValue(input),
|
|
1102
|
+
// Keep whitespace so a deactivating space can be told apart from a
|
|
1103
|
+
// bare Enter that means "keep current".
|
|
1104
|
+
trimInput: false
|
|
1105
|
+
});
|
|
987
1106
|
if (raw === '\x1b')
|
|
988
1107
|
return '\x1b';
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1108
|
+
// A bare Enter keeps the current value (an already-unset value is
|
|
1109
|
+
// normalized to null so it is stored as delegation). Whitespace-only
|
|
1110
|
+
// input is NOT a bare Enter: the live echo already previews it as red
|
|
1111
|
+
// startPrice, so it must overwrite with null — otherwise the preview
|
|
1112
|
+
// would promise startPrice while the old value silently survived.
|
|
1113
|
+
if (raw === '')
|
|
1114
|
+
return isUnsetGridPrice(defaultValue) ? null : (defaultValue === undefined ? null : defaultValue);
|
|
1115
|
+
const lower = raw.trim().toLowerCase();
|
|
1116
|
+
if (isUnsetGridPrice(lower))
|
|
993
1117
|
return null;
|
|
994
1118
|
if (lower === 'pool')
|
|
995
1119
|
return lower;
|
|
996
1120
|
if (lower === 'book')
|
|
997
1121
|
return 'book';
|
|
998
|
-
if (
|
|
1122
|
+
if (AMA_GRID_PRICE_PATTERN.test(lower))
|
|
999
1123
|
return lower;
|
|
1000
1124
|
const num = Number(raw);
|
|
1001
1125
|
if (Number.isFinite(num) && num > 0)
|
|
1002
1126
|
return num;
|
|
1003
|
-
console.log(
|
|
1127
|
+
console.log(`${COLORS.red}Please enter: ama1, ama2, ama3 or ama4 — also allowed: pool, book, ama, a positive number, or none (s/start, n/no/false/0/f).${COLORS.reset}`);
|
|
1004
1128
|
}
|
|
1005
1129
|
}
|
|
1006
1130
|
/**
|
|
@@ -1010,36 +1134,10 @@ async function askGridPriceMode(promptText, defaultValue, startPrice) {
|
|
|
1010
1134
|
* @returns {Object} A normalized bot draft.
|
|
1011
1135
|
*/
|
|
1012
1136
|
function normalizeBotDraft(base = {}) {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
data.botFunds = { ...DEFAULT_CONFIG.botFunds };
|
|
1018
|
-
if (!data.activeOrders)
|
|
1019
|
-
data.activeOrders = { ...DEFAULT_CONFIG.activeOrders };
|
|
1020
|
-
if (typeof data.reserveOrders === 'number')
|
|
1021
|
-
data.reserveOrders = { buy: Math.max(0, Math.floor(data.reserveOrders)), sell: 0 };
|
|
1022
|
-
if (data.reserveOrders === undefined || data.reserveOrders === null || typeof data.reserveOrders !== 'object' || Array.isArray(data.reserveOrders))
|
|
1023
|
-
data.reserveOrders = { ...DEFAULT_CONFIG.reserveOrders };
|
|
1024
|
-
if (data.active === undefined)
|
|
1025
|
-
data.active = DEFAULT_CONFIG.active;
|
|
1026
|
-
if (data.dryRun === undefined)
|
|
1027
|
-
data.dryRun = DEFAULT_CONFIG.dryRun;
|
|
1028
|
-
if (data.minPrice === undefined)
|
|
1029
|
-
data.minPrice = DEFAULT_CONFIG.minPrice;
|
|
1030
|
-
if (data.maxPrice === undefined)
|
|
1031
|
-
data.maxPrice = DEFAULT_CONFIG.maxPrice;
|
|
1032
|
-
if (data.incrementPercent === undefined)
|
|
1033
|
-
data.incrementPercent = DEFAULT_CONFIG.incrementPercent;
|
|
1034
|
-
if (data.targetSpreadPercent === undefined)
|
|
1035
|
-
data.targetSpreadPercent = DEFAULT_CONFIG.targetSpreadPercent;
|
|
1036
|
-
if (data.startPrice === undefined)
|
|
1037
|
-
data.startPrice = data.startPrice || DEFAULT_CONFIG.startPrice || 'pool';
|
|
1038
|
-
if (data.gridPrice === undefined)
|
|
1039
|
-
data.gridPrice = null;
|
|
1040
|
-
delete data.gridPriceOffsetPct;
|
|
1041
|
-
delete data.gridPriceOffsetClampToBounds;
|
|
1042
|
-
return data;
|
|
1137
|
+
// Seeding rules live in modules/bot_defaults.ts (single defaults source).
|
|
1138
|
+
// Return type stays `any`: the editor prompt-flow treats drafts as dynamic
|
|
1139
|
+
// bags and its inference depends on it.
|
|
1140
|
+
return seedBotDraft(base);
|
|
1043
1141
|
}
|
|
1044
1142
|
/**
|
|
1045
1143
|
* Resolve data.preferredAccount to a chain account ID and stamp data.accountId
|
|
@@ -1162,28 +1260,59 @@ async function ensureBotAccountId(data, timeoutMs = 15000, quiet = false, force
|
|
|
1162
1260
|
/**
|
|
1163
1261
|
* Interactive menu to edit bot data.
|
|
1164
1262
|
* @param {Object} [base={}] - The initial bot data to edit.
|
|
1165
|
-
* @
|
|
1263
|
+
* @param {number} [index=0] - Position the edited bot will occupy in the
|
|
1264
|
+
* bots array (used to derive the same botKey the runtime uses for unnamed
|
|
1265
|
+
* fallback entries).
|
|
1266
|
+
* @param {number} [baseIndex=index] - Position the base entry currently
|
|
1267
|
+
* occupies (differs from `index` for copies, whose source sits elsewhere).
|
|
1268
|
+
* @returns {Promise<{ data: any, commitAdapter: (() => void)|null }|null>}
|
|
1269
|
+
* The edited draft plus a commit hook for staged `6) Adapter` flags — the
|
|
1270
|
+
* caller MUST run `commitAdapter()` only after its bots.json save succeeded
|
|
1271
|
+
* (so a failed save never leaves the whitelist out of sync), or null when
|
|
1272
|
+
* cancelled.
|
|
1166
1273
|
*/
|
|
1167
|
-
async function promptBotData(base = {}) {
|
|
1274
|
+
async function promptBotData(base = {}, index = 0, baseIndex = index) {
|
|
1168
1275
|
const data = normalizeBotDraft(base);
|
|
1276
|
+
// Market-adapter flags (6) Adapter) live in
|
|
1277
|
+
// profiles/market_adapter_whitelist.json keyed by botKey — the same file
|
|
1278
|
+
// the market adapter reads. Stage edits here and expose them through the
|
|
1279
|
+
// commit hook so Cancel (or a failed bots.json save) discards them
|
|
1280
|
+
// together with the rest of the draft.
|
|
1281
|
+
const baseEntry = base && typeof base === 'object' && Object.keys(base).length > 0 ? base : null;
|
|
1282
|
+
const baseBotKey = baseEntry ? String(normalizeBotEntry(baseEntry, baseIndex).botKey || '') : '';
|
|
1283
|
+
let adapterStaged = null;
|
|
1284
|
+
const isAmaGridPriceDraft = () => /^ama(?:[1-4])?$/.test(String(data.gridPrice ?? '').trim().toLowerCase());
|
|
1285
|
+
const adapterFlags = () => adapterStaged
|
|
1286
|
+
|| getWhitelistFlags(baseBotKey || String(normalizeBotEntry(data, index).botKey || ''));
|
|
1169
1287
|
let finished = false;
|
|
1170
1288
|
let cancelled = false;
|
|
1171
1289
|
let showMenu = true;
|
|
1172
1290
|
while (!finished) {
|
|
1173
1291
|
if (showMenu) {
|
|
1174
1292
|
console.log(`\n${COLORS.bold}--- Bot Editor: ` + (data.name || 'New Bot') + ` ---${COLORS.reset}`);
|
|
1175
|
-
console.log(`${COLORS.yellowBold}1) Pair:${COLORS.reset}
|
|
1176
|
-
console.log(`${COLORS.yellowBold}2) Identity:${COLORS.reset}
|
|
1177
|
-
console.log(`${COLORS.yellowBold}3) Price:${COLORS.reset}
|
|
1178
|
-
console.log(`${COLORS.yellowBold}4) Grid:${COLORS.reset}
|
|
1179
|
-
console.log(`${COLORS.yellowBold}5)
|
|
1293
|
+
console.log(`${COLORS.yellowBold}1) Pair:${COLORS.reset} ${COLORS.cyan}${data.assetA || '?'} / ${data.assetB || '?'}${COLORS.reset}`);
|
|
1294
|
+
console.log(`${COLORS.yellowBold}2) Identity:${COLORS.reset} ${COLORS.orange}Name:${COLORS.reset} ${data.name || '?'}, ${COLORS.orange}Account:${COLORS.reset} ${data.preferredAccount || '?'} | ${COLORS.orange}Active:${COLORS.reset} ${colorBooleanFlag(data.active, true)}, ${COLORS.orange}DryRun:${COLORS.reset} ${colorBooleanFlag(data.dryRun, false)}`);
|
|
1295
|
+
console.log(`${COLORS.yellowBold}3) Price:${COLORS.reset} ${COLORS.orange}Range:${COLORS.reset} [${colorPriceRangeValue(data.minPrice)} - ${colorPriceRangeValue(data.maxPrice)}] | ${COLORS.orange}Start:${COLORS.reset} ${colorStartPriceValue(data.startPrice)}, ${COLORS.orange}Pool:${COLORS.reset} ${formatPoolRefLabel(data)} | ${COLORS.orange}GridPrice:${COLORS.reset} ${colorGridPriceValue(data.gridPrice)}`);
|
|
1296
|
+
console.log(`${COLORS.yellowBold}4) Grid:${COLORS.reset} ${COLORS.orange}Weights:${COLORS.reset} (S:${data.weightDistribution.sell}, B:${data.weightDistribution.buy}) | ${COLORS.orange}Increment:${COLORS.reset} ${data.incrementPercent}%, ${COLORS.orange}Spread:${COLORS.reset} ${data.targetSpreadPercent}%`);
|
|
1297
|
+
console.log(`${COLORS.yellowBold}5) Inventory:${COLORS.reset} ${COLORS.orange}Sell:${COLORS.reset} ${colorPercentageInput(data.botFunds.sell)}, ${COLORS.orange}Buy:${COLORS.reset} ${colorPercentageInput(data.botFunds.buy)} | ${COLORS.orange}MarketOrder:${COLORS.reset} (S:${data.activeOrders.sell}, B:${data.activeOrders.buy}) | ${COLORS.orange}EdgeOrder:${COLORS.reset} (S:${data.reserveOrders?.sell ?? 0}, B:${data.reserveOrders?.buy ?? 0})`);
|
|
1298
|
+
{
|
|
1299
|
+
const flags = adapterFlags();
|
|
1300
|
+
const inert = !isAmaGridPriceDraft() && (flags.ama || flags.dynamicWeight || flags.asymmetricBounds);
|
|
1301
|
+
const hint = inert ? ` ${COLORS.red}(needs gridPrice=ama)${COLORS.reset}` : '';
|
|
1302
|
+
// Adapter flags read as a health state, not a neutral toggle:
|
|
1303
|
+
// green = on (colorBooleanFlag's greenWhenTrue form, same
|
|
1304
|
+
// helper section 2 uses for Active). Price is the gate for the
|
|
1305
|
+
// other two, so off stays red; Weight/Range are optional
|
|
1306
|
+
// riders, so off is a bright-yellow warning rather than an error.
|
|
1307
|
+
console.log(`${COLORS.yellowBold}6) Adapter:${COLORS.reset} ${COLORS.orange}Price:${COLORS.reset} ${colorBooleanFlag(flags.ama, true)}, ${COLORS.orange}Weight:${COLORS.reset} ${colorBooleanFlag(flags.dynamicWeight, true, COLORS.yellowBold)}, ${COLORS.orange}Range:${COLORS.reset} ${colorBooleanFlag(flags.asymmetricBounds, true, COLORS.yellowBold)}${hint}`);
|
|
1308
|
+
}
|
|
1180
1309
|
console.log('--------------------------------------------------');
|
|
1181
1310
|
console.log(`${COLORS.greenBold}S) Save & Exit${COLORS.reset}`);
|
|
1182
1311
|
console.log(`${COLORS.white}C) Cancel (Discard changes)${COLORS.reset}`);
|
|
1183
1312
|
showMenu = false;
|
|
1184
1313
|
}
|
|
1185
1314
|
const choice = (await readInput('Select section to edit or action: ', {
|
|
1186
|
-
validate: (input) => ['1', '2', '3', '4', '5', 's', 'c'].includes(input.toLowerCase())
|
|
1315
|
+
validate: (input) => ['1', '2', '3', '4', '5', '6', 's', 'c'].includes(input.toLowerCase())
|
|
1187
1316
|
})).trim().toLowerCase();
|
|
1188
1317
|
if (choice === '\x1b') {
|
|
1189
1318
|
finished = true;
|
|
@@ -1206,7 +1335,7 @@ async function promptBotData(base = {}) {
|
|
|
1206
1335
|
const name = await askRequiredString('Bot name', data.name);
|
|
1207
1336
|
if (name === '\x1b')
|
|
1208
1337
|
break;
|
|
1209
|
-
let prefAcc = await askRequiredString('
|
|
1338
|
+
let prefAcc = await askRequiredString('Blockchain account', data.preferredAccount);
|
|
1210
1339
|
if (prefAcc === '\x1b')
|
|
1211
1340
|
break;
|
|
1212
1341
|
// Verify the account exists on the blockchain and stamp its ID.
|
|
@@ -1223,7 +1352,7 @@ async function promptBotData(base = {}) {
|
|
|
1223
1352
|
? `nodes unreachable (lookup timed out). Check your connection and try again, or press Esc to cancel.`
|
|
1224
1353
|
: `account '${draft.preferredAccount}' not found on the blockchain. Check the name and try again, or press Esc to cancel.`;
|
|
1225
1354
|
console.log(`${COLORS.red}Error: ${why}${COLORS.reset}`);
|
|
1226
|
-
const retry = await askRequiredString('
|
|
1355
|
+
const retry = await askRequiredString('Blockchain account', String(draft.preferredAccount ?? ''));
|
|
1227
1356
|
if (retry === '\x1b') {
|
|
1228
1357
|
prefAcc = retry;
|
|
1229
1358
|
break;
|
|
@@ -1254,13 +1383,19 @@ async function promptBotData(base = {}) {
|
|
|
1254
1383
|
const maxP = await askMaxPrice('maxPrice', data.maxPrice, minP);
|
|
1255
1384
|
if (maxP === '\x1b')
|
|
1256
1385
|
break;
|
|
1257
|
-
const startP = await askStartPrice('startPrice (pool, book or
|
|
1386
|
+
const startP = await askStartPrice('startPrice (pool, book or price)', data.startPrice);
|
|
1258
1387
|
if (startP === '\x1b')
|
|
1259
1388
|
break;
|
|
1260
|
-
|
|
1389
|
+
// poolRef only feeds startPrice "pool" (README: ignored for
|
|
1390
|
+
// book/fixed price), so don't ask when startPrice isn't pool —
|
|
1391
|
+
// an existing pin is kept dormant, not cleared, so switching
|
|
1392
|
+
// back to pool restores it.
|
|
1393
|
+
const poolR = isPoolStartPrice(startP)
|
|
1394
|
+
? await askPoolRef('poolRef (pinned pool ID for price source)', data.poolRef, true)
|
|
1395
|
+
: (data.poolRef || null);
|
|
1261
1396
|
if (poolR === '\x1b')
|
|
1262
1397
|
break;
|
|
1263
|
-
const gp = await askGridPriceMode('gridPrice (
|
|
1398
|
+
const gp = await askGridPriceMode('gridPrice (ama1/ama2/ama3/ama4)', data.gridPrice);
|
|
1264
1399
|
if (gp === '\x1b')
|
|
1265
1400
|
break;
|
|
1266
1401
|
data.minPrice = minP;
|
|
@@ -1305,10 +1440,10 @@ async function promptBotData(base = {}) {
|
|
|
1305
1440
|
const oBuy = await askIntegerInRange('activeOrders buy count', data.activeOrders.buy, 1, 100);
|
|
1306
1441
|
if (oBuy === '\x1b')
|
|
1307
1442
|
break;
|
|
1308
|
-
const rBuy = await askIntegerInRange('reserveOrders buy floor count (0 disables)', data.reserveOrders?.buy ?? 0, 0,
|
|
1443
|
+
const rBuy = await askIntegerInRange('reserveOrders buy floor count (0 disables)', data.reserveOrders?.buy ?? 0, 0, 100);
|
|
1309
1444
|
if (rBuy === '\x1b')
|
|
1310
1445
|
break;
|
|
1311
|
-
const rSell = await askIntegerInRange('reserveOrders sell ceiling count (0 disables)', data.reserveOrders?.sell ?? 0, 0,
|
|
1446
|
+
const rSell = await askIntegerInRange('reserveOrders sell ceiling count (0 disables)', data.reserveOrders?.sell ?? 0, 0, 100);
|
|
1312
1447
|
if (rSell === '\x1b')
|
|
1313
1448
|
break;
|
|
1314
1449
|
data.botFunds.sell = fSell;
|
|
@@ -1318,6 +1453,29 @@ async function promptBotData(base = {}) {
|
|
|
1318
1453
|
data.reserveOrders = { buy: rBuy, sell: rSell };
|
|
1319
1454
|
showMenu = true;
|
|
1320
1455
|
break;
|
|
1456
|
+
case '6': {
|
|
1457
|
+
// Per-bot market-adapter flags (Price = AMA pricing, Weight =
|
|
1458
|
+
// dynamic weights, Range = asymmetric range scaling).
|
|
1459
|
+
const flags = adapterFlags();
|
|
1460
|
+
if (!isAmaGridPriceDraft()) {
|
|
1461
|
+
console.log(`${COLORS.gray}Note: these flags only take effect when gridPrice is ama/ama1..ama4 (set in 3) Price).${COLORS.reset}`);
|
|
1462
|
+
}
|
|
1463
|
+
const price = await askBoolean('AMA pricing (Price)', flags.ama);
|
|
1464
|
+
if (price === '\x1b')
|
|
1465
|
+
break;
|
|
1466
|
+
const weight = await askBoolean('Dynamic weights (Weight)', flags.dynamicWeight);
|
|
1467
|
+
if (weight === '\x1b')
|
|
1468
|
+
break;
|
|
1469
|
+
const range = await askBoolean('Range scaling (Range)', flags.asymmetricBounds);
|
|
1470
|
+
if (range === '\x1b')
|
|
1471
|
+
break;
|
|
1472
|
+
adapterStaged = { ama: price, dynamicWeight: weight, asymmetricBounds: range };
|
|
1473
|
+
if (!price && (weight || range)) {
|
|
1474
|
+
console.log(`${COLORS.yellow}Note: Weight/Range only take effect while Price (AMA) is enabled.${COLORS.reset}`);
|
|
1475
|
+
}
|
|
1476
|
+
showMenu = true;
|
|
1477
|
+
break;
|
|
1478
|
+
}
|
|
1321
1479
|
case 's':
|
|
1322
1480
|
// Final basic validation before saving
|
|
1323
1481
|
if (!data.name || !data.assetA || !data.assetB || !data.preferredAccount) {
|
|
@@ -1350,11 +1508,40 @@ async function promptBotData(base = {}) {
|
|
|
1350
1508
|
}
|
|
1351
1509
|
if (cancelled)
|
|
1352
1510
|
return null;
|
|
1511
|
+
// Staged 6) Adapter flags become a commit hook instead of an immediate
|
|
1512
|
+
// write: the caller runs it only after its bots.json save succeeded, so
|
|
1513
|
+
// the whitelist can never end up updated by a save that was rejected
|
|
1514
|
+
// (duplicate bot keys, write error). Writes are skipped when the staged
|
|
1515
|
+
// flags already match what is stored — an unchanged save never creates a
|
|
1516
|
+
// placeholder entry.
|
|
1517
|
+
const stagedFlags = adapterStaged;
|
|
1518
|
+
const commitAdapter = stagedFlags ? () => {
|
|
1519
|
+
const newKey = String(normalizeBotEntry(data, index).botKey || '');
|
|
1520
|
+
const needRename = !!(baseBotKey && newKey && baseBotKey !== newKey);
|
|
1521
|
+
const current = getWhitelistFlags(baseBotKey || newKey);
|
|
1522
|
+
const unchanged = stagedFlags.ama === current.ama
|
|
1523
|
+
&& stagedFlags.dynamicWeight === current.dynamicWeight
|
|
1524
|
+
&& stagedFlags.asymmetricBounds === current.asymmetricBounds;
|
|
1525
|
+
if (needRename && !renameWhitelistEntry(baseBotKey, newKey)) {
|
|
1526
|
+
console.log(`${COLORS.yellow}Adapter flags not saved — see the warning above.${COLORS.reset}`);
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
// Unchanged flags need no write — a successful rename above already
|
|
1530
|
+
// migrated the entry under the new key.
|
|
1531
|
+
if (unchanged)
|
|
1532
|
+
return;
|
|
1533
|
+
if (!setWhitelistFlags(newKey, stagedFlags)) {
|
|
1534
|
+
console.log(`${COLORS.yellow}Warning: adapter flags not saved — could not write ${whitelistFile()}.${COLORS.reset}`);
|
|
1535
|
+
}
|
|
1536
|
+
else {
|
|
1537
|
+
console.log(`${COLORS.green}Adapter flags saved for '${newKey}'. Price/Weight are picked up on the adapter's next cycle; Range scaling applies after 'dexbot reset ${data.name}'.${COLORS.reset}`);
|
|
1538
|
+
}
|
|
1539
|
+
} : null;
|
|
1353
1540
|
// Return the final data structure, preserving ALL fields from the normalized
|
|
1354
1541
|
// draft (deep-copy of base + defaults, minus stripped runtime-managed fields).
|
|
1355
1542
|
// A fixed whitelist here would silently drop custom overrides (logging, timing,
|
|
1356
1543
|
// feeParams, gridLimits, poolRef, etc.) when the caller replaces the bot entry.
|
|
1357
|
-
return { ...data };
|
|
1544
|
+
return { data: { ...data }, commitAdapter };
|
|
1358
1545
|
}
|
|
1359
1546
|
/**
|
|
1360
1547
|
* Interactive menu to edit general settings.
|
|
@@ -1365,13 +1552,13 @@ async function promptGeneralSettings() {
|
|
|
1365
1552
|
let finished = false;
|
|
1366
1553
|
while (!finished) {
|
|
1367
1554
|
console.log(`${COLORS.bold}--- General Settings (Global) ---${COLORS.reset}`);
|
|
1368
|
-
console.log(`${COLORS.yellowBold}1) Grid
|
|
1369
|
-
console.log(`${COLORS.yellowBold}2) Order Recovery:${COLORS.reset} ${COLORS.orange}Dust Threshold:${COLORS.reset} ${settings.GRID_LIMITS.PARTIAL_DUST_THRESHOLD_PERCENTAGE}%`);
|
|
1370
|
-
const nodeCount = (settings.NODES.list || []).length;
|
|
1555
|
+
console.log(`${COLORS.yellowBold}1) Grid Drift:${COLORS.reset} ${COLORS.orange}Funds:${COLORS.reset} ${settings.GRID_LIMITS.GRID_REGENERATION_PERCENTAGE}%, ${COLORS.orange}RMS:${COLORS.reset} ${settings.GRID_LIMITS.GRID_COMPARISON.RMS_PERCENTAGE}% | ${COLORS.orange}AMA Δ:${COLORS.reset} ${settings.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT}%, ${COLORS.orange}AMA-Slope Δ:${COLORS.reset} ${settings.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT}%`);
|
|
1371
1556
|
const hcIntervalMin = ((settings.NODES.healthCheck?.intervalMs || NODE_MANAGEMENT.HEALTH_CHECK_INTERVAL_MS) / 60000).toFixed(0);
|
|
1557
|
+
console.log(`${COLORS.yellowBold}2) Order Maint.:${COLORS.reset} ${COLORS.orange}Dust Threshold:${COLORS.reset} ${settings.GRID_LIMITS.PARTIAL_DUST_THRESHOLD_PERCENTAGE}% | ${COLORS.orange}Health Check:${COLORS.reset} ${hcIntervalMin}min`);
|
|
1558
|
+
const nodeCount = (settings.NODES.list || []).length;
|
|
1372
1559
|
const prefNodeDisplay = settings.NODES.selection?.preferredNode || 'none';
|
|
1373
|
-
console.log(`${COLORS.yellowBold}3) Node Config:${COLORS.reset}
|
|
1374
|
-
console.log(`${COLORS.yellowBold}4) Log
|
|
1560
|
+
console.log(`${COLORS.yellowBold}3) Node Config:${COLORS.reset} ${COLORS.orange}Nodes:${COLORS.reset} ${nodeCount}, ${COLORS.orange}Preferred Node:${COLORS.reset} ${prefNodeDisplay}`);
|
|
1561
|
+
console.log(`${COLORS.yellowBold}4) Log Level:${COLORS.reset} ${COLORS.orange}${settings.LOG_LEVEL}${COLORS.reset} (debug, info, warn, error)`);
|
|
1375
1562
|
const updaterStatus = settings.UPDATER.ACTIVE ? `${COLORS.green}ON${COLORS.reset}` : `${COLORS.red}OFF${COLORS.reset}`;
|
|
1376
1563
|
const currentSched = parseCronToDelta(settings.UPDATER.SCHEDULE || "0 0 * * *");
|
|
1377
1564
|
console.log(`${COLORS.yellowBold}5) Updater:${COLORS.reset} [${updaterStatus}] ${COLORS.orange}Branch:${COLORS.reset} ${settings.UPDATER.BRANCH}, ${COLORS.orange}Interval:${COLORS.reset} ${currentSched.days}d, ${COLORS.orange}Time:${COLORS.reset} ${currentSched.time}`);
|
|
@@ -1387,24 +1574,34 @@ async function promptGeneralSettings() {
|
|
|
1387
1574
|
}
|
|
1388
1575
|
switch (choice) {
|
|
1389
1576
|
case '1':
|
|
1390
|
-
const gRegen = await askNumberWithBounds('Grid
|
|
1577
|
+
const gRegen = await askNumberWithBounds('Grid Funds Regeneration %', settings.GRID_LIMITS.GRID_REGENERATION_PERCENTAGE, 0.1, 50);
|
|
1391
1578
|
if (gRegen === '\x1b')
|
|
1392
1579
|
break;
|
|
1393
1580
|
const rms = await askNumberWithBounds('RMS Divergence Threshold %', settings.GRID_LIMITS.GRID_COMPARISON.RMS_PERCENTAGE, 1, 100);
|
|
1394
1581
|
if (rms === '\x1b')
|
|
1395
1582
|
break;
|
|
1396
|
-
const amaDelta = await askNumberWithBounds('AMA
|
|
1583
|
+
const amaDelta = await askNumberWithBounds('AMA Δ', settings.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT, 0.1, 50.0);
|
|
1397
1584
|
if (amaDelta === '\x1b')
|
|
1398
1585
|
break;
|
|
1586
|
+
const amaSlopeDelta = await askNumberWithBounds('AMA-Slope Δ', settings.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT, 0.1, 100.0);
|
|
1587
|
+
if (amaSlopeDelta === '\x1b')
|
|
1588
|
+
break;
|
|
1399
1589
|
settings.GRID_LIMITS.GRID_REGENERATION_PERCENTAGE = gRegen;
|
|
1400
1590
|
settings.GRID_LIMITS.GRID_COMPARISON.RMS_PERCENTAGE = rms;
|
|
1401
1591
|
settings.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT = amaDelta;
|
|
1592
|
+
settings.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT = amaSlopeDelta;
|
|
1402
1593
|
break;
|
|
1403
1594
|
case '2':
|
|
1404
1595
|
const dust = await askNumberWithBounds('Partial Dust Threshold %', settings.GRID_LIMITS.PARTIAL_DUST_THRESHOLD_PERCENTAGE, 0.1, 50);
|
|
1405
1596
|
if (dust === '\x1b')
|
|
1406
1597
|
break;
|
|
1407
1598
|
settings.GRID_LIMITS.PARTIAL_DUST_THRESHOLD_PERCENTAGE = dust;
|
|
1599
|
+
const hcInterval = await askIntegerInRange('Health Check Interval (min)', (settings.NODES.healthCheck?.intervalMs || NODE_MANAGEMENT.HEALTH_CHECK_INTERVAL_MS) / 60000, 1, 43200);
|
|
1600
|
+
if (hcInterval === '\x1b')
|
|
1601
|
+
break;
|
|
1602
|
+
if (!settings.NODES.healthCheck)
|
|
1603
|
+
settings.NODES.healthCheck = {};
|
|
1604
|
+
settings.NODES.healthCheck.intervalMs = hcInterval * 60000;
|
|
1408
1605
|
break;
|
|
1409
1606
|
case '3':
|
|
1410
1607
|
settings.NODES.enabled = true;
|
|
@@ -1455,12 +1652,6 @@ async function promptGeneralSettings() {
|
|
|
1455
1652
|
if (editorCancelled)
|
|
1456
1653
|
break;
|
|
1457
1654
|
}
|
|
1458
|
-
const hcInterval = await askIntegerInRange('Health Check Interval (min)', (settings.NODES.healthCheck?.intervalMs || NODE_MANAGEMENT.HEALTH_CHECK_INTERVAL_MS) / 60000, 1, 43200);
|
|
1459
|
-
if (hcInterval === '\x1b')
|
|
1460
|
-
break;
|
|
1461
|
-
if (!settings.NODES.healthCheck)
|
|
1462
|
-
settings.NODES.healthCheck = {};
|
|
1463
|
-
settings.NODES.healthCheck.intervalMs = hcInterval * 60000;
|
|
1464
1655
|
const prefNode = await askString('Preferred Node URL (leave empty for automatic selection)', settings.NODES.selection?.preferredNode || '');
|
|
1465
1656
|
if (prefNode === '\x1b')
|
|
1466
1657
|
break;
|
|
@@ -1528,12 +1719,13 @@ async function main() {
|
|
|
1528
1719
|
case '1': {
|
|
1529
1720
|
while (true) {
|
|
1530
1721
|
try {
|
|
1531
|
-
const
|
|
1532
|
-
if (!
|
|
1722
|
+
const result = await promptBotData({}, config.bots.length);
|
|
1723
|
+
if (!result)
|
|
1533
1724
|
break;
|
|
1534
|
-
config.bots.push(
|
|
1725
|
+
config.bots.push(result.data);
|
|
1535
1726
|
saveBotsConfig(config, filePath);
|
|
1536
|
-
|
|
1727
|
+
result.commitAdapter?.();
|
|
1728
|
+
console.log(`\nAdded bot '${result.data.name}' to ${path.basename(filePath)}.`);
|
|
1537
1729
|
}
|
|
1538
1730
|
catch (err) {
|
|
1539
1731
|
console.log(`\n❌ Invalid input: ${getErrorMessage(err)}\n`);
|
|
@@ -1548,10 +1740,12 @@ async function main() {
|
|
|
1548
1740
|
if (idx === null || idx === '\x1b')
|
|
1549
1741
|
break;
|
|
1550
1742
|
try {
|
|
1551
|
-
const
|
|
1552
|
-
if (
|
|
1553
|
-
config.bots[idx] =
|
|
1743
|
+
const result = await promptBotData(config.bots[idx], idx);
|
|
1744
|
+
if (result) {
|
|
1745
|
+
config.bots[idx] = result.data;
|
|
1554
1746
|
saveBotsConfig(config, filePath);
|
|
1747
|
+
result.commitAdapter?.();
|
|
1748
|
+
const entry = result.data;
|
|
1555
1749
|
console.log(`saved settings '${entry.name}' in ${path.basename(filePath)}.\n`);
|
|
1556
1750
|
console.log(`Live pickup (~1min, no reload needed): ${BOT_LIVE_CONFIG_KEYS.join(' / ')}.`);
|
|
1557
1751
|
console.log(`Grid geometry needs 'dexbot reset ${entry.name}' (or 'dexbot reload' for everything at once); market/account changes need 'dexbot reload'.\n`);
|
|
@@ -1573,8 +1767,17 @@ async function main() {
|
|
|
1573
1767
|
if (confirm === '\x1b')
|
|
1574
1768
|
break;
|
|
1575
1769
|
if (confirm) {
|
|
1770
|
+
// Derive the whitelist key before splicing (createBotKey
|
|
1771
|
+
// uses the index only for unnamed fallback entries).
|
|
1772
|
+
const removedKey = String(normalizeBotEntry(config.bots[idx], idx).botKey || '');
|
|
1576
1773
|
const removed = config.bots.splice(idx, 1)[0];
|
|
1577
1774
|
saveBotsConfig(config, filePath);
|
|
1775
|
+
// Only after the bots.json save succeeded, drop the
|
|
1776
|
+
// matching whitelist entry so a deleted bot leaves no
|
|
1777
|
+
// stale flags behind (name reuse would inherit them).
|
|
1778
|
+
if (!removeWhitelistEntry(removedKey)) {
|
|
1779
|
+
console.log(`${COLORS.yellow}Warning: could not remove whitelist entry '${removedKey}' — check ${whitelistFile()}.${COLORS.reset}`);
|
|
1780
|
+
}
|
|
1578
1781
|
console.log(`Removed bot '${removed.name || placeholderName}' from ${path.basename(filePath)}.\n`);
|
|
1579
1782
|
}
|
|
1580
1783
|
else {
|
|
@@ -1589,11 +1792,12 @@ async function main() {
|
|
|
1589
1792
|
if (idx === null || idx === '\x1b')
|
|
1590
1793
|
break;
|
|
1591
1794
|
try {
|
|
1592
|
-
const
|
|
1593
|
-
if (
|
|
1594
|
-
config.bots.splice(idx + 1, 0,
|
|
1795
|
+
const result = await promptBotData(config.bots[idx], idx + 1, idx);
|
|
1796
|
+
if (result) {
|
|
1797
|
+
config.bots.splice(idx + 1, 0, result.data);
|
|
1595
1798
|
saveBotsConfig(config, filePath);
|
|
1596
|
-
|
|
1799
|
+
result.commitAdapter?.();
|
|
1800
|
+
console.log(`Copied bot '${result.data.name}' into ${path.basename(filePath)}.\n`);
|
|
1597
1801
|
}
|
|
1598
1802
|
}
|
|
1599
1803
|
catch (err) {
|
|
@@ -1617,5 +1821,5 @@ async function main() {
|
|
|
1617
1821
|
}
|
|
1618
1822
|
console.log('Botmanager closed!');
|
|
1619
1823
|
}
|
|
1620
|
-
export { main, normalizeBotDraft, ensureBotAccountId, parseJsonWithComments };
|
|
1824
|
+
export { main, normalizeBotDraft, ensureBotAccountId, parseJsonWithComments, parseBooleanInput, colorGridPriceValue, formatPoolRefLabel, isPoolStartPrice, isPoolRefClearInput, loadGeneralSettings, saveGeneralSettings };
|
|
1621
1825
|
//# sourceMappingURL=account_bots.js.map
|