Skip to content

Latest commit

 

History

History
400 lines (318 loc) · 24.3 KB

File metadata and controls

400 lines (318 loc) · 24.3 KB

WInTheChat — System Design

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│  Browser (Next.js :3000)                                        │
│  ├── User Portal      (Terminal, Chat, Newsletter)              │
│  └── Manager Portal   (EventFeed, ContentCards, Reprompt)       │
└──────────────┬──────────────────────────────┬───────────────────┘
               │  REST (/api/*)               │  WebSocket (/ws/*)
┌──────────────▼──────────────────────────────▼───────────────────┐
│  FastAPI Gateway (:8000)                                        │
│  ├── Routers: analysis, trades, content, chat, users            │
│  ├── Services: langgraph_runner, supabase_client, llm           │
│  └── WebSocket: ConnectionManager broadcast                     │
└──────────────┬──────────────────────────────────────────────────┘
               │  triggers
┌──────────────▼──────────────────────────────────────────────────┐
│  LangGraph Agent (in-process)                                   │
│  ├── Node A: Anomaly Detection                                  │
│  ├── Node B: News Correlation        ── Gemini for reasoning    │
│  ├── Node C: Behavioral Coaching     ── Gemini for reasoning    │
│  └── Node D: Content Generator       ── Gemini for generation   │
└──────────────┬──────────────────────────────────────────────────┘
               │  fastmcp.Client (HTTP)
┌──────────────▼──────────────────────────────────────────────────┐
│  FastMCP Tool Server (:9000)                                    │
│  ├── get_price_data            → yfinance                       │
│  ├── search_market_moving_news → NewsData.io                    │
│  ├── fetch_article_deep_dive   → NewsData.io                    │
│  ├── query_user_patterns       → Supabase                       │
│  ├── verify_correlation        → deterministic scoring          │
│  ├── get_news_sentiment        → Alpha Vantage                  │
│  ├── get_alpha_stock_quote     → Alpha Vantage                  │
│  ├── get_finnhub_quote         → Finnhub                        │
│  ├── get_crypto_candles        → Finnhub                        │
│  └── search_mediastack_news    → MediaStack                     │
└─────────────────────────────────────────────────────────────────┘
                         │
                    ┌────▼────┐
                    │ Supabase │  (users, trade_history,
                    │          │   behavioral_metrics,
                    └──────────┘   content_drafts)

Key design decision: LangGraph nodes call FastMCP tools directly via fastmcp.Client (not LLM tool-calling). Gemini is used only for reasoning/generation within nodes. This gives a deterministic pipeline with AI-powered analysis at each step.


1. Backend — FastAPI Gateway

Directory: backend/ Entry point: backend/app/main.py Runs on: port 8000

Routers

File Endpoint Method Purpose
routers/analysis.py /api/analyze/trigger POST Trigger LangGraph pipeline in background task. Accepts ticker, user_id, persona. Returns task_id.
routers/analysis.py /api/analyze/result/{task_id} GET Poll for completed analysis result by task ID.
routers/trades.py /api/trades POST Execute buy/sell trade. Writes to Supabase, updates balance.
routers/trades.py /api/trades GET Fetch trade history for a user (last 50).
routers/content.py /api/content/drafts GET List content drafts with optional ?status= filter.
routers/content.py /api/content/drafts/{id} PATCH Approve, decline, or reprompt a draft. Reprompt calls Gemini to regenerate.
routers/chat.py /api/chat POST Stateless coaching chat. Fetches user context via MCP, sends to Gemini.
routers/users.py /api/users/{id}/newsletter GET Personalized event-driven newsletter. Aggregates content drafts into events.

Services

File Function Purpose
services/llm.py get_llm_client() Returns AsyncOpenAI pointed at Gemini's OpenAI-compatible endpoint.
services/llm.py MODEL "gemini-2.0-flash" — used by all LLM calls in backend.
services/langgraph_runner.py run_analysis() Invokes the compiled LangGraph pipeline, broadcasts WebSocket events at each step, saves drafts to Supabase.
services/langgraph_runner.py create_event_callback() Creates an async callback that tags events with task_id and broadcasts via ConnectionManager.
services/supabase_client.py get_supabase() Singleton Supabase client factory.

WebSocket

File Endpoint/Class Purpose
ws/manager.py ConnectionManager Manages active WebSocket connections. Methods: connect(), disconnect(), broadcast().
ws/manager.py WS /ws/events Accepts client connections, keeps alive, broadcasts pipeline events.

WebSocket event shape:

{
  "task_id": "uuid",
  "event_type": "node_complete | coaching_warning | content_ready | analysis_started | analysis_complete | error",
  "node": "anomaly_detection | news_correlation | behavioral_coaching | content_generator",
  "data": { ... }
}

Scripts

File Function Purpose
scripts/seed_data.py seed() Creates demo user, behavioral metrics, 17 trade history records (mix of revenge_trade, panic_sell, fomo_buy, disciplined), and 3 content drafts (Buffett/Trump/Burry for NVDA).

Config

File Class/Function Purpose
app/config.py Settings Pydantic settings loaded from env: gemini_api_key, supabase_url, supabase_key, newsdata_api_key, mcp_server_url, backend_port, mem0_api_key.
app/config.py get_settings() LRU-cached settings singleton.

Services (continued)

File Function Purpose
services/memory.py get_memory_client() Singleton Mem0 Platform client.
services/memory.py add_memory() Store conversation exchanges as persistent memory for a user.
services/memory.py search_memory() Semantic search across user memories.
services/memory.py get_all_memories() Retrieve all stored memories for a user.
services/memory.py delete_memory() Delete a specific memory by ID.
services/memory.py format_memories_for_prompt() Format memories into LLM-ready prompt text.

Memory Integration

The Mem0 persistent memory layer adds cross-session context to two key surfaces:

  1. Chat endpoint (/api/chat): Before each response, searches Mem0 for memories relevant to the user's message. After responding, stores the exchange as a new memory. This allows the coaching bot to reference past conversations ("Last time you asked about FOMO, I suggested...").

  2. Behavioral coaching node (LangGraph Node C): Before generating coaching advice, searches for past coaching history. After generating, stores the advice. This means the pipeline remembers "We warned this user about revenge trading 3 times this week" and can escalate accordingly.

Memory API endpoints:

Endpoint Method Purpose
/api/memory/{user_id} GET List all memories for a user
/api/memory/search POST Semantic search across memories
/api/memory/{memory_id} DELETE Delete a specific memory
/api/memory/user/{user_id} DELETE Delete all memories for a user

2. FastMCP Server — Tool Server

Directory: fastmcp_server/ Entry point: fastmcp_server/server.py Runs on: port 9000 (streamable-HTTP transport)

Tools

File Tool Function Input Output Data Source
tools/price_data.py get_price_data(ticker, timeframe) ticker (str), timeframe (str, default "5d") {ticker, timeframe, prices[], anomaly{zscore, mean, std, is_anomaly}, latest_price, change_pct} yfinance + Z-score computation
tools/news_search.py search_market_moving_news(ticker, timeframe) ticker (str), timeframe (str, default "7d") {ticker, timeframe, article_count, articles[{article_id, title, source, published_at, sentiment, ai_summary, category}]} NewsData.io API
tools/news_deep_dive.py fetch_article_deep_dive(article_id) article_id (str) {article_id, title, source, published_at, full_content, key_entities[], key_figures{}} NewsData.io API
tools/user_patterns.py query_user_patterns(user_id) user_id (str) {user_id, user_name, balance, behavioral_metrics{}, recent_trades[], pattern_summary{win_rate, avg_loss, avg_win, most_common_emotional_tag}} Supabase
tools/correlation.py verify_correlation(price_event, news_event) price_event (dict), news_event (dict) {correlation_score, strength, temporal_score, relevance_score, magnitude_score, justification} Deterministic 3-layer scoring
tools/alpha_vantage.py get_news_sentiment(tickers, topics?, time_from?, sort?, limit?) tickers (str), topics (str, optional), time_from (str, optional), sort (str, default "LATEST"), limit (int, default 10) {ticker, sentiment_score_avg, sentiment_label, article_count, articles[{title, source, time_published, sentiment_score, sentiment_label, summary, url}]} Alpha Vantage NEWS_SENTIMENT API
tools/alpha_vantage.py get_alpha_stock_quote(ticker) ticker (str) {ticker, price, change, change_pct, volume, high, low, previous_close, latest_trading_day} Alpha Vantage GLOBAL_QUOTE API
tools/finnhub_data.py get_finnhub_quote(ticker) ticker (str) {ticker, current_price, change, change_pct, high, low, open, previous_close, timestamp} Finnhub Quote API
tools/finnhub_data.py get_crypto_candles(symbol, resolution?, days_back?) symbol (str, e.g. "BTC"), resolution (str, default "D"), days_back (int, default 5) {symbol, resolution, candle_count, candles[{timestamp, open, high, low, close, volume}], latest_close} Finnhub Crypto Candle API
tools/mediastack_news.py search_mediastack_news(keywords, categories?, countries?, languages?, sort?, limit?) keywords (str), categories (str, optional), countries (str, default "us"), languages (str, default "en"), sort (str, default "published_desc"), limit (int, default 10) {keywords, article_count, articles[{title, source, author, description, url, category, published_at, country, language}]} MediaStack News API

Every tool has a mock fallback that activates when the live API is unavailable (rate-limited, missing key, network error). Mock data is realistic and demo-ready.

Utilities

File Function Purpose
utils/zscore.py compute_zscore(prices, window=20) Rolling Z-score over a price window. Returns {zscore, mean, std, is_anomaly}. Anomaly threshold: abs(zscore) > 2.0.
utils/supabase_client.py get_supabase() Singleton Supabase client factory.

Correlation Scoring Algorithm

verify_correlation uses a weighted 3-layer check:

Layer Weight What it measures Score range
Temporal proximity 30% Time between price event and news publish. ≤2h = 1.0, ≤24h = 0.3–1.0, ≤72h = 0.2, else 0. 0–1
Relevance 40% Keyword overlap between ticker/company names and article text. 0–1
Magnitude vs Sentiment 30% Alignment of sentiment direction (positive/negative) with price direction (up/down), scaled by magnitude. 0–1

Final score ≥ 0.6 → Strong, ≥ 0.4 → Moderate, < 0.4 → Weak.


3. LangGraph Agent — Analysis Pipeline

Directory: langgraph_agent/ Invoked by: backend/app/services/langgraph_runner.py Not a standalone server — runs in-process within FastAPI.

Pipeline

START
  │
  ▼
┌─────────────────────┐
│ Node A: Anomaly      │  Calls: get_price_data
│ Detection            │  Sets: price_data, is_anomaly, should_continue
└─────────┬───────────┘
          │
    ┌─────▼──────┐
    │ anomaly?   │
    └──┬─────┬───┘
   yes │     │ no
       ▼     ▼
┌──────────┐ END
│ Node B:  │
│ News     │  Calls: search_market_moving_news, verify_correlation
│ Corr.    │  Uses Gemini for justification text
└────┬─────┘
     ▼
┌──────────┐
│ Node C:  │  Calls: query_user_patterns
│ Coaching │  Uses Gemini for personalized advice
└────┬─────┘
     ▼
┌──────────┐
│ Node D:  │  Calls: fetch_article_deep_dive
│ Content  │  Uses Gemini with persona prompts
│ Gen.     │  Generates X + LinkedIn posts per persona
└────┬─────┘
     ▼
    END

State (graph/state.py)

AnalysisState is a TypedDict carrying data across all nodes:

Field Type Set by
ticker str Input
user_id str Input
persona str Input ("buffett", "trump", "burry", or "all")
price_data dict Node A
is_anomaly bool Node A
news_articles list[dict] Node B
correlation_result dict Node B
user_patterns dict Node C
coaching_advice str Node C
content_drafts list[dict] Node D
should_continue bool Node A (controls routing)
error str | None Any node on failure
task_id str Input
event_callback callable Input (WebSocket broadcaster)

Nodes

File Function MCP Tools Called Gemini Usage
nodes/anomaly_detection.py anomaly_detection(state) get_price_data None
nodes/news_correlation.py news_correlation(state) search_market_moving_news, verify_correlation Generates 2-3 sentence justification
nodes/behavioral_coaching.py behavioral_coaching(state) query_user_patterns Generates risk assessment, coaching advice, behavioral warning
nodes/content_generator.py content_generator(state) fetch_article_deep_dive Generates X post (≤280 chars) + LinkedIn post (2-3 paragraphs) per persona

Persona Prompts (graph/prompts/personas.py)

Persona Style Tone
Buffett Folksy, value-driven wisdom. Metaphors about farming, fishing, baseball. Calm, patient. References intrinsic value, margin of safety, compound returns.
Trump Hyperbolic, bullish, ALL CAPS keywords, exclamation marks. Extremely confident, short punchy sentences, superlatives.
Burry Doom-laden, cryptic, hyper-technical. Bearish, contrarian. References historical crashes, P/E ratios, debt levels.

Other Prompts

File Constant Purpose
prompts/correlation.py CORRELATION_JUSTIFICATION_PROMPT Template for explaining why a news event caused a price move.
prompts/coaching.py COACHING_PROMPT Template for generating behavioral coaching advice. Includes risk assessment, specific advice, and behavioral warning.
prompts/personas.py CONTENT_GENERATION_PROMPT Template for generating X + LinkedIn posts. Outputs JSON {x_post, linkedin_post}.

MCP Client (graph/mcp_client.py)

Function Purpose
call_mcp_tool(tool_name, arguments) Opens fastmcp.Client connection to http://localhost:9000/mcp, calls the named tool, parses JSON from the text content block.

LLM Client (graph/llm.py)

Export Value Purpose
get_llm_client() AsyncOpenAI(base_url=Gemini endpoint) Returns client for Gemini 2.0 Flash via OpenAI compatibility layer.
MODEL "gemini-2.0-flash" Model identifier passed to all chat.completions.create() calls.

4. Frontend — Next.js

Directory: frontend/ Entry point: frontend/src/app/layout.tsx Runs on: port 3000 Proxies: /api/*:8000, /ws/*:8000 (via next.config.ts rewrites)

Pages

Route File Description
/ app/page.tsx Landing page with links to User and Manager portals.
/portal/user app/portal/user/page.tsx Trading terminal: ticker selector, price chart, trade form, trade history.
/portal/user/chat app/portal/user/chat/page.tsx AI coaching chat with behavioral context.
/portal/user/newsletter app/portal/user/newsletter/page.tsx Event-driven newsletter with correlation badges. Real-time refresh via WebSocket.
/portal/manager app/portal/manager/page.tsx Content management: run analysis, event feed, content cards with approve/decline/reprompt.

Layouts

File Component Purpose
app/layout.tsx RootLayout HTML shell, dark mode, global CSS.
app/portal/layout.tsx PortalLayout Top nav bar with portal toggle (User/Manager), sub-navigation, user avatar.

Components — Trading

File Component Props Purpose
components/trading/TerminalPanel.tsx TerminalPanel onTradeExecuted? callback Buy/sell form with ticker selector, action toggle, amount input, result display.
components/trading/PriceChart.tsx PriceChart data: PricePoint[], ticker: string Recharts AreaChart with gradient fill. Color-coded green/red. Displays price and change %.
components/trading/TradeHistory.tsx TradeHistory trades: Trade[] Table with ticker, action badge, amount, PnL, emotional tag badge. Color-coded by tag.

Components — Chat

File Component Props Purpose
components/chat/ChatWindow.tsx ChatWindow none Full chat interface: message list, input, loading dots. Calls sendChatMessage API. Auto-scrolls.
components/chat/CoachMessage.tsx CoachMessage message: ChatMessage Single message bubble. User = right/blue, Coach = left/dark with "AI Coach" label.

Components — Newsletter

File Component Props Purpose
components/newsletter/NewsletterCard.tsx NewsletterCard event object Card with ticker badge, change %, correlation strength badge (Strong/Moderate/Weak with color), headline, summary, Z-score.

Components — Manager

File Component Props Purpose
components/manager/EventFeed.tsx EventFeed events: WSEvent[] Scrollable event list. Each event has type badge (color-coded), node name, and contextual data (ticker, anomaly, correlation, coaching snippet, draft count).
components/manager/ContentCard.tsx ContentCard draft, onUpdated? Draft card with platform icon (X/LinkedIn), persona badge, content, correlation score. Actions: Approve (green), Decline (red), Edit (blue). Expandable RepromptBox.
components/manager/PersonaSelector.tsx PersonaSelector value, onChange Button group: Buffett (emerald), Trump (orange), Burry (red), All (blue).
components/manager/RepromptBox.tsx RepromptBox onSubmit, loading Textarea + Regenerate button for custom content instructions.

Hooks

File Hook Returns Purpose
hooks/useWebSocket.ts useWebSocket(url?) {events, connected, clearEvents} Connects to ws://localhost:8000/ws/events. Accumulates parsed WSEvent objects. Auto-reconnects after 3s on disconnect.
hooks/useAnalysis.ts useAnalysis() {runAnalysis, loading, taskId, error} Wraps triggerAnalysis API call with loading/error state management.

API Client (lib/api.ts)

Function HTTP Call Purpose
triggerAnalysis(ticker, userId, persona) POST /api/analyze/trigger Start analysis pipeline
executeTrade(data) POST /api/trades Execute buy/sell trade
getTradeHistory(userId) GET /api/trades Fetch trade history
getContentDrafts(status?) GET /api/content/drafts List content drafts
updateContentDraft(id, data) PATCH /api/content/drafts/{id} Approve, decline, or reprompt
sendChatMessage(userId, message) POST /api/chat Send message to AI coach
getUserNewsletter(userId) GET /api/users/{id}/newsletter Fetch newsletter events

Type Definitions (lib/types.ts)

Interface Key fields
User id, name, email, balance
Trade id, ticker, action, amount, entry_price, pnl, emotional_tag, news_context
PricePoint date, open, high, low, close, volume
AnomalyResult zscore, mean, std, is_anomaly
PriceData ticker, prices[], anomaly, latest_price, change_pct
NewsArticle article_id, title, source, sentiment, ai_summary
CorrelationResult correlation_score, strength, temporal/relevance/magnitude scores, justification
ContentDraft id, ticker, platform, persona, content, status, analysis_context
BehavioralMetrics revenge_trade_probability, panic_sell_threshold, avg_loss_after_news_event, drawdown_threshold
WSEvent task_id, event_type, node, data
ChatMessage role, content, timestamp

5. Database — Supabase

Schema defined in docs/schema.sql.

Table Purpose Key Columns
users Trader profiles id (UUID), name, email, balance
trade_history All executed trades user_id (FK), ticker, action, amount, entry_price, exit_price, pnl, emotional_tag, news_context
behavioral_metrics Per-user behavioral profile user_id (FK, unique), revenge_trade_probability, panic_sell_threshold, avg_loss_after_news_event, drawdown_threshold
content_drafts Generated social media posts ticker, platform (x/linkedin), persona (buffett/trump/burry), content, status (pending/approved/declined), correlation_score, analysis_context (JSONB)

6. External APIs

Service Used by Purpose Env var
Google Gemini LangGraph nodes, Backend chat/content routers LLM reasoning and generation (via OpenAI-compatible endpoint) GEMINI_API_KEY
Supabase FastMCP user_patterns tool, Backend routers Database for users, trades, metrics, drafts SUPABASE_URL, SUPABASE_KEY
yfinance FastMCP price_data tool Historical stock price data + OHLC None (free, no key)
NewsData.io FastMCP news_search + news_deep_dive tools Market news articles with sentiment NEWSDATA_API_KEY
Alpha Vantage FastMCP get_news_sentiment + get_alpha_stock_quote tools Market news sentiment analysis and real-time stock quotes ALPHAADVANTAGE_API_KEY
Finnhub FastMCP get_finnhub_quote + get_crypto_candles tools Real-time stock quotes and crypto OHLCV candlestick data FINHNHUB_API_KEY
MediaStack FastMCP search_mediastack_news tool Global news search with keyword, category, and geographic filtering MEDIASTACK_API_KEY