Architecture¶
Project Structure¶
fin-toolkit/
providers/ # Data & search sources (protocols + implementations)
protocol.py # DataProvider protocol (get_prices, get_financials, get_metrics)
search_protocol.py# SearchProvider protocol (search)
router.py # ProviderRouter: market mapping + fallback chain
search_router.py # SearchRouter: fallback chain for search
yahoo.py # Yahoo Finance (free, no API key)
kase.py # KASE JSON API + Yahoo multi-suffix + dynamic tickers
moex.py # MOEX via aiomoex
smartlab.py # SmartLab fundamentals + IFRS (scraper)
stockanalysis.py # StockAnalysis.com KASE ratios (scraper, KZT-consistent)
financialdatasets.py # Financial Datasets REST API
edgar.py # SEC EDGAR filings via edgartools
pdf_report.py # PDF report parser (IFRS/MSFO)
duckduckgo.py # DuckDuckGo (free, no API key)
searxng.py # SearXNG (self-hosted search)
google.py # Google Search via Gemini API grounding
perplexity.py # Perplexity Sonar API
tavily.py # Tavily Search API
brave.py # Brave Search API
serper.py # Serper (Google Search) API
exa.py # Exa AI semantic search
analysis/ # Analysis engines
technical.py # RSI, EMA, Bollinger Bands, MACD
fundamental.py # Profitability, valuation, stability ratios
risk.py # Volatility, VaR, correlation
portfolio.py # Consensus, position sizing, stop-loss
screening.py # Quick valuation scoring + custom filters
idea.py # Investment idea: scenarios, catalysts, FCF
comparison.py # Stock comparison matrix
alerts.py # Alert evaluation + AlertRule/WatchlistEntry
agents/ # Analysis agents (protocol + implementations)
protocol.py # AnalysisAgent protocol (analyze -> AgentResult)
elvis.py # Elvis Marlamov
buffett.py # Warren Buffett
graham.py # Ben Graham
munger.py # Charlie Munger
wood.py # Cathie Wood
lynch.py # Peter Lynch
registry.py # AgentRegistry: loads agents from config
models/ # Pydantic models
price_data.py # PricePoint, PriceData
financial.py # FinancialStatements, KeyMetrics
portfolio.py # Transaction, Position, PortfolioSummary
results.py # TechnicalResult, FundamentalResult, RiskResult, AgentResult
config/ # Configuration
models.py # ToolkitConfig, DataConfig, SearchConfig
loader.py # YAML + env + defaults loader
mcp_server/ # FastMCP server
server.py # MCP tools (20 tools)
serialize.py # TOON/JSON serialization
report/ # Report generation
html_report.py # Interactive HTML reports with Plotly charts
i18n.py # Bilingual translations, currency helpers
narrative.py # Template-based thesis/FCF/target narratives
watchlist.py # YAML-backed persistent watchlist store
portfolio_store.py # SQLite-backed portfolio store
cli.py # CLI entry point (serve, setup, status)
Protocol-First Design¶
All major boundaries are typing.Protocol classes with @runtime_checkable. New providers and agents implement the protocol — no base class inheritance needed.
@runtime_checkable
class DataProvider(Protocol):
async def get_prices(self, ticker: str, start: date, end: date) -> PriceData: ...
async def get_financials(self, ticker: str) -> FinancialStatements | None: ...
async def get_metrics(self, ticker: str) -> KeyMetrics | None: ...
@runtime_checkable
class AnalysisAgent(Protocol):
name: str
async def analyze(
self, ticker: str, fundamentals: FundamentalResult,
risk: RiskResult, prices: PriceData
) -> AgentResult: ...
Routing & Fallback¶
Request → ProviderRouter
│
├── Explicit provider? → use it
├── Static market mapping? → use configured provider
├── Dynamic tickers? → check providers with list_tickers()
├── Primary provider → try first
└── Fallback chain → iterate until success
└── AllProvidersFailedError
Dynamic tickers are fetched lazily on first request (e.g., KASE's 87 actively traded shares) and cached. This allows new listings to be discovered automatically without config changes.
Same pattern for SearchRouter with the search fallback chain.
MCP Server Wiring¶
mcp_server/server.py uses module-level globals initialized by init_server(). The CLI builds all dependencies and passes them before calling server.run().
cli.py
└── builds providers, analyzers, registry, stores
└── init_server(router, search, technical, fundamental, risk, registry, watchlist, portfolio)
└── server.run()
Exception Hierarchy¶
All exceptions inherit from FinToolkitError:
FinToolkitError
├── TickerNotFoundError
├── ProviderUnavailableError
├── AllProvidersFailedError
├── InsufficientDataError
├── AgentNotFoundError
├── InvalidFilterError
├── WatchlistError
└── PortfolioError
Each exception has a .hint property with actionable guidance, used in MCP error responses.