mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-04-17 19:22:09 +00:00
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.
103 lines
3.7 KiB
Markdown
103 lines
3.7 KiB
Markdown
---
|
|
phase: 1
|
|
title: "Symbol Registry + Formatters"
|
|
status: Pending
|
|
priority: P2
|
|
effort: 45m
|
|
---
|
|
|
|
# Phase 1: Symbol Registry + Formatters
|
|
|
|
## Context
|
|
|
|
- [Module pattern](../../docs/adding-a-module.md)
|
|
- [KV interface](../../src/db/kv-store-interface.js)
|
|
|
|
## 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
|
|
|
|
```js
|
|
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
|