尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
A multi-user MCP server and web dashboard for tracking your Angel One portfolio, with AI-powered daily briefings, a trading agent, and ML-based price predictions.查看完整 README ↗A multi-user MCP server and web dashboard for tracking your Angel One portfolio, with AI-powered daily briefings, a trading agent, and calibrated ML price predictions.
OPENROUTER_API_KEY on the serverservices/marketstore). Carries delivery percentage, which the broker candle API does not expose/api/admin/*) for registering premium users with Fernet-encrypted Angel credentials and managing their schedules├── main.py # Combined entry point (MCP + Web on one port)
├── mcp_server.py # MCP FastMCP app (tools: login, portfolio, trading)
├── web_app.py # FastAPI web dashboard + briefing schedule API
├── angel_client.py # Angel One SmartAPI client wrapper
├── session_manager.py # Per-session client management (in-memory)
│
├── db/ # Postgres layer (SQLModel + Neon)
│ ├── engine.py # Engine, init_db, additive column migrations
│ ├── models.py # User, Schedule, Log tables
│ ├── crypto.py # Fernet encrypt/decrypt for stored credentials
│ └── __init__.py
│
├── adminApi/ # Admin REST API (X-Admin-Key protected)
│ ├── router.py # /api/admin/users, /api/admin/schedules, /api/admin/logs
│ └── __init__.py
│
├── agents/ # Google ADK agents
│ ├── factory.py # LlmAgent builder (OpenRouter via LiteLLM)
│ ├── finance/ # Finance research agent
│ └── trading/ # Proposal-based trading agent
│
├── services/ # Domain logic
│ ├── schedular/ # Scheduler pipeline
│ │ ├── runner.py # APScheduler tick (every 1 min)
│ │ ├── repository.py # DB claim / finish (FOR UPDATE SKIP LOCKED)
│ │ ├── job_executor.py # Per-schedule pipeline (session → briefing → WhatsApp)
│ │ ├── daily_briefing.py # LLM briefing generation (holdings + candles + model)
│ │ └── whatsapp.py # WhatsApp delivery via webhook
│ ├── marketstore/ # Local NSE history + point-in-time store
│ │ ├── bhavcopy.py # One day of the whole market, normalised
│ │ ├── store.py # Parquet, partitioned by year, idempotent
│ │ ├── backfill.py # Resumable CLI backfill
│ │ ├── universe.py # 451 names with cap band and NSE industry
│ │ └── pit.py # Point-in-time fundamental snapshots
│ ├── features/ # The versioned feature contract
│ │ ├── contract.py # Ordered feature list + interval guards
│ │ ├── technical.py # Indicators, vectorised across the panel
│ │ ├── cross_sectional.py # Ranks within universe and industry
│ │ ├── market_context.py # Market and industry context
│ │ ├── flow.py # Delivery % and turnover
│ │ ├── fundamental.py # Scores read through the PIT store
│ │ └── sentiment.py # FinBERT aggregates from the news archive
│ ├── compliance/ # Mandate gate over a dated ruleset
│ ├── prediction_service.py # Registry-backed, interval-strict predictions
│ ├── decision_record.py # Append-only audit trail for trades
│ ├── peer_service.py # Valuation percentiles vs industry peers
│ ├── estimates_service.py # Consensus, revision momentum, surprises
│ ├── ownership_service.py # Delivery, deals, insiders (pluggable source)
│ ├── ai_service.py # OpenRouter AI helpers
│ ├── technical_service.py # RSI, MACD, Bollinger, SMA, volume signals
│ ├── fundamental_service.py # Fundamental data
│ ├── sentiment_service.py # News sentiment scoring
│ ├── sector_service.py # Sectoral analysis
│ ├── news_service.py # News fetching
│ ├── risk_profile.py # User risk profile
│ ├── trade_proposals.py # Proposal store + risk and compliance gates
│ └── realtime_feed.py # Real-time price feed
│
├── integrations/ # Adapters bridging sessions ↔ MCP / local accounts
├── models/ # Labelling, validation, registry, training
│ ├── labeling.py # Triple barriers, CUSUM events, weights
│ ├── validation.py # Purged walk-forward CV and metrics
│ ├── registry.py # Model + contract + metrics, versioned
│ ├── train.py # The training CLI
│ └── artifacts/ # Trained models and their reports
├── frontend/ # Jinja2 templates and static assets (CSS, JS)
├── data/ # JSON data files served under /static/data
│ ├── compliance_rules.json # Mandate ruleset (TEMPLATE — see the file)
│ ├── market/ # Parquet daily bars
│ └── pit/ # Point-in-time snapshots
├── requirements.txt # Python dependencies
├── Dockerfile # Container definition
└── .env.example # Environment variable template
One process (main.py) mounts the MCP app under /mcp and the FastAPI web app at / on a single Uvicorn port. The scheduler runs inside the same process (APScheduler async) when SCHEDULER_ENABLED=1.
flowchart TB
subgraph clients [Clients]
Browser[Web browser]
MCPClient[MCP client e.g. Cursor]
WhatsApp[WhatsApp ← user's phone]
end
subgraph server [MyFinanceMCP process]
Starlette[Starlette / Uvicorn]
Web[FastAPI web_app]
MCP[mcp_server FastMCP SSE]
Admin[adminApi router]
SM[session_manager in-memory]
FinanceADK[Finance ADK agent]
TradingADK[Trading ADK agent]
Scheduler[APScheduler tick 1 min]
AngelWrap[angel_client AngelOneClient]
end
subgraph db [Postgres / Neon]
Users[users encrypted creds]
Schedules[schedules next_run status]
Logs[logs run history]
end
subgraph external [External services]
AngelAPI[Angel One SmartAPI]
OR[OpenRouter API]
WAHook[WhatsApp webhook service]
Other[yfinance gnews NSE data etc.]
end
Browser -->|HTTPS session cookie| Starlette
MCPClient -->|Streamable HTTP /mcp/mcp + Bearer token| Starlette
Starlette --> Web
Starlette --> MCP
Web --> Admin
Web --> SM
Web --> FinanceADK
Web --> TradingADK
MCP --> SM
SM --> AngelWrap
AngelWrap --> AngelAPI
FinanceADK --> OR
TradingADK --> OR
Web --> Other
Admin --> db
Scheduler --> db
Scheduler -->|run_one| AngelWrap
Scheduler -->|send| WAHook
WAHook --> WhatsApp
| Path | Purpose |
|---|---|
/, /dashboard, /agent, … | Jinja pages + static assets |
/api/* | JSON for dashboard, news, research, /api/agent/chat, /api/agent/new-chat, /api/ai/* |
/mcp/... | MCP over SSE (login, holdings, orders, etc.) |
/docs | Swagger UI (FastAPI auto-generated) |
AngelOneClient in memory and stores an opaque sid in a signed session cookie.schedules for rows where next_run <= now() → claims them with FOR UPDATE SKIP LOCKED → for each claimed schedule: decrypts credentials from users, creates an ephemeral Angel session, generates the briefing, sends WhatsApp, writes to logs, and arms next_run.Authorization: Bearer <token>: either an OAuth 2.1 access token (claude.ai custom connectors and other OAuth clients; PKCE S256, Dynamic Client Registration, consent at /oauth/consent, tokens hashed and bound to the /mcp/mcp resource) or a personal access token created on /connect. The user is derived from the verified token and their broker session is rebuilt from their encrypted stored credentials; no credentials pass through tools.You need 4 credentials from the Angel One SmartAPI portal:
https://smartapi.angelbroking.com/signin
| Credential | How to get it |
|---|---|
| API Key | SmartAPI → My Apps → Create App → copy the API Key |
| Client ID | Your Angel One account ID (e.g. AB1234) — visible on the SmartAPI dashboard |
| PIN | The 4-digit trading PIN you use to log into Angel One |
| TOTP Secret | SmartAPI → My Profile → Enable TOTP → copy the Base32 secret key |
Note: The TOTP secret is only shown once when you enable it. Save it immediately.
python3 -m venv .venv
source .venv/bin/activate # macOS / Linux
# .venv\Scripts\activate # Windows
pip install -r requirements.txt
The scheduler and admin API require a Postgres database. The easiest option for development is a free Neon database. Tables are created automatically on first startup via init_db().
cp .env.example .env
Edit .env with your values:
| Variable | Required | Description |
|---|---|---|
ANGELONE_API_KEY | For ADK CLI only | Bootstrap credentials for local ADK agent testing |
ANGELONE_CLIENT_ID | For ADK CLI only | See above |
ANGELONE_PASSWORD | For ADK CLI only | See above |
ANGELONE_TOTP_SECRET | For ADK CLI only | See above |
OPENROUTER_API_KEY | Yes (for Agent/Trading/Briefing) | Server-side LLM key |
DATABASE_URL | Yes | Postgres connection string (e.g. Neon pooler URL with ?sslmode=require) |
ENCRYPTION_KEY | Yes | Fernet key for encrypting Angel credentials in DB. Generate with: python -m db.crypto generate |
ADMIN_API_KEY | Yes | Static key protecting all /api/admin/* endpoints |
SCHEDULER_ENABLED | Yes (for briefings) | Set to 1 to start the APScheduler tick on process startup |
SESSION_SECRET | Recommended | Signs session cookies. Random key generated on each restart if unset (invalidates existing cookies) |
ALLOWED_HOSTS | Recommended | Comma-separated hostnames the app answers to. Unset accepts any Host header, which leaves Host-header reflection open in production |
Note on two OpenRouter keys: The server
OPENROUTER_API_KEYis used by the ADK agents and the daily briefing pipeline. There is a separate optional per-browser OpenRouter key for Dashboard → AI Insights / Ask AI (/api/ai/*) — that is stored only in the browser and never sent to the server's env.
Scaling note: The ADK integration uses in-memory runners in the same process. Use a single Uvicorn worker (the default with python main.py) so chat state and Angel sessions stay consistent.
python main.py
The server starts on http://localhost:8000:
http://localhost:8000/http://localhost:8000/docshttp://localhost:8000/mcp/mcpThe app runs without a trained model. Predictions then come from a rule-based technical score, and every response and the dashboard label say so.
1. Build the market store. One HTTP call per trading day pulls the entire NSE cash market, including delivery percentage. Resumable — interrupt it freely.
python -m services.marketstore.backfill --from 2020-01-01
The delivery-enriched bhavcopy archive starts around January 2020; earlier dates
return "data not found". Check progress any time with --status.
2. Train. Walk-forward validated, calibrated, and registered with its full contract:
python models/train.py
Artefacts land in models/artifacts/<version>/ with a report.md you should
read before trusting anything. Use --sample for a fast smoke test and
--no-register to evaluate without making a model live.
What to expect. An out-of-sample AUC near 0.54 on daily Indian equities is a real result. A number far above that is almost always a leak rather than an edge, and should be investigated as a bug rather than celebrated. Shorter horizons are weaker: in the first run the 1-day and 1-week models scored ~0.50 (no signal) while 1-month scored 0.55.
On fundamentals and sentiment. Both feature blocks are forward-only. yfinance reports today's restated figures, so historical values cannot be reconstructed without look-ahead bias. Snapshots accumulate from the day capture begins — every fundamentals fetch the app makes writes one. Until the archive is deep enough, those blocks are null, and the training report says so explicitly rather than implying coverage the model did not have.
Proposed trades pass through a hard gate before reaching the broker. Rules live
in data/compliance_rules.json with an effective date and a source per rule, so
a rule change is a data edit and the ruleset in force on a past date can be
reconstructed.
The shipped file is a template. Its limits are placeholders that exist to exercise the engine. Before it governs a real trade, every limit must be read from and cited to the current SEBI master circular, the scheme information document, and the fund's own policy. The loader logs a warning while the template is in use, and every API response carries
is_template: true.
Every decision — executed, blocked or failed — is written to the append-only
decision_records table and readable at GET /api/trading/decisions.
The scheduler sends a personalised WhatsApp message to each premium user at their configured time each day. The message includes:
POST /api/briefing/schedule with {"time_ist": "08:30"}.SCHEDULER_ENABLED=1 in your environment and restart.APScheduler tick (every 1 min)
└── repository.claim_due_schedules() ← SQL: next_run <= now(), FOR UPDATE SKIP LOCKED
└── job_executor.run_one(claim)
├── Load user + decrypt credentials (Fernet)
├── Create ephemeral Angel session
├── generate_daily_briefing() ← holdings + candles + model + LLM
├── whatsapp.send() ← POST to webhook service
├── Write Log row
└── finish_schedule() ← advance next_run by interval_minutes
All endpoints require the X-Admin-Key: <your ADMIN_API_KEY> header. Browse to /docs for interactive Swagger UI.
| Endpoint | Method | Description |
|---|---|---|
/api/admin/users | GET | List all registered users |
/api/admin/users | POST | Create a user (encrypts password + TOTP before storing) |
/api/admin/users/{id} | GET | Get a single user |
/api/admin/users/{id} | DELETE | Deactivate a user |
/api/admin/schedules | GET | List all schedules |
/api/admin/schedules | POST | Create a schedule for a user |
/api/admin/schedules/{id} | GET | Get a single schedule |
/api/admin/schedules/{id} | PATCH | Update schedule (enable/disable, interval, next_run) |
/api/admin/schedules/{id} | DELETE | Delete a schedule |
/api/admin/logs | GET | Recent scheduler run logs (filterable by user_id) |
curl -X POST http://localhost:8000/api/admin/users \
-H "X-Admin-Key: $ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"whatsapp_number": "918107037133",
"angel_api_key": "",
"angel_client_id": "",
"angel_password": "",
"angel_totp_secret": ""
}'
Settings → Connectors → Add custom connector, URL https://agentfolio.in/mcp/mcp (use the apex domain; www redirects). Claude discovers the OAuth server from /.well-known/oauth-protected-resource/mcp/mcp, registers itself, and sends you to a sign-in page (WhatsApp number + app password) to approve read-only portfolio:read access. No token to paste. Connected apps can be disconnected on /connect.
OAuth needs PUBLIC_BASE_URL set to the exact public origin (e.g. https://agentfolio.in): the issuer, the endpoints and the resource that tokens are bound to are all derived from it. Optional: OAUTH_EXTRA_REDIRECT_URIS (comma-separated, exact) to allow other clients' callbacks beyond claude.ai/claude.com and loopback; OAUTH_ALLOW_LOOPBACK_REDIRECTS=0 to refuse http://localhost callbacks.
Add to your .cursor/mcp.json:
{
"mcpServers": {
"angelone-portfolio": {
"url": "http://localhost:8000/mcp/mcp"
}
}
}
Add an Authorization: Bearer <token> header using a token from the /connect page. See /connect for per-client examples.
Add to your claude_desktop_config.json:
{
"mcpServers": {
"angelone-portfolio": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:8000/mcp/mcp"]
}
}
}
Go to Settings → Beta features → MCP Servers → Add Server and enter:
npx-y mcp-remote http://localhost:8000/mcp/mcp| Tool | Description |
|---|---|
get_profile | Account profile |
get_holdings | Stock holdings with P&L |
get_all_holdings | Complete holdings overview |
get_positions | Open positions |
get_order_book | Today's orders |
get_trade_book | Executed trades |
get_funds | Account funds / margin |
get_ltp | Last traded price |
search_scrip | Search stocks by name |
portfolio_summary | High-level portfolio summary |
get_candle_data | Historical OHLC data |
get_stock_history | OHLCV history by symbol |
calculate_pnl | P&L for one symbol |
get_market_depth | Best 5 bids/asks |
predict_price_direction | Technical-signal outlook (not advice) |
All tools are read-only. There are no order placement, modification, cancellation or login tools.
DATABASE_URL (Postgres connection string)ENCRYPTION_KEYADMIN_API_KEYOPENROUTER_API_KEYSCHEDULER_ENABLED=1SESSION_SECREThttps://myfinancemcp.up.railway.app)Web users: Visit https://your-app.up.railway.app and log in with Angel One credentials.
MCP users: Update the url in your MCP client config:
{
"mcpServers": {
"angelone-portfolio": {
"url": "https://your-app.up.railway.app/mcp/mcp"
}
}
}
Note: Render free tier spins down after inactivity. The scheduler resumes when the service wakes up, but will immediately fire all overdue schedules on the next tick.
| Location | What is stored | Notes |
|---|---|---|
| Server memory | Transient AngelOneClient instances keyed by web sid or MCP connection id | Not written to DB; cleared on logout, session expiry (~8 hours), or process restart |
| Postgres DB | Premium users rows with angel_password_encrypted and angel_totp_secret_encrypted | Encrypted with Fernet (ENCRYPTION_KEY). Never returned in plaintext by any API response |
Server .env / host env | OPENROUTER_API_KEY, ENCRYPTION_KEY, ADMIN_API_KEY, DATABASE_URL, SESSION_SECRET | Protect the host and use a secrets manager in production |
| Signed session cookie | Opaque sid (web Angel session), adk_chat_session_id (ADK thread) | Signed with SESSION_SECRET. Use HTTPS in production |
| Server memory (ADK) | InMemoryRunner and ADK session/event state per logged-in user | Lost on restart. Use a single Uvicorn worker |
Browser localStorage | Optional encrypted OpenRouter key for Dashboard → AI Insights only | Not used by /agent or scheduler. Encrypted with AES-GCM using client id as key material |
| Browser (typical) | No Angel credentials after login | Sent once over HTTPS at login; session cookie identifies the server-side session |
SESSION_TTL_SECONDS in session_manager.py).FOR UPDATE SKIP LOCKED pattern in repository.py makes the scheduler safe to run across multiple replicas, though only one process should have SCHEDULER_ENABLED=1..env (file permissions, no commits) and use platform secret stores in production.localStorage — treat the device as trusted.MIT