Skip to content

Commit ee1d75c

Browse files
fix(crypto): Issue with small cap coins precision (#240)
--- Fix: Micro-cap Token Price Formatting and Backtest Parametrization Problem Micro-cap cryptocurrency tokens (PEPE, BONK, SHIB, FLOKI, etc.) were displaying as $0.00 in both LLM prompts and frontend charts due to fixed 2-decimal place formatting. This caused: - LLM agents receiving incorrect price data ($0.00 instead of actual prices like $0.00001234) - Poor trading decisions for meme coins and micro-cap tokens - Inefficient token usage in LLM prompts - Potential display issues in charts when these assets are shown Example Issue: PEPEUSDT: Current price is $0.00 ❌ (actual price: $0.00001234) Solution Implemented adaptive price formatting with scientific notation for small-value assets: New Formatting Logic: - Prices ≥ $1.00: Standard format with 4 decimals ($43,250.6700) - Prices < $1.00: Scientific notation ($1.23e-05) Benefits: - ✅ Token-efficient (saves tokens in LLM prompts) - ✅ LLM-friendly for mathematical operations - ✅ No precision loss - ✅ Works for any price magnitude Changes Made 1. Backend: LLM Prompt Formatting Files: - live_trade_bench/agents/bitmex_agent.py (lines 35-45) - live_trade_bench/agents/base_agent.py (lines 179-189) Current Price Display: if price >= 1.0: # Standard formatting for larger prices analysis_parts.append(f"{symbol}: Current price is ${price:,.4f}") else: # Scientific notation for micro-cap tokens (PEPE, BONK, etc.) analysis_parts.append(f"{symbol}: Current price is ${price:.2e}") Historical Price Display: if hist_price >= 1.0: price_str = f"{hist_price:.4f}" else: price_str = f"{hist_price:.2e}" 2. Frontend: Chart Tooltip Formatting File: frontend/src/components/charts/AreaChart.tsx (lines 45-61, 175-183, 213-215) Added formatPrice() utility function: ``` const formatPrice = (value: number): string => { if (value >= 1.0) { return value.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 4 }); } else if (value >= 0.0001) { return value.toFixed(8).replace(/\.?0+$/, ''); } else if (value > 0) { return value.toExponential(2); // Scientific notation } return '0'; }; ``` Applied to: - Tooltip labels - Tooltip footer (total value) - Y-axis tick labels <img width="545" height="467" alt="Screenshot 2025-11-18 at 22 00 58" src="https://github.com/user-attachments/assets/79f9ed09-3f67-4616-9b05-d803489c6766" /> 3. Backtest Script Parametrization File: examples/backtest_demo.py ``` Added CLI argument parser with full parametrization support: New CLI Arguments: --start-date, --end-date # Time period (YYYY-MM-DD) --exchanges # Comma-separated: stock,polymarket,bitmex --bitmex-symbols # Custom BitMEX symbols (e.g., XBTUSDT,PEPEUSDT) --stocks # Custom stock symbols (e.g., AAPL,TSLA) --bitmex-count, --stock-count # Number of trending assets to fetch --initial-cash-bitmex # Initial capital per exchange --threshold # Polymarket volume filter Example Usage: # 1-week backtest for BTC + PEPE python examples/backtest_demo.py \ --start-date 2025-11-10 \ --end-date 2025-11-17 \ --exchanges bitmex \ --bitmex-symbols XBTUSDT,PEPEUSDT \ --initial-cash-bitmex 1000 # Multi-exchange with custom assets python examples/backtest_demo.py \ --start-date 2025-11-01 \ --end-date 2025-11-30 \ --exchanges stock,bitmex \ --stocks AAPL,TSLA,NVDA \ --bitmex-symbols XBTUSDT,ETHUSDT,PEPEUSDT ```
1 parent 1b1b13b commit ee1d75c

File tree

5 files changed

+303
-36
lines changed

5 files changed

+303
-36
lines changed

examples/backtest_demo.py

Lines changed: 170 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# backtest_demo_simple.py
2+
import argparse
23
import json
34
import os
45
import sys
56
from concurrent.futures import ThreadPoolExecutor, as_completed
67
from datetime import datetime, timedelta
7-
from typing import Any, Dict, List, Tuple
8+
from typing import Any, Dict, List, Optional, Tuple
89

910
from live_trade_bench.systems.bitmex_system import BitMEXPortfolioSystem
1011
from live_trade_bench.systems.polymarket_system import PolymarketPortfolioSystem
@@ -17,17 +18,37 @@
1718
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
1819

1920

20-
def get_backtest_config() -> Dict[str, Any]:
21+
def get_backtest_config(args: Optional[argparse.Namespace] = None) -> Dict[str, Any]:
22+
"""Get backtest configuration from CLI args or defaults."""
23+
if args is None:
24+
# Defaults when no args provided
25+
return {
26+
"start_date": "2025-10-01",
27+
"end_date": "2025-11-01",
28+
"interval_days": 1,
29+
"initial_cash": {"polymarket": 500.0, "stock": 1000.0, "bitmex": 1000.0},
30+
"parallelism": int(os.environ.get("LTB_PARALLELISM", "16")),
31+
"threshold": 0.2,
32+
"market_num": 10,
33+
"stock_num": 15,
34+
"bitmex_num": 12,
35+
}
36+
37+
# Use CLI args with fallback to defaults
2138
return {
22-
"start_date": "2025-10-01",
23-
"end_date": "2025-11-01",
24-
"interval_days": 1,
25-
"initial_cash": {"polymarket": 500.0, "stock": 1000.0, "bitmex": 1000.0}, # Updated to $1,000
39+
"start_date": args.start_date or "2025-10-01",
40+
"end_date": args.end_date or "2025-11-01",
41+
"interval_days": args.interval_days,
42+
"initial_cash": {
43+
"polymarket": args.initial_cash_polymarket,
44+
"stock": args.initial_cash_stock,
45+
"bitmex": args.initial_cash_bitmex,
46+
},
2647
"parallelism": int(os.environ.get("LTB_PARALLELISM", "16")),
27-
"threshold": 0.2,
28-
"market_num": 10,
29-
"stock_num": 15,
30-
"bitmex_num": 12,
48+
"threshold": args.threshold,
49+
"market_num": args.polymarket_count,
50+
"stock_num": args.stock_count,
51+
"bitmex_num": args.bitmex_count,
3152
}
3253

3354

@@ -58,6 +79,8 @@ def build_systems(
5879
market_num: int = 5,
5980
stock_num: int = 15,
6081
bitmex_num: int = 12,
82+
custom_stocks: Optional[List[str]] = None,
83+
custom_bitmex_symbols: Optional[List[str]] = None,
6184
):
6285
systems: Dict[str, Dict[str, Any]] = {"polymarket": {}, "stock": {}, "bitmex": {}}
6386

@@ -70,18 +93,26 @@ def build_systems(
7093
)
7194

7295
if run_stock:
73-
print("Pre-fetching stock data...")
74-
from live_trade_bench.fetchers.stock_fetcher import fetch_trending_stocks
96+
if custom_stocks:
97+
print(f"Using custom stock list: {custom_stocks}")
98+
verified_stocks = custom_stocks
99+
else:
100+
print("Pre-fetching trending stock data...")
101+
from live_trade_bench.fetchers.stock_fetcher import fetch_trending_stocks
75102

76-
verified_stocks = fetch_trending_stocks(stock_num)
103+
verified_stocks = fetch_trending_stocks(stock_num)
77104

78105
if run_bitmex:
79-
print("Pre-fetching BitMEX contracts...")
80-
from live_trade_bench.fetchers.bitmex_fetcher import BitMEXFetcher
81-
fetcher = BitMEXFetcher()
82-
trending_contracts = fetcher.get_trending_contracts(limit=bitmex_num)
83-
bitmex_symbols = [c["symbol"] for c in trending_contracts]
84-
print(f" Using {len(bitmex_symbols)} BitMEX contracts: {bitmex_symbols[:5]}...")
106+
if custom_bitmex_symbols:
107+
print(f"Using custom BitMEX symbols: {custom_bitmex_symbols}")
108+
bitmex_symbols = custom_bitmex_symbols
109+
else:
110+
print("Pre-fetching trending BitMEX contracts...")
111+
from live_trade_bench.fetchers.bitmex_fetcher import BitMEXFetcher
112+
fetcher = BitMEXFetcher()
113+
trending_contracts = fetcher.get_trending_contracts(limit=bitmex_num)
114+
bitmex_symbols = [c["symbol"] for c in trending_contracts]
115+
print(f" Using {len(bitmex_symbols)} BitMEX contracts: {bitmex_symbols[:5]}...")
85116

86117
for model_name, model_id in models:
87118
if run_polymarket:
@@ -277,9 +308,125 @@ def save_models_data(
277308
)
278309

279310

311+
def parse_args() -> argparse.Namespace:
312+
"""Parse command-line arguments for backtest configuration."""
313+
parser = argparse.ArgumentParser(
314+
description="Run backtests for trading models across multiple exchanges",
315+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
316+
)
317+
318+
# Time period
319+
parser.add_argument(
320+
"--start-date",
321+
type=str,
322+
default="2025-10-01",
323+
help="Backtest start date (YYYY-MM-DD)",
324+
)
325+
parser.add_argument(
326+
"--end-date",
327+
type=str,
328+
default="2025-11-01",
329+
help="Backtest end date (YYYY-MM-DD)",
330+
)
331+
parser.add_argument(
332+
"--interval-days",
333+
type=int,
334+
default=1,
335+
help="Interval between trading days",
336+
)
337+
338+
# Exchange selection
339+
parser.add_argument(
340+
"--exchanges",
341+
type=str,
342+
default="stock,polymarket,bitmex",
343+
help="Comma-separated list of exchanges to test (stock,polymarket,bitmex)",
344+
)
345+
346+
# Asset counts (for auto-fetching trending assets)
347+
parser.add_argument(
348+
"--stock-count",
349+
type=int,
350+
default=15,
351+
help="Number of trending stocks to fetch",
352+
)
353+
parser.add_argument(
354+
"--polymarket-count",
355+
type=int,
356+
default=10,
357+
help="Number of Polymarket markets to fetch",
358+
)
359+
parser.add_argument(
360+
"--bitmex-count",
361+
type=int,
362+
default=12,
363+
help="Number of BitMEX contracts to fetch",
364+
)
365+
366+
# Custom asset lists (override auto-fetching)
367+
parser.add_argument(
368+
"--stocks",
369+
type=str,
370+
default=None,
371+
help="Custom stock symbols (comma-separated, e.g., AAPL,TSLA,MSFT)",
372+
)
373+
parser.add_argument(
374+
"--bitmex-symbols",
375+
type=str,
376+
default=None,
377+
help="Custom BitMEX symbols (comma-separated, e.g., XBTUSDT,PEPEUSDT)",
378+
)
379+
380+
# Initial cash
381+
parser.add_argument(
382+
"--initial-cash-stock",
383+
type=float,
384+
default=1000.0,
385+
help="Initial cash for stock trading",
386+
)
387+
parser.add_argument(
388+
"--initial-cash-polymarket",
389+
type=float,
390+
default=500.0,
391+
help="Initial cash for Polymarket trading",
392+
)
393+
parser.add_argument(
394+
"--initial-cash-bitmex",
395+
type=float,
396+
default=1000.0,
397+
help="Initial cash for BitMEX trading",
398+
)
399+
400+
# Polymarket specific
401+
parser.add_argument(
402+
"--threshold",
403+
type=float,
404+
default=0.2,
405+
help="Polymarket volume threshold filter",
406+
)
407+
408+
return parser.parse_args()
409+
410+
280411
def main():
412+
args = parse_args()
281413
print("🔮 Parallel Backtest (Per-agent systems)")
282-
cfg = get_backtest_config()
414+
415+
# Parse exchange selection
416+
exchanges = [x.strip().lower() for x in args.exchanges.split(",")]
417+
run_polymarket = "polymarket" in exchanges
418+
run_stock = "stock" in exchanges
419+
run_bitmex = "bitmex" in exchanges
420+
421+
# Parse custom asset lists
422+
custom_stocks = [s.strip() for s in args.stocks.split(",")] if args.stocks else None
423+
custom_bitmex_symbols = (
424+
[s.strip() for s in args.bitmex_symbols.split(",")]
425+
if args.bitmex_symbols
426+
else None
427+
)
428+
429+
cfg = get_backtest_config(args)
283430
all_models = get_base_model_configs()
284431

285432
# Filter to GPT + Anthropic only
@@ -291,10 +438,6 @@ def main():
291438

292439
print(f"⚡ Using {len(models)} models (GPT + Anthropic only)")
293440
print(f" Filtered from {len(all_models)} total models")
294-
295-
run_polymarket = True
296-
run_stock = True
297-
run_bitmex = True
298441
market_count = sum([run_polymarket, run_stock, run_bitmex])
299442
market_names = []
300443
if run_stock:
@@ -317,7 +460,9 @@ def main():
317460
threshold=cfg["threshold"],
318461
market_num=cfg["market_num"],
319462
stock_num=cfg["stock_num"],
320-
bitmex_num=cfg["bitmex_num"]
463+
bitmex_num=cfg["bitmex_num"],
464+
custom_stocks=custom_stocks,
465+
custom_bitmex_symbols=custom_bitmex_symbols,
321466
)
322467

