Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions scripts/analyze-orders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,25 @@ function stripColorCodes(str: string): string {
// Load bot configurations
const botsConfig = readJSON(BOTS_CONFIG).bots;

/**
* formatBotFunds: Normalize a botFunds side value for display.
*
* botFunds accepts both percentage strings ("90%") and absolute numbers (35
* meaning 35 units of the side's asset) — see the README bot options table.
* The "Funds:" display path calls string methods (padEnd/replace) on these
* values, so a numeric setting crashed the whole analysis with
* "str.replace is not a function". Always hand the display a string.
*
* @param {string|number|null} value - raw botFunds side value from bots.json
* @returns {string} display representation, '-' when unset
*/
function formatBotFunds(value: any): string {
if (value == null) return '-';
if (typeof value === 'number') return Number.isFinite(value) ? String(value) : '-';
const str = String(value);
return str.length > 0 ? str : '-';
}

function getConfiguredBotConfig(botKey: string, botData: any): any {
const meta = botData?.meta || {};
return botsConfig.find((bot: any, index: any) => {
Expand Down Expand Up @@ -569,8 +588,11 @@ function analyzeOrder(botData: any, config: any, botKey: string): any {
},
// Target active orders from config
activeOrdersTarget: config ? config.activeOrders : null,
// Bot fund allocation settings from config
botFunds: config ? config.botFunds : null,
// Bot fund allocation settings from config (normalized to strings for
// the display paths; bots.json allows percentage strings or numbers)
botFunds: config && config.botFunds
? { buy: formatBotFunds(config.botFunds.buy), sell: formatBotFunds(config.botFunds.sell) }
: null,
// Weight distribution from config
weightDistribution: config ? config.weightDistribution : null,
// Resolved grid price label, value, and staleness flag
Expand Down
43 changes: 43 additions & 0 deletions tests/test_analyze_orders_dynamic_weight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,48 @@ function testAnalyzeOrderOmitsDynamicWeightForNonAma() {
assert.strictEqual(analysis.dynamicWeight, null, 'non-AMA bot should have null dynamicWeight');
}

function testAnalyzeOrderFormatsNumericBotFunds() {
const { analyzeOrder, formatAnalysis } = loadAnalyzer();
const botData = {
meta: { assetA: 'XRP', assetB: 'BTS', updatedAt: new Date().toISOString() },
boundaryIdx: 0,
grid: [
{ type: 'buy', state: 'active', orderId: 'a', price: 100, size: 1 },
{ type: 'sell', state: 'active', orderId: 'b', price: 110, size: 1 },
],
};
// bots.json documents botFunds as either percentage strings ("90%") or
// absolute numbers. A numeric value used to crash formatAnalysis with
// "str.replace is not a function" because the Funds display path calls
// string methods on the raw config values.
const config = {
gridPrice: 'fixed',
targetSpreadPercent: 1.5,
incrementPercent: 0.5,
activeOrders: { buy: 1, sell: 1 },
botFunds: { buy: 35, sell: 90 },
weightDistribution: { buy: 0.5, sell: 0.5 },
};
const analysis = analyzeOrder(botData, config, 'numeric-funds-bot');
assert.strictEqual(analysis.botFunds.buy, '35', 'numeric botFunds.buy should be normalized to a string');
assert.strictEqual(analysis.botFunds.sell, '90', 'numeric botFunds.sell should be normalized to a string');

// The original crash: formatAnalysis renders the Funds line with
// padEnd/stripColorCodes on these values.
let output;
assert.doesNotThrow(() => { output = formatAnalysis(analysis); }, 'formatAnalysis must survive numeric botFunds configs');
const fundsLine = stripColorCodes(String(output)).split('\n').find((l) => l.includes('Funds:'));
assert.ok(fundsLine, 'Funds line should be rendered');
assert.ok(fundsLine.includes('35'), 'Funds line should show the numeric buy value');
assert.ok(fundsLine.includes('90'), 'Funds line should show the numeric sell value');

// Percentage strings must keep their exact form.
const analysisPct = analyzeOrder(botData, { ...config, botFunds: { buy: '35%', sell: '90%' } }, 'pct-funds-bot');
assert.strictEqual(analysisPct.botFunds.buy, '35%', 'percentage botFunds.buy should pass through unchanged');
assert.strictEqual(analysisPct.botFunds.sell, '90%', 'percentage botFunds.sell should pass through unchanged');
assert.doesNotThrow(() => { formatAnalysis(analysisPct); }, 'formatAnalysis must survive percentage botFunds configs');
}

function testResolveAmaKey() {
const { resolveAmaKey } = loadAnalyzer();
assert.strictEqual(resolveAmaKey({ gridPrice: 'ama' }), 'AMA3', 'ama resolves to AMA3 (default)');
Expand Down Expand Up @@ -782,6 +824,7 @@ async function main() {
testFormatWeightLineNullWeights();
testAnalyzeOrderIncludesDynamicWeightForAma();
testAnalyzeOrderOmitsDynamicWeightForNonAma();
testAnalyzeOrderFormatsNumericBotFunds();
console.log('analyze-orders dynamic weight tests passed');
}

Expand Down