Skip to main content

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

AreaOperationsBehavior
HealthGET /healthLiveness response from the process
Authenticationregister, login, Google login, refresh, logout, validate under /api/v1/authProxies Sentinel while keeping the Trader API contract
Asset discoverysearch, popular, detail, icon, enriched detail, related assets under /api/v1/assetsCombines provider data, database popularity, and Redis caches
Pricecurrent and history under /api/v1/priceFetches market prices and chart bars with caching
Credit packsGET /api/v1/credit-packsLists active purchasable packs
News previewGET /api/v1/news/previewProxies deterministic News-Analyzer preview; no LLM and no credits
Analysis previewGET /api/v1/analysis/previewReturns only a previously warmed rich preview for the top popular asset
Platform metricsGET /api/v1/platform/metricsPublic aggregate product metrics
Payment webhookPOST /api/v1/payments/webhookReceives 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

AreaOperationsImportant rule
Analysisinitialize, overview, fundamentals, valuation, risks, scenarios, historical contextRequires a valid token; only overview deducts credits
Recent assetslist, add, clear /api/v1/assets/recentRecords are scoped to the authenticated account
Creditsbalance, ensure user, analysis costAdmin balance is reported as unlimited
Historylist, detail, delete /api/v1/analysis/historyDetail and deletion are account-owned
Settingsread and update by Sentinel account identifierStores disclaimer acceptance and preferences
Purchasespurchase history by account identifierReturns normalized display fields
Paymentscreate, status, cancelUses PayOS and the authenticated account
Administrationaccounts, purchases, dashboard, cache settings, credit packsRequires 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:

  1. Reject an empty symbol, normalize it to uppercase, and resolve asset detail.
  2. Reject an unresolved asset whose type remains UNKNOWN with HTTP 404.
  3. Require authentication strictly (user.account_id). Unauthenticated requests are rejected immediately with HTTP 401.
  4. For a non-admin authenticated account, deduct 10 credits transactionally. Return HTTP 402 if the balance is too low.
  5. Trigger News-Analyzer’s asynchronous analysis pipeline (analyze_async).
  6. Save a pending user history record (status = 'pending') in PostgreSQL returning its UUID.
  7. 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

DataDefault lifetimeNotes
Analysis overview21,600 secondsBase default; selected setting is operator controlled
Current price60 secondsPer symbol
Price history300 secondsPer symbol, interval, and range
Asset search120 secondsPer normalized query
Popular assets60 secondsPrice-enriched aggregate
Binance symbol list300 secondsProvider support data
CoinGecko symbol map3,600 secondsProvider support data
Enriched profile3,600 secondsShares the profile concept with News-Analyzer
Asset detail600 secondsPer 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

SettingDefaultMeaning
PORT, HOST8080, all interfacesListener
ENVIRONMENTProdSelects service-discovery behavior
DOMAIN_NAMElocalhost when neededProduction News-Analyzer host suffix
SENTINEL_URLinternal Sentinel addressAuthentication upstream
FINAI_JWT_SECRETemptyShared token-verification secret; must be set in production
DATABASE_URLlocal finai PostgreSQLDurable application database
REDIS_URLlocal RedisRequired cache connection
STARTING_CREDITS100Initial user balance
ANALYZE_COST10Loaded by configuration, but current charge logic uses the compiled 10-credit constant
PRICE_CACHE_TTL60 secondsCurrent-price cache
HISTORY_CACHE_TTL300 secondsPrice-history cache
ANALYSIS_CACHE_TTL21,600 secondsInitial overview cache setting
FINNHUB_API_KEYabsentOptional enrichment provider
PAYOS_CLIENT_ID, PAYOS_API_KEY, PAYOS_CHECKSUM_KEYemptyPayment credentials
PAYOS_WEBHOOK_URLhosted defaultPublic settlement callback
RUST_LOGservice info loggingStructured 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.