dexbot 1.6.4 → 1.6.5
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 +40 -0
- package/README.md +12 -12
- package/analysis/README.md +8 -1
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/analyze_dynamic_weight.ts +9 -3
- 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/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/analyze_dynamic_weight.js +9 -3
- package/dist/analysis/analyze_dynamic_weight.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 +27 -68
- 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/interval_utils.d.ts +1 -1
- package/dist/market_adapter/interval_utils.js +1 -1
- package/dist/modules/account_bots.d.ts +55 -5
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +258 -117
- package/dist/modules/account_bots.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/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/grid.d.ts +9 -0
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +23 -5
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/manager.js +3 -2
- package/dist/modules/order/manager.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/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/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 +46 -0
- package/dist/scripts/update_dist_freshness.d.ts.map +1 -0
- package/dist/scripts/update_dist_freshness.js +115 -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 +10 -7
- package/docs/DEXBOT_COMPARISON.md +3 -3
- package/docs/EVOLUTION.md +8 -7
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +4 -4
- package/docs/GRID_RECALCULATION.md +20 -24
- package/docs/GRID_RECONCILE.md +2 -2
- package/docs/LOGGING.md +1 -0
- package/docs/README.md +16 -16
- package/docs/WORKFLOW.md +1 -2
- package/docs/architecture.md +2 -2
- package/docs/developer_guide.md +1 -0
- package/market_adapter/README.md +36 -60
- package/modules/README.md +2 -1
- package/package.json +1 -2
- package/scripts/README.md +18 -25
- package/scripts/reset-settings.sh +1 -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,
|
|
@@ -87,11 +88,13 @@ import { path } from './path_api.js';
|
|
|
87
88
|
import { getStorage } from './storage/index.js';
|
|
88
89
|
import { ensureProfilesDirectory, readInput, sleep } from './order/utils/system.js';
|
|
89
90
|
import { setGlobalConsoleLevel, getGlobalConsoleLevel } from './order/logger.js';
|
|
90
|
-
import {
|
|
91
|
+
import { GRID_LIMITS, RANGE_QUALITY, MARKET_ADAPTER, NODE_MANAGEMENT, INCREMENT_BOUNDS, buildDefaultGeneralSettings } from './constants.js';
|
|
92
|
+
import { seedBotDraft, isUnsetGridPrice } from './bot_defaults.js';
|
|
91
93
|
import { PATHS } from './paths.js';
|
|
92
94
|
import { SETTINGS_FILE, readGeneralSettings, writeGeneralSettings } from './general_settings.js';
|
|
93
95
|
import { parseJsonWithComments } from './order/utils/system.js';
|
|
94
|
-
import { assertNoDuplicateBotKeys, loadSettingsFile } from './bot_settings.js';
|
|
96
|
+
import { assertNoDuplicateBotKeys, loadSettingsFile, normalizeBotEntry } from './bot_settings.js';
|
|
97
|
+
import { getWhitelistFlags, setWhitelistFlags, renameWhitelistEntry, removeWhitelistEntry, whitelistFile } from './market_adapter_whitelist.js';
|
|
95
98
|
import { BOT_LIVE_CONFIG_KEYS } from './runtime_settings.js';
|
|
96
99
|
import { mergeSettings } from './settings_merge.js';
|
|
97
100
|
import { getErrorMessage } from './utils/errors.js';
|
|
@@ -157,26 +160,9 @@ function saveBotsConfig(config, filePath) {
|
|
|
157
160
|
* @returns {Object} The loaded settings or default settings if the file doesn't exist.
|
|
158
161
|
*/
|
|
159
162
|
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
|
-
};
|
|
163
|
+
// Single canonical defaults document (modules/constants.ts) — same source
|
|
164
|
+
// the first-run generator and the local-overrides merge use.
|
|
165
|
+
const defaults = buildDefaultGeneralSettings();
|
|
180
166
|
const settings = readGeneralSettings({
|
|
181
167
|
fallback: null,
|
|
182
168
|
onError: (err) => {
|
|
@@ -190,6 +176,14 @@ function loadGeneralSettings() {
|
|
|
190
176
|
? configuredDeltaPercent
|
|
191
177
|
: MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT;
|
|
192
178
|
merged.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT = effectiveDeltaPercent;
|
|
179
|
+
// MARKET_ADAPTER validation for AMA_SLOPE_DELTA_THRESHOLD_PERCENT (the
|
|
180
|
+
// slope trigger factor: (value/100) x maxSlopePct). Same >0 guard so a
|
|
181
|
+
// hand-edited 0/negative/NaN can never disable the slope reset silently.
|
|
182
|
+
const configuredSlopeDeltaPercent = Number(merged.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT);
|
|
183
|
+
const effectiveSlopeDeltaPercent = Number.isFinite(configuredSlopeDeltaPercent) && configuredSlopeDeltaPercent > 0
|
|
184
|
+
? configuredSlopeDeltaPercent
|
|
185
|
+
: MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT;
|
|
186
|
+
merged.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT = effectiveSlopeDeltaPercent;
|
|
193
187
|
return merged;
|
|
194
188
|
}
|
|
195
189
|
/**
|
|
@@ -559,6 +553,13 @@ function isDynamicPriceSource(value) {
|
|
|
559
553
|
const lower = value.trim().toLowerCase();
|
|
560
554
|
return lower === 'pool' || lower === 'book' || /^ama(?:[1-4])?$/.test(lower);
|
|
561
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* AMA grid-price values: the four explicit presets ama1..ama4 plus bare
|
|
558
|
+
* "ama" (the pair's default preset). Bare "ama" is a valid green value but is
|
|
559
|
+
* deliberately not listed in the prompt label. These are the ONLY values that
|
|
560
|
+
* color green — pool/book/numeric/none stay accepted, just displayed red.
|
|
561
|
+
*/
|
|
562
|
+
const AMA_GRID_PRICE_PATTERN = /^ama(?:[1-4])?$/;
|
|
562
563
|
/**
|
|
563
564
|
* Colors a start-price value for display: green when it is a dynamic source
|
|
564
565
|
* ("pool"/"book"), red when it is a fixed numeric price (no live rescaling).
|
|
@@ -573,26 +574,53 @@ function colorStartPriceValue(value) {
|
|
|
573
574
|
return `${COLORS.red}${text}${COLORS.reset}`;
|
|
574
575
|
}
|
|
575
576
|
/**
|
|
576
|
-
* Colors a grid-price value for display:
|
|
577
|
-
* ("
|
|
578
|
-
*
|
|
579
|
-
* start
|
|
577
|
+
* Colors a grid-price value for display: GREEN only for AMA values
|
|
578
|
+
* ("ama"/"ama1".."ama4"), RED for everything else — pool/book references,
|
|
579
|
+
* numeric values and any unset grid price (null, false, blank/whitespace, and
|
|
580
|
+
* the "none"/"s"/"no"/"f"/"start" aliases) which delegates to startPrice and is
|
|
581
|
+
* therefore labeled "startPrice", always in red: the delegation itself is the
|
|
582
|
+
* thing the editor wants replaced by an AMA preset.
|
|
580
583
|
* @param {*} value - The grid price value to color.
|
|
581
|
-
* @param {*} [startPrice] - The bot's current startPrice for null resolution.
|
|
582
584
|
* @returns {string} ANSI-colored value string.
|
|
583
585
|
*/
|
|
584
|
-
function colorGridPriceValue(value
|
|
585
|
-
if (value
|
|
586
|
-
|
|
587
|
-
? `${COLORS.green}startPrice${COLORS.reset}`
|
|
588
|
-
: `${COLORS.red}startPrice${COLORS.reset}`;
|
|
589
|
-
return label;
|
|
586
|
+
function colorGridPriceValue(value) {
|
|
587
|
+
if (isUnsetGridPrice(value)) {
|
|
588
|
+
return `${COLORS.red}startPrice${COLORS.reset}`;
|
|
590
589
|
}
|
|
591
590
|
const text = String(value);
|
|
592
|
-
if (
|
|
591
|
+
if (AMA_GRID_PRICE_PATTERN.test(text.trim().toLowerCase()))
|
|
593
592
|
return `${COLORS.green}${text}${COLORS.reset}`;
|
|
594
593
|
return `${COLORS.red}${text}${COLORS.reset}`;
|
|
595
594
|
}
|
|
595
|
+
/**
|
|
596
|
+
* True when startPrice === "pool" and no poolRef is pinned, so the runtime
|
|
597
|
+
* auto-selects the pair's default pool for order alignment. Deliberately NOT
|
|
598
|
+
* keyed on gridPrice === "pool": anchoring the grid reference on the live
|
|
599
|
+
* market price is discouraged and stays red in the GridPrice field, so the
|
|
600
|
+
* Pool label must not read as a green healthy default for that case.
|
|
601
|
+
* @param {*} data - The bot draft.
|
|
602
|
+
* @returns {boolean}
|
|
603
|
+
*/
|
|
604
|
+
function startPriceUsesDefaultPool(data) {
|
|
605
|
+
return String(data?.startPrice ?? '').trim().toLowerCase() === 'pool';
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Colors the pool label for the editor summary. An unset poolRef does NOT
|
|
609
|
+
* mean "no pool": when startPrice is "pool" the runtime auto-selects the
|
|
610
|
+
* pair's default pool, so it reads green `default`. Otherwise the pin is
|
|
611
|
+
* inert, shown as grey `none` so it does not compete with live values (a
|
|
612
|
+
* gridPrice of "pool"/"book" is discouraged and stays red in its own field).
|
|
613
|
+
* Display only — the stored poolRef is never changed here.
|
|
614
|
+
* @param {*} data - The bot draft.
|
|
615
|
+
* @returns {string} ANSI-colored pinned pool ID, `default` (green), or `none` (grey).
|
|
616
|
+
*/
|
|
617
|
+
function formatPoolRefLabel(data) {
|
|
618
|
+
if (data?.poolRef)
|
|
619
|
+
return String(data.poolRef);
|
|
620
|
+
if (startPriceUsesDefaultPool(data))
|
|
621
|
+
return `${COLORS.green}default${COLORS.reset}`;
|
|
622
|
+
return `${COLORS.gray}none${COLORS.reset}`;
|
|
623
|
+
}
|
|
596
624
|
/**
|
|
597
625
|
* Colors a boolean flag value for display: green when the flag is in its
|
|
598
626
|
* healthy state, red when it is not.
|
|
@@ -600,13 +628,15 @@ function colorGridPriceValue(value, startPrice) {
|
|
|
600
628
|
* @param {boolean} greenWhenTrue - True when `true` is the healthy state
|
|
601
629
|
* (e.g. Active), false when `false` is
|
|
602
630
|
* healthy (e.g. DryRun off).
|
|
631
|
+
* @param {string} [offColor] - Color for the unhealthy value (default red);
|
|
632
|
+
* pass a warn color for flags that are optional.
|
|
603
633
|
* @returns {string} ANSI-colored "true"/"false" string.
|
|
604
634
|
*/
|
|
605
|
-
function colorBooleanFlag(value, greenWhenTrue) {
|
|
635
|
+
function colorBooleanFlag(value, greenWhenTrue, offColor = COLORS.red) {
|
|
606
636
|
const isTrue = !!value;
|
|
607
637
|
const healthy = greenWhenTrue ? isTrue : !isTrue;
|
|
608
638
|
const text = String(isTrue);
|
|
609
|
-
return healthy ? `${COLORS.green}${text}${COLORS.reset}` : `${
|
|
639
|
+
return healthy ? `${COLORS.green}${text}${COLORS.reset}` : `${offColor}${text}${COLORS.reset}`;
|
|
610
640
|
}
|
|
611
641
|
/**
|
|
612
642
|
* Converts a cron string to a readable format (days delta and time).
|
|
@@ -892,20 +922,46 @@ async function askNumberOrPercentage(promptText, defaultValue) {
|
|
|
892
922
|
}
|
|
893
923
|
return parsed;
|
|
894
924
|
}
|
|
925
|
+
/**
|
|
926
|
+
* Parses a raw boolean answer.
|
|
927
|
+
* Accepted: y/yes/true/1/t → true, n/no/false/0/f → false (case-insensitive);
|
|
928
|
+
* empty input keeps the current/default value; anything else is rejected so
|
|
929
|
+
* the caller can re-prompt (the old `startsWith('y')` rule silently parsed
|
|
930
|
+
* "true" as false).
|
|
931
|
+
* @param {string} raw - Raw prompt input.
|
|
932
|
+
* @param {boolean} [defaultValue] - Value returned for empty input.
|
|
933
|
+
* @returns {{ ok: boolean, value?: boolean }} ok=false for unrecognized input.
|
|
934
|
+
*/
|
|
935
|
+
function parseBooleanInput(raw, defaultValue) {
|
|
936
|
+
const value = String(raw ?? '').trim().toLowerCase();
|
|
937
|
+
if (!value)
|
|
938
|
+
return { ok: true, value: !!defaultValue };
|
|
939
|
+
if (value === 'y' || value === 'yes' || value === 'true' || value === '1' || value === 't')
|
|
940
|
+
return { ok: true, value: true };
|
|
941
|
+
if (value === 'n' || value === 'no' || value === 'false' || value === '0' || value === 'f')
|
|
942
|
+
return { ok: true, value: false };
|
|
943
|
+
return { ok: false };
|
|
944
|
+
}
|
|
895
945
|
/**
|
|
896
946
|
* Prompts the user for a boolean value (Y/n).
|
|
947
|
+
* Enter keeps the current/default value; unrecognized input re-prompts with
|
|
948
|
+
* a hint instead of silently coercing. See parseBooleanInput for the exact
|
|
949
|
+
* accepted spellings.
|
|
897
950
|
* @param {string} promptText - The prompt text to display.
|
|
898
951
|
* @param {boolean} [defaultValue] - The default value to use if input is empty.
|
|
899
952
|
* @returns {Promise<boolean|string>} The boolean value or '\x1b' if ESC.
|
|
900
953
|
*/
|
|
901
954
|
async function askBoolean(promptText, defaultValue) {
|
|
902
955
|
const label = defaultValue ? 'Y/n' : 'y/N';
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
956
|
+
for (;;) {
|
|
957
|
+
const raw = (await readInput(`${promptText} (${label}): `)).trim().toLowerCase();
|
|
958
|
+
if (raw === '\x1b')
|
|
959
|
+
return '\x1b';
|
|
960
|
+
const parsed = parseBooleanInput(raw, !!defaultValue);
|
|
961
|
+
if (parsed.ok)
|
|
962
|
+
return parsed.value;
|
|
963
|
+
console.log('Please enter y/yes/true or n/no/false (Enter keeps the current value).');
|
|
964
|
+
}
|
|
909
965
|
}
|
|
910
966
|
/**
|
|
911
967
|
* Prompts the user for the start price (numeric or "pool"/"book").
|
|
@@ -969,38 +1025,49 @@ async function askPoolRef(promptText, currentValue) {
|
|
|
969
1025
|
}
|
|
970
1026
|
}
|
|
971
1027
|
/**
|
|
972
|
-
* Prompts the user for the grid price mode
|
|
1028
|
+
* Prompts the user for the grid price mode.
|
|
1029
|
+
* The label lists ama1..ama4; bare "ama" is accepted as well (green) but not
|
|
1030
|
+
* listed. The live colorizer echoes AMA input green and every other value
|
|
1031
|
+
* (pool/book/number/none) red — red only flags "not an AMA value", it does not
|
|
1032
|
+
* reject: pool, book, numeric references, none/null (delegate to startPrice)
|
|
1033
|
+
* and bare "ama" all stay accepted, exactly as before. Only input that is not
|
|
1034
|
+
* a value at all is rejected, with a red error line.
|
|
973
1035
|
* @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.
|
|
1036
|
+
* @param {string} [defaultValue] - The current value, offered as the Enter default.
|
|
1037
|
+
* @returns {Promise<any>} The normalized grid-price value, the unchanged
|
|
1038
|
+
* current value on Enter, or '\x1b' if ESC.
|
|
978
1039
|
*/
|
|
979
|
-
async function askGridPriceMode(promptText, defaultValue
|
|
1040
|
+
async function askGridPriceMode(promptText, defaultValue) {
|
|
980
1041
|
while (true) {
|
|
981
|
-
const coloredDefault = defaultValue
|
|
982
|
-
|
|
983
|
-
: colorGridPriceValue(
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
1042
|
+
const coloredDefault = colorGridPriceValue(defaultValue ?? null);
|
|
1043
|
+
const raw = await readInput(`${promptText} [${coloredDefault}]: `, {
|
|
1044
|
+
colorize: (input) => colorGridPriceValue(input),
|
|
1045
|
+
// Keep whitespace so a deactivating space can be told apart from a
|
|
1046
|
+
// bare Enter that means "keep current".
|
|
1047
|
+
trimInput: false
|
|
1048
|
+
});
|
|
987
1049
|
if (raw === '\x1b')
|
|
988
1050
|
return '\x1b';
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1051
|
+
// A bare Enter keeps the current value (an already-unset value is
|
|
1052
|
+
// normalized to null so it is stored as delegation). Whitespace-only
|
|
1053
|
+
// input is NOT a bare Enter: the live echo already previews it as red
|
|
1054
|
+
// startPrice, so it must overwrite with null — otherwise the preview
|
|
1055
|
+
// would promise startPrice while the old value silently survived.
|
|
1056
|
+
if (raw === '')
|
|
1057
|
+
return isUnsetGridPrice(defaultValue) ? null : (defaultValue === undefined ? null : defaultValue);
|
|
1058
|
+
const lower = raw.trim().toLowerCase();
|
|
1059
|
+
if (isUnsetGridPrice(lower))
|
|
993
1060
|
return null;
|
|
994
1061
|
if (lower === 'pool')
|
|
995
1062
|
return lower;
|
|
996
1063
|
if (lower === 'book')
|
|
997
1064
|
return 'book';
|
|
998
|
-
if (
|
|
1065
|
+
if (AMA_GRID_PRICE_PATTERN.test(lower))
|
|
999
1066
|
return lower;
|
|
1000
1067
|
const num = Number(raw);
|
|
1001
1068
|
if (Number.isFinite(num) && num > 0)
|
|
1002
1069
|
return num;
|
|
1003
|
-
console.log(
|
|
1070
|
+
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
1071
|
}
|
|
1005
1072
|
}
|
|
1006
1073
|
/**
|
|
@@ -1010,36 +1077,10 @@ async function askGridPriceMode(promptText, defaultValue, startPrice) {
|
|
|
1010
1077
|
* @returns {Object} A normalized bot draft.
|
|
1011
1078
|
*/
|
|
1012
1079
|
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;
|
|
1080
|
+
// Seeding rules live in modules/bot_defaults.ts (single defaults source).
|
|
1081
|
+
// Return type stays `any`: the editor prompt-flow treats drafts as dynamic
|
|
1082
|
+
// bags and its inference depends on it.
|
|
1083
|
+
return seedBotDraft(base);
|
|
1043
1084
|
}
|
|
1044
1085
|
/**
|
|
1045
1086
|
* Resolve data.preferredAccount to a chain account ID and stamp data.accountId
|
|
@@ -1162,10 +1203,30 @@ async function ensureBotAccountId(data, timeoutMs = 15000, quiet = false, force
|
|
|
1162
1203
|
/**
|
|
1163
1204
|
* Interactive menu to edit bot data.
|
|
1164
1205
|
* @param {Object} [base={}] - The initial bot data to edit.
|
|
1165
|
-
* @
|
|
1206
|
+
* @param {number} [index=0] - Position the edited bot will occupy in the
|
|
1207
|
+
* bots array (used to derive the same botKey the runtime uses for unnamed
|
|
1208
|
+
* fallback entries).
|
|
1209
|
+
* @param {number} [baseIndex=index] - Position the base entry currently
|
|
1210
|
+
* occupies (differs from `index` for copies, whose source sits elsewhere).
|
|
1211
|
+
* @returns {Promise<{ data: any, commitAdapter: (() => void)|null }|null>}
|
|
1212
|
+
* The edited draft plus a commit hook for staged `6) Adapter` flags — the
|
|
1213
|
+
* caller MUST run `commitAdapter()` only after its bots.json save succeeded
|
|
1214
|
+
* (so a failed save never leaves the whitelist out of sync), or null when
|
|
1215
|
+
* cancelled.
|
|
1166
1216
|
*/
|
|
1167
|
-
async function promptBotData(base = {}) {
|
|
1217
|
+
async function promptBotData(base = {}, index = 0, baseIndex = index) {
|
|
1168
1218
|
const data = normalizeBotDraft(base);
|
|
1219
|
+
// Market-adapter flags (6) Adapter) live in
|
|
1220
|
+
// profiles/market_adapter_whitelist.json keyed by botKey — the same file
|
|
1221
|
+
// the market adapter reads. Stage edits here and expose them through the
|
|
1222
|
+
// commit hook so Cancel (or a failed bots.json save) discards them
|
|
1223
|
+
// together with the rest of the draft.
|
|
1224
|
+
const baseEntry = base && typeof base === 'object' && Object.keys(base).length > 0 ? base : null;
|
|
1225
|
+
const baseBotKey = baseEntry ? String(normalizeBotEntry(baseEntry, baseIndex).botKey || '') : '';
|
|
1226
|
+
let adapterStaged = null;
|
|
1227
|
+
const isAmaGridPriceDraft = () => /^ama(?:[1-4])?$/.test(String(data.gridPrice ?? '').trim().toLowerCase());
|
|
1228
|
+
const adapterFlags = () => adapterStaged
|
|
1229
|
+
|| getWhitelistFlags(baseBotKey || String(normalizeBotEntry(data, index).botKey || ''));
|
|
1169
1230
|
let finished = false;
|
|
1170
1231
|
let cancelled = false;
|
|
1171
1232
|
let showMenu = true;
|
|
@@ -1173,17 +1234,28 @@ async function promptBotData(base = {}) {
|
|
|
1173
1234
|
if (showMenu) {
|
|
1174
1235
|
console.log(`\n${COLORS.bold}--- Bot Editor: ` + (data.name || 'New Bot') + ` ---${COLORS.reset}`);
|
|
1175
1236
|
console.log(`${COLORS.yellowBold}1) Pair:${COLORS.reset} ${COLORS.cyan}${data.assetA || '?'} / ${data.assetB || '?'}${COLORS.reset}`);
|
|
1176
|
-
console.log(`${COLORS.yellowBold}2) Identity:${COLORS.reset} ${COLORS.orange}Name:${COLORS.reset} ${data.name || '?'}
|
|
1177
|
-
console.log(`${COLORS.yellowBold}3) Price:${COLORS.reset} ${COLORS.orange}Range:${COLORS.reset} [${colorPriceRangeValue(data.minPrice)} - ${colorPriceRangeValue(data.maxPrice)}]
|
|
1178
|
-
console.log(`${COLORS.yellowBold}4) Grid:${COLORS.reset} ${COLORS.orange}Weights:${COLORS.reset} (S:${data.weightDistribution.sell}, B:${data.weightDistribution.buy})
|
|
1237
|
+
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)}`);
|
|
1238
|
+
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)}`);
|
|
1239
|
+
console.log(`${COLORS.yellowBold}4) Grid:${COLORS.reset} ${COLORS.orange}Weights:${COLORS.reset} (S:${data.weightDistribution.sell}, B:${data.weightDistribution.buy}) | ${COLORS.orange}Incr:${COLORS.reset} ${data.incrementPercent}%, ${COLORS.orange}Spread:${COLORS.reset} ${data.targetSpreadPercent}%`);
|
|
1179
1240
|
console.log(`${COLORS.yellowBold}5) Funding:${COLORS.reset} ${COLORS.orange}Sell:${COLORS.reset} ${colorPercentageInput(data.botFunds.sell)}, ${COLORS.orange}Buy:${COLORS.reset} ${colorPercentageInput(data.botFunds.buy)} | ${COLORS.orange}Orders:${COLORS.reset} (S:${data.activeOrders.sell}, B:${data.activeOrders.buy}) | ${COLORS.orange}Reserve:${COLORS.reset} (S:${data.reserveOrders?.sell ?? 0}, B:${data.reserveOrders?.buy ?? 0})`);
|
|
1241
|
+
{
|
|
1242
|
+
const flags = adapterFlags();
|
|
1243
|
+
const inert = !isAmaGridPriceDraft() && (flags.ama || flags.dynamicWeight || flags.asymmetricBounds);
|
|
1244
|
+
const hint = inert ? ` ${COLORS.red}(needs gridPrice=ama)${COLORS.reset}` : '';
|
|
1245
|
+
// Adapter flags read as a health state, not a neutral toggle:
|
|
1246
|
+
// green = on (colorBooleanFlag's greenWhenTrue form, same
|
|
1247
|
+
// helper section 2 uses for Active). Price is the gate for the
|
|
1248
|
+
// other two, so off stays red; Weight/Range are optional
|
|
1249
|
+
// riders, so off is a bright-yellow warning rather than an error.
|
|
1250
|
+
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}`);
|
|
1251
|
+
}
|
|
1180
1252
|
console.log('--------------------------------------------------');
|
|
1181
1253
|
console.log(`${COLORS.greenBold}S) Save & Exit${COLORS.reset}`);
|
|
1182
1254
|
console.log(`${COLORS.white}C) Cancel (Discard changes)${COLORS.reset}`);
|
|
1183
1255
|
showMenu = false;
|
|
1184
1256
|
}
|
|
1185
1257
|
const choice = (await readInput('Select section to edit or action: ', {
|
|
1186
|
-
validate: (input) => ['1', '2', '3', '4', '5', 's', 'c'].includes(input.toLowerCase())
|
|
1258
|
+
validate: (input) => ['1', '2', '3', '4', '5', '6', 's', 'c'].includes(input.toLowerCase())
|
|
1187
1259
|
})).trim().toLowerCase();
|
|
1188
1260
|
if (choice === '\x1b') {
|
|
1189
1261
|
finished = true;
|
|
@@ -1260,7 +1332,7 @@ async function promptBotData(base = {}) {
|
|
|
1260
1332
|
const poolR = await askPoolRef('poolRef (pinned pool ID for price source)', data.poolRef);
|
|
1261
1333
|
if (poolR === '\x1b')
|
|
1262
1334
|
break;
|
|
1263
|
-
const gp = await askGridPriceMode('gridPrice (
|
|
1335
|
+
const gp = await askGridPriceMode('gridPrice (ama1/ama2/ama3/ama4)', data.gridPrice);
|
|
1264
1336
|
if (gp === '\x1b')
|
|
1265
1337
|
break;
|
|
1266
1338
|
data.minPrice = minP;
|
|
@@ -1318,6 +1390,29 @@ async function promptBotData(base = {}) {
|
|
|
1318
1390
|
data.reserveOrders = { buy: rBuy, sell: rSell };
|
|
1319
1391
|
showMenu = true;
|
|
1320
1392
|
break;
|
|
1393
|
+
case '6': {
|
|
1394
|
+
// Per-bot market-adapter flags (Price = AMA pricing, Weight =
|
|
1395
|
+
// dynamic weights, Range = asymmetric range scaling).
|
|
1396
|
+
const flags = adapterFlags();
|
|
1397
|
+
if (!isAmaGridPriceDraft()) {
|
|
1398
|
+
console.log(`${COLORS.gray}Note: these flags only take effect when gridPrice is ama/ama1..ama4 (set in 3) Price).${COLORS.reset}`);
|
|
1399
|
+
}
|
|
1400
|
+
const price = await askBoolean('AMA pricing (Price)', flags.ama);
|
|
1401
|
+
if (price === '\x1b')
|
|
1402
|
+
break;
|
|
1403
|
+
const weight = await askBoolean('Dynamic weights (Weight)', flags.dynamicWeight);
|
|
1404
|
+
if (weight === '\x1b')
|
|
1405
|
+
break;
|
|
1406
|
+
const range = await askBoolean('Range scaling (Range)', flags.asymmetricBounds);
|
|
1407
|
+
if (range === '\x1b')
|
|
1408
|
+
break;
|
|
1409
|
+
adapterStaged = { ama: price, dynamicWeight: weight, asymmetricBounds: range };
|
|
1410
|
+
if (!price && (weight || range)) {
|
|
1411
|
+
console.log(`${COLORS.yellow}Note: Weight/Range only take effect while Price (AMA) is enabled.${COLORS.reset}`);
|
|
1412
|
+
}
|
|
1413
|
+
showMenu = true;
|
|
1414
|
+
break;
|
|
1415
|
+
}
|
|
1321
1416
|
case 's':
|
|
1322
1417
|
// Final basic validation before saving
|
|
1323
1418
|
if (!data.name || !data.assetA || !data.assetB || !data.preferredAccount) {
|
|
@@ -1350,11 +1445,40 @@ async function promptBotData(base = {}) {
|
|
|
1350
1445
|
}
|
|
1351
1446
|
if (cancelled)
|
|
1352
1447
|
return null;
|
|
1448
|
+
// Staged 6) Adapter flags become a commit hook instead of an immediate
|
|
1449
|
+
// write: the caller runs it only after its bots.json save succeeded, so
|
|
1450
|
+
// the whitelist can never end up updated by a save that was rejected
|
|
1451
|
+
// (duplicate bot keys, write error). Writes are skipped when the staged
|
|
1452
|
+
// flags already match what is stored — an unchanged save never creates a
|
|
1453
|
+
// placeholder entry.
|
|
1454
|
+
const stagedFlags = adapterStaged;
|
|
1455
|
+
const commitAdapter = stagedFlags ? () => {
|
|
1456
|
+
const newKey = String(normalizeBotEntry(data, index).botKey || '');
|
|
1457
|
+
const needRename = !!(baseBotKey && newKey && baseBotKey !== newKey);
|
|
1458
|
+
const current = getWhitelistFlags(baseBotKey || newKey);
|
|
1459
|
+
const unchanged = stagedFlags.ama === current.ama
|
|
1460
|
+
&& stagedFlags.dynamicWeight === current.dynamicWeight
|
|
1461
|
+
&& stagedFlags.asymmetricBounds === current.asymmetricBounds;
|
|
1462
|
+
if (needRename && !renameWhitelistEntry(baseBotKey, newKey)) {
|
|
1463
|
+
console.log(`${COLORS.yellow}Adapter flags not saved — see the warning above.${COLORS.reset}`);
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
// Unchanged flags need no write — a successful rename above already
|
|
1467
|
+
// migrated the entry under the new key.
|
|
1468
|
+
if (unchanged)
|
|
1469
|
+
return;
|
|
1470
|
+
if (!setWhitelistFlags(newKey, stagedFlags)) {
|
|
1471
|
+
console.log(`${COLORS.yellow}Warning: adapter flags not saved — could not write ${whitelistFile()}.${COLORS.reset}`);
|
|
1472
|
+
}
|
|
1473
|
+
else {
|
|
1474
|
+
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}`);
|
|
1475
|
+
}
|
|
1476
|
+
} : null;
|
|
1353
1477
|
// Return the final data structure, preserving ALL fields from the normalized
|
|
1354
1478
|
// draft (deep-copy of base + defaults, minus stripped runtime-managed fields).
|
|
1355
1479
|
// A fixed whitelist here would silently drop custom overrides (logging, timing,
|
|
1356
1480
|
// feeParams, gridLimits, poolRef, etc.) when the caller replaces the bot entry.
|
|
1357
|
-
return { ...data };
|
|
1481
|
+
return { data: { ...data }, commitAdapter };
|
|
1358
1482
|
}
|
|
1359
1483
|
/**
|
|
1360
1484
|
* Interactive menu to edit general settings.
|
|
@@ -1365,7 +1489,7 @@ async function promptGeneralSettings() {
|
|
|
1365
1489
|
let finished = false;
|
|
1366
1490
|
while (!finished) {
|
|
1367
1491
|
console.log(`${COLORS.bold}--- General Settings (Global) ---${COLORS.reset}`);
|
|
1368
|
-
console.log(`${COLORS.yellowBold}1) Grid Health:${COLORS.reset} ${COLORS.orange}
|
|
1492
|
+
console.log(`${COLORS.yellowBold}1) Grid Health:${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}%`);
|
|
1369
1493
|
console.log(`${COLORS.yellowBold}2) Order Recovery:${COLORS.reset} ${COLORS.orange}Dust Threshold:${COLORS.reset} ${settings.GRID_LIMITS.PARTIAL_DUST_THRESHOLD_PERCENTAGE}%`);
|
|
1370
1494
|
const nodeCount = (settings.NODES.list || []).length;
|
|
1371
1495
|
const hcIntervalMin = ((settings.NODES.healthCheck?.intervalMs || NODE_MANAGEMENT.HEALTH_CHECK_INTERVAL_MS) / 60000).toFixed(0);
|
|
@@ -1387,18 +1511,22 @@ async function promptGeneralSettings() {
|
|
|
1387
1511
|
}
|
|
1388
1512
|
switch (choice) {
|
|
1389
1513
|
case '1':
|
|
1390
|
-
const gRegen = await askNumberWithBounds('Grid
|
|
1514
|
+
const gRegen = await askNumberWithBounds('Grid Funds Regeneration %', settings.GRID_LIMITS.GRID_REGENERATION_PERCENTAGE, 0.1, 50);
|
|
1391
1515
|
if (gRegen === '\x1b')
|
|
1392
1516
|
break;
|
|
1393
1517
|
const rms = await askNumberWithBounds('RMS Divergence Threshold %', settings.GRID_LIMITS.GRID_COMPARISON.RMS_PERCENTAGE, 1, 100);
|
|
1394
1518
|
if (rms === '\x1b')
|
|
1395
1519
|
break;
|
|
1396
|
-
const amaDelta = await askNumberWithBounds('AMA
|
|
1520
|
+
const amaDelta = await askNumberWithBounds('AMA Δ', settings.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT, 0.1, 50.0);
|
|
1397
1521
|
if (amaDelta === '\x1b')
|
|
1398
1522
|
break;
|
|
1523
|
+
const amaSlopeDelta = await askNumberWithBounds('AMA-Slope Δ', settings.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT, 0.1, 100.0);
|
|
1524
|
+
if (amaSlopeDelta === '\x1b')
|
|
1525
|
+
break;
|
|
1399
1526
|
settings.GRID_LIMITS.GRID_REGENERATION_PERCENTAGE = gRegen;
|
|
1400
1527
|
settings.GRID_LIMITS.GRID_COMPARISON.RMS_PERCENTAGE = rms;
|
|
1401
1528
|
settings.MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT = amaDelta;
|
|
1529
|
+
settings.MARKET_ADAPTER.AMA_SLOPE_DELTA_THRESHOLD_PERCENT = amaSlopeDelta;
|
|
1402
1530
|
break;
|
|
1403
1531
|
case '2':
|
|
1404
1532
|
const dust = await askNumberWithBounds('Partial Dust Threshold %', settings.GRID_LIMITS.PARTIAL_DUST_THRESHOLD_PERCENTAGE, 0.1, 50);
|
|
@@ -1528,12 +1656,13 @@ async function main() {
|
|
|
1528
1656
|
case '1': {
|
|
1529
1657
|
while (true) {
|
|
1530
1658
|
try {
|
|
1531
|
-
const
|
|
1532
|
-
if (!
|
|
1659
|
+
const result = await promptBotData({}, config.bots.length);
|
|
1660
|
+
if (!result)
|
|
1533
1661
|
break;
|
|
1534
|
-
config.bots.push(
|
|
1662
|
+
config.bots.push(result.data);
|
|
1535
1663
|
saveBotsConfig(config, filePath);
|
|
1536
|
-
|
|
1664
|
+
result.commitAdapter?.();
|
|
1665
|
+
console.log(`\nAdded bot '${result.data.name}' to ${path.basename(filePath)}.`);
|
|
1537
1666
|
}
|
|
1538
1667
|
catch (err) {
|
|
1539
1668
|
console.log(`\n❌ Invalid input: ${getErrorMessage(err)}\n`);
|
|
@@ -1548,10 +1677,12 @@ async function main() {
|
|
|
1548
1677
|
if (idx === null || idx === '\x1b')
|
|
1549
1678
|
break;
|
|
1550
1679
|
try {
|
|
1551
|
-
const
|
|
1552
|
-
if (
|
|
1553
|
-
config.bots[idx] =
|
|
1680
|
+
const result = await promptBotData(config.bots[idx], idx);
|
|
1681
|
+
if (result) {
|
|
1682
|
+
config.bots[idx] = result.data;
|
|
1554
1683
|
saveBotsConfig(config, filePath);
|
|
1684
|
+
result.commitAdapter?.();
|
|
1685
|
+
const entry = result.data;
|
|
1555
1686
|
console.log(`saved settings '${entry.name}' in ${path.basename(filePath)}.\n`);
|
|
1556
1687
|
console.log(`Live pickup (~1min, no reload needed): ${BOT_LIVE_CONFIG_KEYS.join(' / ')}.`);
|
|
1557
1688
|
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 +1704,17 @@ async function main() {
|
|
|
1573
1704
|
if (confirm === '\x1b')
|
|
1574
1705
|
break;
|
|
1575
1706
|
if (confirm) {
|
|
1707
|
+
// Derive the whitelist key before splicing (createBotKey
|
|
1708
|
+
// uses the index only for unnamed fallback entries).
|
|
1709
|
+
const removedKey = String(normalizeBotEntry(config.bots[idx], idx).botKey || '');
|
|
1576
1710
|
const removed = config.bots.splice(idx, 1)[0];
|
|
1577
1711
|
saveBotsConfig(config, filePath);
|
|
1712
|
+
// Only after the bots.json save succeeded, drop the
|
|
1713
|
+
// matching whitelist entry so a deleted bot leaves no
|
|
1714
|
+
// stale flags behind (name reuse would inherit them).
|
|
1715
|
+
if (!removeWhitelistEntry(removedKey)) {
|
|
1716
|
+
console.log(`${COLORS.yellow}Warning: could not remove whitelist entry '${removedKey}' — check ${whitelistFile()}.${COLORS.reset}`);
|
|
1717
|
+
}
|
|
1578
1718
|
console.log(`Removed bot '${removed.name || placeholderName}' from ${path.basename(filePath)}.\n`);
|
|
1579
1719
|
}
|
|
1580
1720
|
else {
|
|
@@ -1589,11 +1729,12 @@ async function main() {
|
|
|
1589
1729
|
if (idx === null || idx === '\x1b')
|
|
1590
1730
|
break;
|
|
1591
1731
|
try {
|
|
1592
|
-
const
|
|
1593
|
-
if (
|
|
1594
|
-
config.bots.splice(idx + 1, 0,
|
|
1732
|
+
const result = await promptBotData(config.bots[idx], idx + 1, idx);
|
|
1733
|
+
if (result) {
|
|
1734
|
+
config.bots.splice(idx + 1, 0, result.data);
|
|
1595
1735
|
saveBotsConfig(config, filePath);
|
|
1596
|
-
|
|
1736
|
+
result.commitAdapter?.();
|
|
1737
|
+
console.log(`Copied bot '${result.data.name}' into ${path.basename(filePath)}.\n`);
|
|
1597
1738
|
}
|
|
1598
1739
|
}
|
|
1599
1740
|
catch (err) {
|
|
@@ -1617,5 +1758,5 @@ async function main() {
|
|
|
1617
1758
|
}
|
|
1618
1759
|
console.log('Botmanager closed!');
|
|
1619
1760
|
}
|
|
1620
|
-
export { main, normalizeBotDraft, ensureBotAccountId, parseJsonWithComments };
|
|
1761
|
+
export { main, normalizeBotDraft, ensureBotAccountId, parseJsonWithComments, parseBooleanInput, colorGridPriceValue, formatPoolRefLabel, loadGeneralSettings, saveGeneralSettings };
|
|
1621
1762
|
//# sourceMappingURL=account_bots.js.map
|