Files
miti99bot/plans/260414-1457-trading-module/phase-01-symbols-and-format.md
tiennm99 c9270764f2 feat: add fake trading module with crypto, stocks, forex and gold
Paper trading system with 5 commands (trade_topup, trade_buy,
trade_sell, trade_convert, trade_stats). Supports VN stocks via TCBS,
crypto via CoinGecko, forex via ER-API, and gold via PAX Gold proxy.
Per-user portfolio stored in KV with 60s price caching. 54 new tests.
2026-04-14 15:16:53 +07:00

3.7 KiB

phase, title, status, priority, effort
phase title status priority effort
1 Symbol Registry + Formatters Pending P2 45m

Phase 1: Symbol Registry + Formatters

Context

Overview

Two pure-data/pure-function files with zero side effects. Foundation for all other phases.

File: src/modules/trading/symbols.js

Requirements

  • Export SYMBOLS — frozen object keyed by uppercase symbol name
  • Export CURRENCIES — frozen Set of supported fiat: VND, USD
  • Export helper getSymbol(name) — case-insensitive lookup, returns entry or undefined
  • Export helper listSymbols() — returns formatted string of all symbols grouped by category

Data shape

export const SYMBOLS = Object.freeze({
  // crypto
  BTC:  { category: "crypto",  apiId: "bitcoin",  label: "Bitcoin" },
  ETH:  { category: "crypto",  apiId: "ethereum", label: "Ethereum" },
  SOL:  { category: "crypto",  apiId: "solana",   label: "Solana" },
  // stock (Vietnam)
  TCB:  { category: "stock",   apiId: "TCB", label: "Techcombank" },
  VPB:  { category: "stock",   apiId: "VPB", label: "VPBank" },
  FPT:  { category: "stock",   apiId: "FPT", label: "FPT Corp" },
  VNM:  { category: "stock",   apiId: "VNM", label: "Vinamilk" },
  HPG:  { category: "stock",   apiId: "HPG", label: "Hoa Phat" },
  // others
  GOLD: { category: "others",  apiId: "pax-gold", label: "Gold (troy oz)" },
});

Implementation steps

  1. Create src/modules/trading/symbols.js
  2. Define SYMBOLS constant with all entries above
  3. Define CURRENCIES = Object.freeze(new Set(["VND", "USD"]))
  4. getSymbol(name)SYMBOLS[name.toUpperCase()] with guard for falsy input
  5. listSymbols() — group by category, format as SYMBOL — Label per line
  6. Keep under 60 lines

File: src/modules/trading/format.js

Requirements

  • formatVND(n) — integer, dot thousands separator, suffix VND. Example: 15.000.000 VND
  • formatUSD(n) — 2 decimals, comma thousands, prefix $. Example: $1,234.56
  • formatCrypto(n) — up to 8 decimals, strip trailing zeros. Example: 0.00125
  • formatStock(n) — integer (Math.floor), no decimals. Example: 150
  • formatAmount(n, symbol) — dispatcher: looks up symbol category, calls correct formatter
  • formatCurrency(n, currency) — VND or USD formatter based on currency string

Implementation steps

  1. Create src/modules/trading/format.js
  2. formatVND: Math.round(n).toLocaleString("vi-VN") + VND — verify dot separator (or manual impl for CF Workers locale support)
  3. formatUSD: n.toFixed(2) with comma grouping + $ prefix
  4. formatCrypto: parseFloat(n.toFixed(8)).toString() to strip trailing zeros
  5. formatStock: Math.floor(n).toString()
  6. formatAmount: switch on getSymbol(sym).category
  7. formatCurrency: switch on currency string
  8. Keep under 80 lines

Edge cases

  • formatVND(0) -> 0 VND
  • formatCrypto(1.00000000) -> 1
  • formatAmount with unknown symbol -> return raw number string
  • CF Workers may not have full locale support — implement manual dot-separator for VND

Failure modes

Risk Likelihood Impact Mitigation
toLocaleString not available in CF Workers runtime Medium Medium Manual formatter fallback: split on groups of 3, join with .

Success criteria

  • SYMBOLS has 9 entries across 3 categories
  • getSymbol("btc") returns BTC entry (case-insensitive)
  • getSymbol("NOPE") returns undefined
  • formatVND(15000000) === "15.000.000 VND"
  • formatCrypto(0.001) === "0.001" (no trailing zeros)
  • formatStock(1.7) === "1"
  • Both files under 200 lines