Skip to content

Commit b703472

Browse files
baotoqclaude
andcommitted
docs(27): update state after phase completion
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c7756f4 commit b703472

File tree

3 files changed

+128
-13
lines changed

3 files changed

+128
-13
lines changed

.planning/STATE.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,27 @@
99
See: .planning/PROJECT.md (updated 2026-02-20)
1010

1111
**Core value:** Single view of all investments (crypto, ETF, savings) with real P&L, plus automated BTC DCA
12-
**Current focus:** Phase 27Price Feeds & Market Data
12+
**Current focus:** Phase 28Portfolio API Endpoints
1313

1414
## Current Position
1515

16-
Phase: 27 of 29 (Price Feeds & Market Data)
17-
Plan: 0 of TBD
18-
Status: Ready to plan
19-
Last activity: 2026-02-20 — Phase 26 complete (3 plans, 7 entities, 14 new tests)
16+
Phase: 27 of 29 (Price Feeds & Market Data) — COMPLETE
17+
Plan: 2 of 2 (all executed)
18+
Status: Phase 27 complete
19+
Last activity: 2026-02-20 — Phase 27 complete (2 plans, 3 providers, 11 new files)
2020

21-
Progress: [##░░░░░░░░] 25% (v4.0)
21+
Progress: [#####░░░░░] 50% (v4.0)
2222

2323
## Performance Metrics
2424

2525
**Velocity:**
26-
- Total plans completed: 59 (across v1.0-v4.0)
26+
- Total plans completed: 61 (across v1.0-v4.0)
2727
- v1.0: 1 day (11 plans)
2828
- v1.1: 1 day (7 plans)
2929
- v1.2: 2 days (12 plans)
3030
- v2.0: 2 days (15 plans)
3131
- v3.0: 1 day (11 plans)
32-
- v4.0: in progress (3 plans so far)
32+
- v4.0: in progress (5 plans so far)
3333

3434
**By Milestone:**
3535

@@ -40,7 +40,7 @@ Progress: [##░░░░░░░░] 25% (v4.0)
4040
| v1.2 | 9-12 | 12 | Complete |
4141
| v2.0 | 13-19 | 15 | Complete |
4242
| v3.0 | 20-25 | 11 | Complete |
43-
| v4.0 | 26-29 | 3/TBD | Phase 26 complete |
43+
| v4.0 | 26-29 | 5/TBD | Phase 27 complete |
4444

4545
## Accumulated Context
4646

@@ -63,6 +63,15 @@ All decisions logged in PROJECT.md Key Decisions table.
6363
- Compound interest test tolerance: 500 VND on 10M deposits (double-precision variance from Math.Pow)
6464
- VND Principal: numeric(18,0); Crypto Quantity: numeric(18,8); Fee: numeric(18,2); Rate: numeric(8,6)
6565

66+
**Phase 27 decisions:**
67+
- VNDirect dchart-api used instead of finfo-api (finfo times out externally, dchart verified live)
68+
- PriceFeedEntry uses positional record with long FetchedAtUnixSeconds (avoids MessagePack DateTimeOffset resolver issues)
69+
- VNDirect ETF provider uses stale-while-revalidate (returns stale immediately, fire-and-forget refresh)
70+
- OpenErApi exchange rate uses wait-for-fetch (accuracy matters more for currency conversion)
71+
- CoinGecko API key added as DefaultRequestHeader on HttpClient at DI registration time
72+
- Shared resilience config: 2 retries, 1s exponential backoff, 15s total/8s attempt timeout
73+
- VNDirect close prices multiplied by 1000 to convert from thousands-of-VND to actual VND
74+
6675
**v3.0 Flutter conventions carried forward:**
6776
- Dark-only theme, NavigationBar (Material 3) + CupertinoIcons, StatefulShellRoute
6877
- Manual fromJson for DTO models (no json_serializable), intl as explicit dependency
@@ -71,7 +80,7 @@ All decisions logged in PROJECT.md Key Decisions table.
7180

7281
### Known Risks
7382

74-
- Phase 27: VNDirect finfo API JSON schema unconfirmed (endpoint timed out during research). Needs live request verification at Phase 27 planning start. Use `/gsd:research-phase` before planning Phase 27.
83+
- VNDirect dchart-api is undocumented/unofficial — could change without notice (research valid until 2026-03-20)
7584

7685
### Pending Todos
7786

@@ -84,8 +93,8 @@ v4.0 roadmap: 4 phases (26-29), 20 requirements, all mapped.
8493
## Session Continuity
8594

8695
Last session: 2026-02-20
87-
Stopped at: Phase 26 complete — all 3 plans executed (domain models, interest calculator, EF Core persistence)
88-
Next step: `/gsd:research-phase 27` (verify VNDirect API before planning)
96+
Stopped at: Phase 27 complete — all 2 plans executed (shared types + CoinGecko provider, VNDirect + exchange rate + DI wiring)
97+
Next step: `/gsd:plan-phase 28` (Portfolio API Endpoints)
8998

9099
---
91-
*State updated: 2026-02-20 after Phase 26 completion*
100+
*State updated: 2026-02-20 after Phase 27 completion*
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Phase 27 Plan 01 — Summary
2+
3+
**Completed:** 2026-02-20
4+
**Duration:** Single pass, zero errors
5+
6+
## What Was Built
7+
8+
Created the shared price feed infrastructure and CoinGecko crypto price provider:
9+
10+
1. **PriceFeedEntry** — MessagePack-serializable positional record with `Price`, `FetchedAtUnixSeconds`, `Currency`. Uses `long` for timestamp to avoid DateTimeOffset resolver issues. Computed `FetchedAt` property via `[IgnoreMember]`.
11+
12+
2. **PriceFeedResult** — Return type record with `Price`, `FetchedAt`, `IsStale`, `Currency`. Static factories `Fresh()` and `Stale()` for clarity.
13+
14+
3. **ICryptoPriceProvider** — Interface with `GetPriceAsync(string coinGeckoId)` and batch `GetPricesAsync(IEnumerable<string> coinGeckoIds)`.
15+
16+
4. **IEtfPriceProvider** — Interface with `GetPriceAsync(string vnDirectTicker)`.
17+
18+
5. **IExchangeRateProvider** — Interface with `GetUsdToVndRateAsync()`.
19+
20+
6. **CoinGeckoPriceProvider** — Full implementation with:
21+
- 5-minute freshness window, 30-day physical Redis TTL
22+
- Lazy fetch: fresh cache returns immediately, stale attempts refresh with fallback, empty cache blocks
23+
- Batch API call via `/simple/price?ids={csv}&vs_currencies=usd`
24+
- Optional `x-cg-demo-api-key` header from CoinGeckoOptions.ApiKey
25+
- Primary constructor with HttpClient, IDistributedCache, IOptionsMonitor, ILogger
26+
27+
## Files Created
28+
29+
| File | Purpose |
30+
|------|---------|
31+
| `Infrastructure/PriceFeeds/PriceFeedEntry.cs` | MessagePack cache record |
32+
| `Infrastructure/PriceFeeds/PriceFeedResult.cs` | Provider return type |
33+
| `Infrastructure/PriceFeeds/Crypto/ICryptoPriceProvider.cs` | Crypto provider interface |
34+
| `Infrastructure/PriceFeeds/Crypto/CoinGeckoPriceProvider.cs` | CoinGecko implementation |
35+
| `Infrastructure/PriceFeeds/Etf/IEtfPriceProvider.cs` | ETF provider interface |
36+
| `Infrastructure/PriceFeeds/ExchangeRate/IExchangeRateProvider.cs` | Exchange rate interface |
37+
38+
## Verification
39+
40+
- `dotnet build TradingBot.slnx` — 0 errors
41+
- All MessagePack attributes correctly applied (MsgPack017 warning resolved with positional record)
42+
- No new NuGet packages required
43+
44+
## Decisions Made
45+
46+
- Used positional record syntax for PriceFeedEntry to avoid MessagePack init accessor warnings
47+
- CoinGecko API key sent per-request via HttpRequestMessage header (not default header on HttpClient — that's handled in ServiceCollectionExtensions in Plan 02)
48+
- Batch method returns stale results for cached IDs and throws only if uncached IDs fail to fetch
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Phase 27 Plan 02 — Summary
2+
3+
**Completed:** 2026-02-20
4+
**Duration:** Single pass, zero errors
5+
6+
## What Was Built
7+
8+
Completed the price feed infrastructure with VNDirect ETF provider, exchange rate provider, DI wiring, and Program.cs integration:
9+
10+
1. **VNDirectPriceProvider** — Stale-while-revalidate pattern:
11+
- Fresh cache (< 48h) returns immediately
12+
- Stale cache returns immediately + fire-and-forget background refresh via `_ = RefreshInBackgroundAsync()`
13+
- Empty cache blocks on fetch, throws if API is down
14+
- Close prices multiplied by 1000 (dchart returns thousands-of-VND)
15+
- Uses `CancellationToken.None` for background refresh (not tied to original request)
16+
17+
2. **VNDirectDchartResponse** — JSON DTO for dchart API array format (`t`, `c`, `o`, `h`, `l`, `v`, `s`).
18+
19+
3. **OpenErApiProvider** — Wait-for-fetch pattern (not stale-while-revalidate):
20+
- Fresh cache (< 12h) returns immediately
21+
- Stale cache tries refresh, falls back to stale on failure
22+
- Empty cache blocks, throws if API is down
23+
- Fetches from `v6/latest/USD`, extracts VND rate
24+
25+
4. **OpenErApiResponse** — JSON DTO for exchange rate response.
26+
27+
5. **ServiceCollectionExtensions.AddPriceFeeds()** — Wires all three providers:
28+
- CoinGeckoPriceProvider HttpClient (api.coingecko.com, optional x-cg-demo-api-key header)
29+
- VNDirectPriceProvider HttpClient (dchart-api.vndirect.com.vn)
30+
- OpenErApiProvider HttpClient (open.er-api.com)
31+
- Shared resilience config: 2 retries, 1s exponential backoff, 15s total timeout, 8s attempt timeout
32+
- All registered as Scoped (matching existing DI patterns)
33+
34+
6. **Program.cs** — Added `builder.Services.AddPriceFeeds(builder.Configuration)` after CoinGecko registration.
35+
36+
## Files Created/Modified
37+
38+
| File | Action |
39+
|------|--------|
40+
| `Infrastructure/PriceFeeds/Etf/VNDirectPriceProvider.cs` | Created |
41+
| `Infrastructure/PriceFeeds/Etf/VNDirectDchartResponse.cs` | Created |
42+
| `Infrastructure/PriceFeeds/ExchangeRate/OpenErApiProvider.cs` | Created |
43+
| `Infrastructure/PriceFeeds/ExchangeRate/OpenErApiResponse.cs` | Created |
44+
| `Infrastructure/PriceFeeds/ServiceCollectionExtensions.cs` | Created |
45+
| `Program.cs` | Modified (added using + AddPriceFeeds call) |
46+
47+
## Verification
48+
49+
- `dotnet build TradingBot.slnx` — 0 errors
50+
- `dotnet test` — 76/76 passed (all existing tests unchanged)
51+
- No new NuGet packages required
52+
53+
## Decisions Made
54+
55+
- VNDirect uses stale-while-revalidate (returns stale immediately, refreshes in background) per research recommendation — 48h TTL means staleness only during VN market hours
56+
- OpenErApiProvider uses wait-for-fetch (not stale-while-revalidate) — exchange rate accuracy matters more for currency conversion
57+
- Shared `ConfigureResilience` method avoids repeating resilience options 3 times
58+
- CoinGecko API key added as DefaultRequestHeader on HttpClient at registration time (ServiceCollectionExtensions), not per-request

0 commit comments

Comments
 (0)