323468
for i, d in enumerate(days, 1):

frontend/src/components/charts/AreaChart.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,24 @@ const AreaChart: React.FC<AreaChartProps> = ({
4242
title = "Portfolio Composition Over Time",
4343
size = 'medium'
4444
}) => {
45+
// Format price with adaptive precision for small crypto values
46+
const formatPrice = (value: number): string => {
47+
if (value >= 1.0) {
48+
// Standard formatting for larger values
49+
return value.toLocaleString(undefined, {
50+
minimumFractionDigits: 2,
51+
maximumFractionDigits: 4
52+
});
53+
} else if (value >= 0.0001) {
54+
// Show 8 decimals for small values
55+
return value.toFixed(8).replace(/\.?0+$/, '');
56+
} else if (value > 0) {
57+
// Scientific notation for very small values (micro-cap tokens)
58+
return value.toExponential(2);
59+
}
60+
return '0';
61+
};
62+
4563
if (!portfolioHistory || portfolioHistory.length === 0) {
4664
return (
4765
<div className={`area-chart-container ${size}`}>
@@ -157,11 +175,11 @@ const AreaChart: React.FC<AreaChartProps> = ({
157175
label: (context: any) => {
158176
const label = context.dataset.label || '';
159177
const value = context.parsed.y;
160-
return `${label}: $${value.toFixed(2)}`;
178+
return `${label}: $${formatPrice(value)}`;
161179
},
162180
footer: (tooltipItems: any[]) => {
163181
const total = tooltipItems.reduce((sum, item) => sum + item.parsed.y, 0);
164-
return `Total: $${total.toFixed(2)}`;
182+
return `Total: $${formatPrice(total)}`;
165183
},
166184
},
167185
},
@@ -193,7 +211,7 @@ const AreaChart: React.FC<AreaChartProps> = ({
193211
size: size === 'small' ? 8 : 10,
194212
},
195213
callback: (value: any) => {
196-
return `$${value}`;
214+
return `$${formatPrice(value)}`;
197215
},
198216
},
199217
},

live_trade_bench/agents/base_agent.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,15 @@ def _format_price_history(
178178
for i, h in enumerate(reversed(recent_history)):
179179
hist_price = h.get("price", 0.0)
180180
hist_date = h.get("date", "Unknown Date")
181-
price_str = (
182-
f"close price ${hist_price:,.2f}"
183-
if is_stock
184-
else f"{hist_price:.4f}"
185-
)
181+
# LLM-friendly formatting: scientific notation for small values
182+
if is_stock:
183+
price_str = f"close price ${hist_price:,.2f}"
184+
else:
185+
# Crypto: use scientific notation for micro-cap tokens
186+
if hist_price >= 1.0:
187+
price_str = f"{hist_price:.4f}"
188+
else:
189+
price_str = f"{hist_price:.2e}"
186190
change_str = "N/A"
187191
original_index = len(recent_history) - 1 - i
188192
if original_index > 0:
@@ -192,7 +196,11 @@ def _format_price_history(
192196
if prev_day_price > 0:
193197
change = hist_price - prev_day_price
194198
change_pct = (change / prev_day_price) * 100
195-
change_str = f"{change:+.2f} ({change_pct:+.2f}%)"
199+
# Use scientific notation for small crypto price changes
200+
if not is_stock and abs(change) < 0.01:
201+
change_str = f"{change:+.2e} ({change_pct:+.2f}%)"
202+
else:
203+
change_str = f"{change:+.2f} ({change_pct:+.2f}%)"
196204
lines.append(f" - {hist_date}: {price_str} (Change: {change_str})")
197205
else:
198206
current_price = self.history_tail(ticker, 1)

live_trade_bench/agents/bitmex_agent.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,15 @@ def _prepare_market_analysis(self, market_data: Dict[str, Dict[str, Any]]) -> st
3232
price = data.get("current_price", 0.0)
3333
price_history = data.get("price_history", [])
3434

35-
# Format price with crypto-specific styling
35+
# Format price with crypto-specific styling (LLM-friendly)
36+
# Use scientific notation for small values to save tokens and enable math
3637
if "USD" in symbol or "USDT" in symbol:
37-
analysis_parts.append(f"{symbol}: Current price is ${price:,.2f}")
38+
if price >= 1.0:
39+
# Standard formatting for larger prices
40+
analysis_parts.append(f"{symbol}: Current price is ${price:,.4f}")
41+
else:
42+
# Scientific notation for micro-cap tokens (PEPE, BONK, etc.)
43+
analysis_parts.append(f"{symbol}: Current price is ${price:.2e}")
3844
else:
3945
analysis_parts.append(f"{symbol}: Current price is {price:.6f}")
4046

0 commit comments

Comments
 (0)