{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Build a risk-analyst LangChain agent in 25 lines\n", "\n", "**Problem:** LLMs are unreliable at *computing* finance math. Ask one to price a Black-Scholes call and the answer drifts run to run. Ask for the higher-order Greeks and they're frequently wrong.\n", "\n", "**Fix:** keep the LLM for reasoning, hand the math to a deterministic API.\n", "\n", "This notebook shows a LangChain agent that answers risk and portfolio questions using [QuantOracle](https://api.quantoracle.dev) — 63 deterministic quant calculators + 10 composite workflows, all citation-verified against Hull / Wilmott / Bailey & Lopez de Prado.\n", "\n", "- 1,000 free calls per IP per day, no signup, no API key\n", "- Paid composites via [x402](https://x402.org) USDC micropayments on Base or Solana\n", "- Cataloged in [CDP Bazaar](https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources) for agent discovery" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Install" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2026-04-25T01:30:05.229585Z", "iopub.status.busy": "2026-04-25T01:30:05.228754Z", "iopub.status.idle": "2026-04-25T01:30:06.573257Z", "shell.execute_reply": "2026-04-25T01:30:06.572440Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Note: you may need to restart the kernel to use updated packages.\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n", "[notice] A new release of pip is available: 25.3 -> 26.0.1\n", "[notice] To update, run: D:\\Quantcalc\\venv\\Scripts\\python.exe -m pip install --upgrade pip\n" ] } ], "source": [ "%pip install --quiet langchain-quantoracle langchain-openai langgraph" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2026-04-25T01:30:06.603333Z", "iopub.status.busy": "2026-04-25T01:30:06.603154Z", "iopub.status.idle": "2026-04-25T01:30:06.609588Z", "shell.execute_reply": "2026-04-25T01:30:06.606613Z" } }, "outputs": [], "source": [ "import os\n", "# Set OPENAI_API_KEY in your environment, or uncomment + paste below:\n", "# os.environ[\"OPENAI_API_KEY\"] = \"sk-...\"\n", "assert os.environ.get(\"OPENAI_API_KEY\"), \"Set OPENAI_API_KEY first\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Load just the risk + portfolio + stats tools\n", "\n", "QuantOracle exposes 73 endpoints. Loading all of them into an agent prompt confuses smaller models. Filter by category to keep the tool list focused." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2026-04-25T01:30:06.615430Z", "iopub.status.busy": "2026-04-25T01:30:06.614976Z", "iopub.status.idle": "2026-04-25T01:30:07.819302Z", "shell.execute_reply": "2026-04-25T01:30:07.818138Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "D:\\Quantcalc\\venv\\Lib\\site-packages\\langchain_core\\_api\\deprecation.py:25: UserWarning: Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater.\n", " from pydantic.v1.fields import FieldInfo as FieldInfoV1\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "Loaded 25 tools\n", " • risk_portfolio\n", " • risk_kelly\n", " • risk_correlation\n", " • risk_position_size\n", " • risk_drawdown\n", " ...\n" ] } ], "source": [ "from langchain_quantoracle import QuantOracleToolkit\n", "\n", "tools = QuantOracleToolkit(categories=[\"risk\", \"portfolio\", \"stats\"]).get_tools()\n", "print(f\"Loaded {len(tools)} tools\")\n", "for t in tools[:5]:\n", " print(f\" • {t.name}\")\n", "print(\" ...\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Build the agent" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2026-04-25T01:30:07.821275Z", "iopub.status.busy": "2026-04-25T01:30:07.821124Z", "iopub.status.idle": "2026-04-25T01:30:09.887701Z", "shell.execute_reply": "2026-04-25T01:30:09.886140Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "C:\\Users\\sfelc\\AppData\\Local\\Temp\\ipykernel_49012\\1686872763.py:13: LangGraphDeprecatedSinceV10: create_react_agent has been moved to `langchain.agents`. Please update your import to `from langchain.agents import create_agent`. Deprecated in LangGraph V1.0 to be removed in V2.0.\n", " agent = create_react_agent(llm, tools, prompt=SYSTEM)\n" ] } ], "source": [ "from langchain_openai import ChatOpenAI\n", "from langgraph.prebuilt import create_react_agent\n", "\n", "llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n", "\n", "SYSTEM = (\n", " \"You are a quantitative analyst. For ANY financial calculation — Sharpe, \"\n", " \"VaR, drawdown, Kelly, position sizing, regression — you MUST call a \"\n", " \"QuantOracle tool. Never compute math in your head. After tools return, \"\n", " \"explain the result in plain English and recommend an action.\"\n", ")\n", "\n", "agent = create_react_agent(llm, tools, prompt=SYSTEM)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Ask a risk question with real returns\n", "\n", "These are 20 days of made-up daily returns. The agent should pick `risk_portfolio` (a free tool that bundles Sharpe, Sortino, Calmar, VaR, CVaR, drawdown, win rate, fat-tail check) and contextualize the result." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2026-04-25T01:30:09.889764Z", "iopub.status.busy": "2026-04-25T01:30:09.889606Z", "iopub.status.idle": "2026-04-25T01:30:16.786129Z", "shell.execute_reply": "2026-04-25T01:30:16.782859Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Your Sharpe ratio is approximately **2.15**, which indicates that you are generating a good return relative to the risk you are taking. The maximum drawdown is **-2.2%**, meaning that at one point, your portfolio value dropped by that percentage from its peak.\n", "\n", "**Advice:** Your risk-adjusted returns are strong, but keep an eye on the drawdown; consider setting stop-loss orders to protect against larger losses.\n" ] } ], "source": [ "returns = [\n", " 0.012, -0.008, 0.015, -0.022, 0.018, 0.005, -0.011, 0.014, -0.003, 0.009,\n", " -0.018, 0.020, -0.007, 0.012, 0.003, -0.013, 0.016, -0.005, 0.011, -0.009,\n", "]\n", "\n", "result = agent.invoke({\n", " \"messages\": [{\n", " \"role\": \"user\",\n", " \"content\": (\n", " f\"Here are my last 20 daily returns: {returns}. \"\n", " \"Am I taking too much risk for the return I'm getting? \"\n", " \"Give me Sharpe, max drawdown, and one sentence of advice.\"\n", " ),\n", " }]\n", "})\n", "\n", "print(result[\"messages\"][-1].content)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Behind the scenes that one call returned (real API output, deterministic):\n", "\n", "```json\n", "{\n", " \"returns\": {\"annualized\": 0.4914, \"vol\": 0.2052, \"win_rate\": 0.55, \"profit_factor\": 1.4062},\n", " \"risk\": {\"sharpe\": 2.151, \"sortino\": 3.3664, \"calmar\": 22.3364, \"max_drawdown\": -0.022,\n", " \"var_95\": -0.022, \"cvar_95\": -0.022},\n", " \"distribution\": {\"skewness\": -0.2911, \"excess_kurtosis\": -1.2361, \"fat_tails\": false},\n", " \"n\": 20, \"ms\": 18.5\n", "}\n", "```\n", "\n", "Same input → same output, every time. The LLM doesn't *do* the math — it interprets the result." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. Position sizing with Kelly criterion" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2026-04-25T01:30:16.793006Z", "iopub.status.busy": "2026-04-25T01:30:16.792521Z", "iopub.status.idle": "2026-04-25T01:30:29.391492Z", "shell.execute_reply": "2026-04-25T01:30:29.390619Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Based on your strategy with a 55% win rate, an average winner of $1.50, and an average loser of $1.00, here are the recommended fractions of your account to risk per trade:\n", "\n", "1. **Full Kelly**: 25% of your account\n", "2. **Half Kelly**: 12.5% of your account\n", "3. **Quarter Kelly**: 6.25% of your account\n", "\n", "### Explanation:\n", "- **Full Kelly** is the maximum amount you can risk based on your edge and payoff ratio. In this case, it suggests risking 25% of your account on each trade.\n", "- **Half Kelly** is a more conservative approach, suggesting you risk 12.5% of your account, which can help mitigate risk while still taking advantage of your strategy's edge.\n", "- **Quarter Kelly** is even more conservative, recommending a risk of 6.25% of your account. This is often recommended for those who want to minimize volatility and drawdowns.\n", "\n", "### Recommendation:\n", "I recommend using the **Quarter Kelly** strategy, risking 6.25% of your account per trade. This approach balances potential growth with risk management, allowing you to stay in the game longer while minimizing the impact of potential losses.\n" ] } ], "source": [ "result = agent.invoke({\n", " \"messages\": [{\n", " \"role\": \"user\",\n", " \"content\": (\n", " \"I have a strategy with a 55% win rate. Average winner: $1.50. \"\n", " \"Average loser: $1.00. What fraction of my account should each \"\n", " \"trade risk? Give me full Kelly, half-Kelly, and quarter-Kelly.\"\n", " ),\n", " }]\n", "})\n", "\n", "print(result[\"messages\"][-1].content)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7. Browse the other composites\n", "\n", "The same pattern works for the rest of QuantOracle's composites — each bundles 5–15 calculator calls into one round trip with a purpose-built output:\n", "\n", "- `backtest_strategy` — full SMA / RSI / momentum backtest with equity curve + buy-hold benchmark ($0.10)\n", "- `portfolio_rebalance-plan` — generate the trade list to reach target weights with cost estimate ($0.05)\n", "- `options_strategy-optimizer` — rank top option strategies given your outlook + vol view ($0.08)\n", "- `risk_full-analysis` — full tearsheet from a returns array ($0.04)\n", "- `trade_evaluate`, `portfolio_health`, `pairs_signal`, `options_spread-scan`, `indicators_regime-classify`\n", "\n", "Just include the matching category in the toolkit (`backtest`, `portfolio`, `options`, `risk`, `trade`, `pairs`, `indicators`) and the agent picks the right tool at runtime. Every paid call settles on-chain via x402 — every settlement is a permanent audit record." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. Composite workflows: when one tool replaces ten\n", "\n", "QuantOracle has 10 **composite** endpoints that bundle 5–15 calculator calls into a single round trip with a purpose-built output. They're paid-only via x402 (USDC on Base or Solana).\n", "\n", "Examples:\n", "- `backtest_strategy` — full SMA / RSI / momentum backtest with equity curve + buy-hold benchmark ($0.10)\n", "- `portfolio_rebalance-plan` — generate the trade list to reach target weights with cost estimate ($0.05)\n", "- `options_strategy-optimizer` — rank top option strategies given your outlook + vol view ($0.08)\n", "- `hedging_recommend` — compare protective puts, collars, inverse hedges ($0.04)\n", "- `risk_full-analysis` — full tearsheet from a returns array ($0.04)\n", "\n", "If you're using an x402-capable HTTP client (e.g. [AgentCash](https://agentcash.dev), Coinbase AgentKit), payment is automatic — your agent gets a 402, signs an EIP-3009 USDC transfer, and retries. Otherwise the toolkit raises an `HTTPError` and you can layer your own payment flow.\n", "\n", "```python\n", "# Example — same pattern, just include the composite categories\n", "tools = QuantOracleToolkit(\n", " categories=[\"risk\", \"backtest\", \"portfolio\", \"hedging\"]\n", ").get_tools()\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What this pattern buys you\n", "\n", "| | LLM-only | LLM + QuantOracle |\n", "|---|---|---|\n", "| Reproducibility | drifts run-to-run | deterministic |\n", "| Higher-order Greeks (vanna, charm, speed) | frequently wrong | exact |\n", "| Iterative numerics (IV solver, GARCH, optimizer) | degrades | converges |\n", "| Cost per call | LLM tokens (~$0.02–0.10) | $0 free tier, then $0.002–$0.10 |\n", "| Audit trail | none | every call hashable + replayable |\n", "\n", "When an agent makes 50 tool calls during a backtest, every calculation has to be right. The pattern — **LLM for reasoning, deterministic API for compute** — is what actually works in production." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Links\n", "\n", "- API + docs: \n", "- Tool catalog: \n", "- GitHub: \n", "- PyPI: [`langchain-quantoracle`](https://pypi.org/project/langchain-quantoracle/)\n", "- MCP server: `npx quantoracle-mcp` ([npm](https://www.npmjs.com/package/quantoracle-mcp))\n", "- CDP Bazaar listings: (filter `resource` for `quantoracle.dev`)\n", "- x402 discovery feed: " ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.14.3" } }, "nbformat": 4, "nbformat_minor": 4 }