mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-04-17 19:22:09 +00:00
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.
This commit is contained in:
102
plans/260414-1457-trading-module/phase-01-symbols-and-format.md
Normal file
102
plans/260414-1457-trading-module/phase-01-symbols-and-format.md
Normal file
@@ -0,0 +1,102 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user