Trader-BE
Trader-BE is the product’s core application API. It is a Rust Axum service that stands between the frontend BFF and the account, market-data, payment, persistence, cache, and analysis systems. It owns product rules such as who may call a route, how credits are charged, which analyses belong to a user, and which cached result is safe to reuse. It does not perform the deep investment analysis itself.
Startup and dependency behavior
At startup Trader-BE loads environment configuration, attempts a PostgreSQL connection, runs embedded migrations when connected, builds one shared HTTP client, connects every Redis cache, constructs repositories and services, binds public and protected routes, and starts background cache work.
PostgreSQL failure is non-fatal. Credits, purchases, settings, and credit packs switch to in-memory implementations and analysis history is disabled. Redis failure is fatal because every major market and analysis service is constructed with a Redis connection. Operators should therefore treat PostgreSQL as required for production correctness and Redis as required for availability.
Two background loops run. Asset caches warm shortly after startup. The preview loop reloads operational cache settings, checks whether rich preview refresh is enabled and due, selects the most popular asset, and warms its analysis preview. A PostgreSQL advisory lock prevents several service replicas from performing the same expensive refresh at once.
Public API groups
| Area | Operations | Behavior |
|---|---|---|
| Health | GET /health | Liveness response from the process |
| Authentication | register, login, Google login, refresh, logout, validate under /api/v1/auth | Proxies Sentinel while keeping the Trader API contract |
| Asset discovery | search, popular, detail, icon, enriched detail, related assets under /api/v1/assets | Combines provider data, database popularity, and Redis caches |
| Price | current and history under /api/v1/price | Fetches market prices and chart bars with caching |
| Credit packs | GET /api/v1/credit-packs | Lists active purchasable packs |
| News preview | GET /api/v1/news/preview | Proxies deterministic News-Analyzer preview; no LLM and no credits |
| Analysis preview | GET /api/v1/analysis/preview | Returns only a previously warmed rich preview for the top popular asset |
| Platform metrics | GET /api/v1/platform/metrics | Public aggregate product metrics |
| Payment webhook | POST /api/v1/payments/webhook | Receives PayOS settlement notifications |
Public does not mean uncached or unvalidated. Symbols and query fields are normalized, provider failures are handled, and Nginx may add a separate HTTP cache in deployed environments.
Protected API groups
| Area | Operations | Important rule |
|---|---|---|
| Analysis | initialize, overview, fundamentals, valuation, risks, scenarios, historical context | Requires a valid token; only overview deducts credits |
| Recent assets | list, add, clear /api/v1/assets/recent | Records are scoped to the authenticated account |
| Credits | balance, ensure user, analysis cost | Admin balance is reported as unlimited |
| History | list, detail, delete /api/v1/analysis/history | Detail and deletion are account-owned |
| Settings | read and update by Sentinel account identifier | Stores disclaimer acceptance and preferences |
| Purchases | purchase history by account identifier | Returns normalized display fields |
| Payments | create, status, cancel | Uses PayOS and the authenticated account |
| Administration | accounts, purchases, dashboard, cache settings, credit packs | Requires ADMIN role, not merely authentication |
Asset search, identity, and popularity
Search uses Yahoo Finance for listed instruments and Binance information for cryptocurrency trading symbols. Supporting CoinGecko data helps map cryptocurrency symbols to stable identities. Results are grouped by asset class and normalized before reaching the frontend.
Asset detail resolves the symbol and type. Enriched detail asks News-Analyzer for a market profile when the shared profile cache is cold. Related assets use available sector, industry, type, and provider information. Icons are fetched through a dedicated endpoint so browser image loading does not need to know provider URLs.
Popularity is based on recorded user behavior in PostgreSQL. A search contributes 0.1 to the score and an analysis contributes 1.0, making a paid research action ten times stronger than a search. The popular endpoint orders by that score. During a cold start with too little observed activity it uses a curated fallback so the homepage is not empty. Prices are added when the popular response is assembled.
Recent searches and global popularity are separate. Recent searches are private per-account navigation history. Popularity is an aggregate ranking and contains no user identity in its response.
Price behavior
Current price and history are fetched from upstream market providers, normalized into the frontend contract, and stored in typed Redis caches. The default current-price lifetime is 60 seconds. The configured default history lifetime is 300 seconds. Cache settings may alter selected operational lifetimes.
The history endpoint accepts the symbol, interval, and range expected by the chart UI. Those inputs are part of the cache identity, so a one-day chart cannot be returned for a one-year request.
Analysis orchestration
Analysis initialization normalizes the symbol and forwards it to News-Analyzer. It does not deduct credits. It returns the Redis-backed context identifier used by later supporting-tab calls.
The overview operation (POST /api/v1/analyze) performs these product-level steps:
- Reject an empty symbol, normalize it to uppercase, and resolve asset detail.
- Reject an unresolved asset whose type remains UNKNOWN with HTTP 404.
- Require authentication strictly (
user.account_id). Unauthenticated requests are rejected immediately with HTTP 401. - For a non-admin authenticated account, deduct 10 credits transactionally. Return HTTP 402 if the balance is too low.
- Trigger News-Analyzer’s asynchronous analysis pipeline (
analyze_async). - Save a pending user history record (
status = 'pending') in PostgreSQL returning its UUID. - Return HTTP 202 ACCEPTED with
{ "id": local_id, "status": "pending" }.
The frontend or client then polls GET /api/v1/analysis/history/:id to retrieve real-time completed_stages, running_stages, and the final result payload once execution finishes.
The supporting-tab operations simply normalize the symbol and pass the optional context identifier to News-Analyzer. They do not deduct credits and Trader-BE currently returns their JSON largely unchanged.
Trader-BE’s overview cache key includes the normalized symbol and news lookback. A cached response is considered safe only when it is linked to a News-Analyzer analysis run. This prevents older payload shapes without durable lineage from being reused indefinitely.
Concurrent cache misses with the same normalized symbol and news lookback are coalesced inside each Trader-BE process. The first request calls News-Analyzer while later matching requests wait on the same in-flight key. After the first request caches its response, the waiting requests recheck the cache and return that exact result instead of starting duplicate analysis work. Different symbols or lookback values use different keys and may run concurrently. This lock is process-local; separate Trader-BE replicas still rely on the shared Redis result cache after a result has been written.
Analysis history
Trader-BE stores a product-facing summary of each completed user analysis in PostgreSQL. The schema evolved to include the News-Analyzer run identifier, model usage, summary fields, investment-overview payloads, variance details, and trend details. History list supports page, page size, and optional asset filter. The detail and delete operations include the authenticated account in their database query so one user cannot retrieve another user’s analysis by guessing its UUID.
News-Analyzer’s analysis artifact and Trader-BE’s history row are related but not duplicates. The analyzer artifact preserves stage outputs and evidence-oriented processing. Trader-BE history preserves the user-owned product record and presentation payload.
Credits
The credit repository owns balances and transaction entries. PostgreSQL deduction locks the account row inside a transaction, verifies the balance, writes the new balance, and records the reason. This prevents two concurrent analyses from both spending the same credits.
The fixed implemented charge is 10 credits. New users receive the configured starting amount, normally 100, when their credit record is ensured. Administrators bypass deduction. Low-confidence refunds are also transactional and create a positive credit transaction.
Only the low-confidence outcome causes an automatic refund. A timeout or upstream failure after deduction currently has no general compensation path. This is an operational and product limitation worth monitoring.
PayOS and purchase records
Active credit packs define name, number of credits, price, currency, description, badge, popularity marker, activation, and order. A signed-in user chooses a pack and Trader-BE creates both a local pending purchase and a PayOS payment link. The browser sends the user to PayOS.
The public webhook locates the local transaction by gateway order code, marks successful settlement, and adds credits. The signed-in status endpoint reconciles the current payment state, and cancellation marks or requests cancellation of a pending order. Purchase-history responses use browser-oriented field names and preserve status and payment method.
PayOS client identifier, API key, checksum key, and public webhook URL must be configured together. Without them, market and analysis features can still operate, but checkout cannot complete correctly.
Settings and administration
User settings contain disclaimer acceptance plus an open JSON preferences object. Reading an absent row returns false and an empty preferences object. Updating performs an upsert.
Administrator APIs list and inspect accounts, change account status, show aggregate dashboard data, list all purchases, manage credit packs, and read or update cache settings. Cache settings are stored in the database, copied into a shared in-process lock, and periodically reloaded by background work. This allows operational tuning without rebuilding the service.
Redis caches
| Data | Default lifetime | Notes |
|---|---|---|
| Analysis overview | 21,600 seconds | Base default; selected setting is operator controlled |
| Current price | 60 seconds | Per symbol |
| Price history | 300 seconds | Per symbol, interval, and range |
| Asset search | 120 seconds | Per normalized query |
| Popular assets | 60 seconds | Price-enriched aggregate |
| Binance symbol list | 300 seconds | Provider support data |
| CoinGecko symbol map | 3,600 seconds | Provider support data |
| Enriched profile | 3,600 seconds | Shares the profile concept with News-Analyzer |
| Asset detail | 600 seconds | Per symbol |
Production may place Nginx in front of Trader-BE. Its short HTTP response cache is an additional layer, not a replacement for Redis. When diagnosing freshness, inspect both layers and the runtime cache settings.
Configuration
| Setting | Default | Meaning |
|---|---|---|
| PORT, HOST | 8080, all interfaces | Listener |
| ENVIRONMENT | Prod | Selects service-discovery behavior |
| DOMAIN_NAME | localhost when needed | Production News-Analyzer host suffix |
| SENTINEL_URL | internal Sentinel address | Authentication upstream |
| FINAI_JWT_SECRET | empty | Shared token-verification secret; must be set in production |
| DATABASE_URL | local finai PostgreSQL | Durable application database |
| REDIS_URL | local Redis | Required cache connection |
| STARTING_CREDITS | 100 | Initial user balance |
| ANALYZE_COST | 10 | Loaded by configuration, but current charge logic uses the compiled 10-credit constant |
| PRICE_CACHE_TTL | 60 seconds | Current-price cache |
| HISTORY_CACHE_TTL | 300 seconds | Price-history cache |
| ANALYSIS_CACHE_TTL | 21,600 seconds | Initial overview cache setting |
| FINNHUB_API_KEY | absent | Optional enrichment provider |
| PAYOS_CLIENT_ID, PAYOS_API_KEY, PAYOS_CHECKSUM_KEY | empty | Payment credentials |
| PAYOS_WEBHOOK_URL | hosted default | Public settlement callback |
| RUST_LOG | service info logging | Structured log filtering |
In development, News-Analyzer is addressed by its internal container name. In production, Trader-BE constructs an HTTPS host from DOMAIN_NAME. A configured NEWS_ANALYZER_PORT value in compose does not affect this code path.
Logs and errors
Requests receive tracing output in JSON. Application errors map validation, authentication, not-found, payment-required, upstream, and internal cases to HTTP responses. Request identifiers are supported by middleware in the repository and should be propagated through deployments so a frontend failure can be matched to Trader-BE, News-Analyzer, and LLM-Wrapper logs.