A custom ReAct (Reasoning + Acting) agent for market microstructure analysis, built from scratch without LangChain, LlamaIndex, or any agent framework. The system uses a Qwen3-8B language model to reason about financial data, dispatch analytical tools, and synthesize findings into actionable insights.
+-------------------+
| Gradio UI |
| (app.py / ui/) |
+--------+----------+
|
v
+-----------+-----------+
| AgentOrchestrator |
| (ReAct Loop) |
| |
| 1. Build prompt |
| 2. Call model |
| 3. Parse output |
| 4. Dispatch tool |
| 5. Loop or answer |
+-----------+-----------+
|
+-----------------+-----------------+
| |
+---------v---------+ +---------v---------+
| ModelEngine | | ToolRegistry |
| (Qwen3-8B) | | |
+-------------------+ | fetch_market_data|
| compute_tech_ind |
| analyze_vol_prof |
| run_monte_carlo |
| fetch_econ_data |
| correlate_assets |
| generate_report |
+---------+---------+
|
+---------v---------+
| Data Layer |
| |
| TieredCache |
| RateLimiter |
| ProviderRegistry |
| (yfinance, AV, |
| FRED) |
+-------------------+
The agent follows a Thought-Action-Observation loop, limited to 8 iterations:
User: "Is AAPL overbought?"
Iteration 1:
Thought: I need RSI and Bollinger Bands for AAPL.
Action: compute_technical_indicators(ticker="AAPL", indicators=["rsi", "bollinger"])
Observation: {"rsi": {"current": 72.3, "interpretation": "Overbought"}, ...}
Iteration 2:
Thought: RSI is 72.3 (above 70) and price is near the upper Bollinger Band.
I have enough data to answer.
Answer: AAPL appears overbought. The 14-period RSI reads 72.3 (above the 70
threshold) and the price is touching the upper Bollinger Band...
Key design decisions:
- One tool call per iteration. This keeps the reasoning chain legible and makes error recovery straightforward.
- Malformed output correction. If the model produces unparseable output, the orchestrator injects a correction message and lets the model retry.
- Timeout enforcement. Each tool call has a configurable timeout (default 10 seconds) to prevent hangs from slow APIs.
This project intentionally avoids agent frameworks to demonstrate:
-
Transparency. Every line of the ReAct loop is visible and debuggable. There is no "magic" behind an
AgentExecutor-- the loop is 80 lines of straightforward Python inagent/orchestrator.py. -
Control. The prompt, parsing, and error recovery are all hand-tuned for the Qwen3 model family. Generic frameworks apply one-size-fits-all prompting that often produces worse results with smaller models.
-
Minimal dependencies. The agent layer depends only on the standard library and the model/tools it already needs. No framework lock-in.
-
Educational value. Reading this codebase should teach you exactly how tool-calling agents work, from prompt construction through output parsing to observation injection.
| Tool | Description | Key Output |
|---|---|---|
fetch_market_data |
OHLCV data via yfinance (Alpha Vantage fallback) | Dates, OHLCV arrays, metadata |
compute_technical_indicators |
RSI, MACD, Bollinger, VWAP, SMA, EMA, ATR | Current values + interpretations |
analyze_volume_profile |
Volume-at-price histogram | POC, Value Area, HVN list |
run_monte_carlo |
GBM price simulation (vectorized numpy) | Percentile paths, VaR, expected range |
fetch_economic_data |
FRED macro data (fed funds, CPI, unemployment, ...) | Time series + trend analysis |
correlate_assets |
Correlation matrix, rolling correlation, beta | Matrix, rolling series, betas |
generate_report |
Compile all results into structured Markdown | Formatted report |
- Memory tier: Python dict for sub-millisecond lookups.
- SQLite tier: Persistent storage that survives restarts.
- TTL-based expiration with stale-data fallback when upstream sources fail.
- Thread-safe via
threading.Lock.
Token-bucket algorithm with per-API configuration:
| API | Rate | Burst | Cap |
|---|---|---|---|
| yfinance | 2/s | 5 | 2000/hr |
| Alpha Vantage | 5/min | 1 | 25/day |
| FRED | 2/s | 5 | -- |
Automatic failover chain: primary provider -> secondary -> stale cache -> error.
git clone <repo-url>
cd agentic-market-analyzer
pip install -r requirements.txtCopy the environment template and add your API keys:
cp .env.example .env
# Edit .env with your keyspython app.pyUses mock model responses so you can explore the UI and tool integrations without a GPU.
python app.py --liveLoads Qwen3-8B-AWQ for real inference. Requires a CUDA-capable GPU with at least 8 GB VRAM.
--port PORT Server port (default: 7860)
--share Create a public Gradio share link
--debug Enable debug logging
- Create a new Space with the Gradio SDK and ZeroGPU hardware.
- Push this repository to the Space.
- Set secrets in the Space settings:
FRED_API_KEYALPHA_VANTAGE_API_KEYHF_TOKEN
- The app will load with
demo_mode=Falseand use@spaces.GPUfor on-demand GPU allocation.
pytest tests/ -vThe test suite covers:
- Technical indicators: RSI bounds, MACD sign in trends, Bollinger band ordering, VWAP range, SMA/EMA convergence, ATR non-negativity.
- Monte Carlo: Path start price, percentile ordering, median convergence, variance scaling, reproducibility.
- Volume Profile: POC correctness, Value Area coverage, HVN threshold.
- Agent Parser: XML tool calls, bare JSON, Action fallback, malformed JSON repair, answer detection, edge cases.
- Orchestrator: Single tool call + answer, direct answer, max iteration handling, malformed output correction, tool failure reporting.
- Cache: Set/get, TTL expiration, stale fallback, persistence, pruning, concurrent thread safety.
- Rate Limiter: Burst capacity, depletion blocking, token recovery.
-
Fail gracefully. Every tool returns a
ToolResultwith explicit success/failure status. The agent sees errors as observations and can adapt its strategy. -
Retry with backoff. Network calls use exponential backoff (max 3 retries) before failing. This handles transient errors without overwhelming APIs.
-
Stale over nothing. The cache will serve expired data when the upstream is unreachable. Slightly old market data is more useful than an error.
-
Structured errors. Error messages always include context (which provider failed, what was attempted, what the user should try).
agentic-market-analyzer/
app.py Gradio entry point
requirements.txt Pinned dependencies
.env.example Environment variable template
agent/
orchestrator.py ReAct loop engine
events.py AgentEvent dataclasses
parser.py Parse model output into tool calls
prompt_builder.py System prompt + tool schema formatting
conversation.py Message history management
model/
engine.py Model inference (Qwen3-8B or fallback)
config.py Model settings
tools/
base.py BaseTool ABC, ToolResult, ToolRegistry
market_data.py fetch_market_data
technical_indicators.py compute_technical_indicators
volume_profile.py analyze_volume_profile
monte_carlo.py run_monte_carlo (GBM simulation)
economic_data.py fetch_economic_data (FRED)
correlation.py correlate_assets
report.py generate_report
data/
cache.py Two-tier cache (memory + SQLite)
rate_limiter.py Token bucket rate limiter
providers/
base.py BaseDataProvider ABC
yfinance_provider.py yfinance wrapper
alphavantage_provider.py Alpha Vantage wrapper
fred_provider.py FRED wrapper
registry.py Provider registry with failover
ui/
components.py Gradio UI builders
formatters.py Format agent events for display
charts.py Matplotlib chart generators
tests/
conftest.py Shared fixtures
test_tools/ Indicator, Monte Carlo, volume profile tests
test_agent/ Parser and orchestrator tests
test_data/ Cache and rate limiter tests
MIT