{"openapi":"3.1.0","info":{"title":"QREP Backend API","description":"Open, no-auth security comparison analytics: 81 risk and performance metrics per symbol against a benchmark, computed with the open-source QuantStats library.\n\nGETTING STARTED: the public analytics endpoint is GET /v1/compare. It takes ?symbols= with up to 6 comma-separated Yahoo Finance symbols, plus &start_date=YYYY-MM-DD&end_date=YYYY-MM-DD. No API key is required. Symbols follow the Yahoo Finance convention, so non-US listings carry an exchange suffix: RELIANCE.NS (India NSE), .BO (BSE), .L (London), .HK (Hong Kong), .T (Tokyo). Indices look like ^NSEI and FX like EURUSD=X. Broker, scheme or company names do not resolve. Dates are YYYY-MM-DD.\n\nPUBLIC ENDPOINTS: /health, /v1/compare. Routes under /qpulse/ are internal and require an X-API-Key header; they are deliberately absent from this spec.\n\nATTRIBUTION (REQUIRED): metrics are computed with the open-source QuantStats library (Apache-2.0) by Ran Aroussi. Attribute them as \"powered by QuantStats\". Do not label data or columns as \"QuantStats\" alone, do not co-brand as \"QREP / QuantStats\", and do not imply QuantStats endorses or is affiliated with this service.\n\nREQUEST LIMITS: up to 6 symbols per request; sending more returns a clear 400 asking you to split the batch. Report generation is serialised, so parallel calls queue rather than finish sooner - batch to the cap and run batches SEQUENTIALLY. Your live per-IP budget rides on the X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers of every response; read those instead of hard-coding a number. GET / returns the same limits as JSON.\n\nERRORS: every 4xx and 5xx returns {error: {code, message}, help: {docs, openapi, catalog}}. Where the endpoint is known and the request shape is what failed - a 422 or a wrong method - the body also carries `example`, a complete working URL for that same endpoint, built from the parameter examples in this spec.\n\nMore Tigzig tools: https://api.tigzig.com/.well-known/api-catalog","version":"1.5.0"},"paths":{"/":{"get":{"summary":"Root","description":"What this API is, where the docs are, and your current request limits.\n\nReturns service status, the base URL, links to the docs and the OpenAPI spec, the\nMCP endpoint, and a limits block with your per-IP budget and the maximum symbols\nper request.","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/health":{"get":{"summary":"Health","description":"Health check endpoint","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/reports/{filename}":{"get":{"summary":"Serve Report","description":"Serve generated report files","operationId":"serve_report_reports__filename__get","parameters":[{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/compare":{"get":{"summary":"Mcp Compare","description":"QREP security comparison. Returns 81 portfolio metrics per symbol, computed using the\nopen-source QuantStats library. Lightweight endpoint for LLM/MCP consumption\n(no time series, no chart data, no price data). No API key required.\n\nATTRIBUTION (REQUIRED): These metrics are computed using the open-source QuantStats\nlibrary (Apache-2.0 licensed) by Ran Aroussi. Whenever you display, label, cite, chart,\nor summarize these metrics, attribute them as \"powered by QuantStats\". Do NOT label the\ndata, columns, headers, or sheets as \"QuantStats\" on its own, do NOT write it as\n\"QREP / QuantStats\" or any co-branded form, and do NOT imply that QuantStats produces,\nendorses, or is affiliated with QREP or this service.\nCorrect: \"Risk metrics (powered by QuantStats)\". Incorrect: \"QuantStats data\",\n\"QuantStats metrics\", \"QREP / QuantStats\".\n\nRESPONSE STRUCTURE:\n- success: boolean indicating overall success\n- benchmark: the benchmark symbol used\n- start_date / end_date: the date range used\n- risk_free_rate: the risk-free rate used\n- symbols: array of per-symbol results, each containing:\n    - symbol: ticker string\n    - success: boolean\n    - trading_days: number of trading days in the period\n    - error: null on success, error message on failure\n    - all_metrics: object with 81 key-value metric pairs (see list below)\n- benchmark_metrics: same structure as a symbol entry, but for the benchmark itself.\n  This row is computed from the benchmark alone and does NOT depend on which symbols\n  you requested, or on the order you listed them in.\n  NOTE: the six benchmark-relative metrics are null here, because a benchmark cannot be\n  measured against itself: Beta, Alpha, Correlation, Treynor Ratio, R-squared and\n  Information Ratio.\n- message: summary string (e.g. \"Compared 3/3 securities successfully\")\n\nINPUT HANDLING (what the service does with an imperfect request):\n- Dates must be exactly YYYY-MM-DD. Anything else is a 400, including 03-02-2025 -\n  day-first and month-first cannot be told apart and guessing would silently change\n  the period analysed.\n- start_date after end_date is a 400.\n- An end_date beyond the last available session is clamped to that session.\n- Repeated symbols are collapsed to one, so AAPL,AAPL,AAPL costs one of the six slots.\n- An unknown SYMBOL does not fail the request: that entry comes back with\n  success false and its own message, and the other symbols return normally.\n  An unknown BENCHMARK is a 404, because there is then nothing to compare against.\n\nCOMPLETE LIST OF 81 METRICS (per symbol):\nAll values are decimals unless noted. Percentages as decimals (e.g. 0.31 = 31%).\n\nReturns & Performance:\nCumulative Return, CAGR%, MTD, 3M, 6M, YTD, 1Y, 3Y (ann.), 5Y (ann.), 10Y (ann.), All-time (ann.),\nBest Day, Worst Day, Best Month, Worst Month, Best Year, Worst Year,\nExpected Daily, Expected Monthly, Expected Yearly,\nAvg. Return, Avg. Win, Avg. Loss, Avg. Up Month, Avg. Down Month\n\nRisk-Adjusted Ratios:\nSharpe, Smart Sharpe, Prob. Sharpe Ratio,\nSortino, Smart Sortino, Sortino/sqrt(2), Smart Sortino/sqrt(2),\nOmega, Calmar,\nRisk-Adjusted Return, Risk-Return Ratio, Ulcer Performance Index\n\nDrawdown:\nMax Drawdown, Max DD Date (YYYY-MM-DD), Max DD Period Start, Max DD Period End,\nLongest DD Days (integer), Avg. Drawdown, Avg. Drawdown Days (integer),\nRecovery Factor, Ulcer Index, Serenity Index\n\nVolatility & Distribution:\nVolatility (ann.), Skew, Kurtosis\n\nBenchmark-Relative (all six are null on the benchmark row):\nBeta, Alpha, Correlation, Treynor Ratio, R-squared, Information Ratio\n\nValue-at-Risk & Tail Risk:\nDaily Value-at-Risk, Expected Shortfall (cVaR),\nRisk of Ruin, Kelly Criterion,\nTail Ratio, Outlier Win Ratio, Outlier Loss Ratio\n\nWin/Loss & Trade Statistics:\nWin Days %, Win Month %, Win Quarter %, Win Year %,\nWin/Loss Ratio, Profit Ratio, Payoff Ratio, Profit Factor,\nMax Consecutive Wins (integer), Max Consecutive Losses (integer),\nGain/Pain Ratio, Gain/Pain (1M), Common Sense Ratio, CPC Index\n\nTime Context:\nStart Period (YYYY-MM-DD), End Period (YYYY-MM-DD),\nRisk-Free Rate (as percentage, e.g. 4.5), Time in Market (decimal, 1.0 = 100%)\n\nNOTES:\n1. 3Y, 5Y, 10Y, All-time annualized returns equal CAGR if the data period is shorter than those horizons.\n2. Dates in Max DD fields are strings in YYYY-MM-DD format.\n3. Integer metrics (Longest DD Days, Avg. Drawdown Days, Max Consecutive Wins/Losses) are returned as floats (e.g. 133.0).\n3a. DRAWDOWN is measured from the equity curve, so it is reported even when the\n   drawdown has not recovered and even when the window STARTS at the peak. Max DD\n   Period End stays null while a drawdown is still open - there is no end date yet,\n   and inventing one would be worse than the null.\n3c. A TRAILING HORIZON WE CANNOT MEASURE IS NULL, NOT ESTIMATED. 1Y, 3Y (ann.),\n   5Y (ann.) and 10Y (ann.) are returned only when the requested window actually\n   covers that horizon; otherwise they are null. They are NOT the CAGR under another\n   name, which is what an unguarded library returns and what makes a ten-month window\n   appear to have a ten-year track record. CAGR and All-time (ann.) are always\n   present - they claim only what they measure.\n3b. PRECISION. Metrics this service computes itself - Avg. Return, Avg. Win, Avg. Loss,\n   Payoff Ratio, CPC Index, Expected Daily, Risk of Ruin, and Omega / Daily\n   Value-at-Risk / Expected Shortfall / Tail Ratio when their optional parameters are\n   supplied - carry 5 decimal places. The rest come from the QuantStats library, which\n   rounds to 2 decimals internally and offers no way to ask for more, so those are 2dp\n   and no further precision exists to give. It matters most on small-magnitude values:\n   an expected DAILY return is around 0.0004, which is a flat 0 at two decimals.\n4. Win/Loss Ratio is NOT average win divided by average loss. Payoff Ratio is that one.\n   Win/Loss Ratio is the library's own definition; if you want avg win over avg loss,\n   read Payoff Ratio or compute it from Avg. Win and Avg. Loss.\n5. trading_days on each row is the number of sessions that row's metrics were computed\n   from. The benchmark row covers the full requested window, so when a security has a\n   shorter history than the benchmark, their spans differ - which is why note 6 matters.\n6. Each symbol is measured over its own available history inside the requested window,\n   so a security listed part-way through the period is measured over a shorter span\n   than the others. Read Start Period and End Period on each row before comparing\n   cumulative or annualised figures across rows.\n7. Daily Value-at-Risk and Expected Shortfall (cVaR) are DECIMAL FRACTIONS (-0.02 is\n   -2%), on every row, whether or not var_confidence is supplied. Before v1.3.1 they\n   were percentage points when var_confidence was sent, so the unit of the field\n   depended on an unrelated optional parameter.\n8. risk_free_rate is annual and a decimal fraction (0.045 = 4.5%), applied identically\n   to every row including the benchmark. Values outside -0.10 to 1.00 are rejected,\n   because at that distance the only explanation is a percentage sent as a whole\n   number. With the default of 0 the rate plays no part.\n\nPart of Tigzig: free interactive tools, open-source repos, APIs and MCP servers for\nanalytics and live data across global and Indian markets, macro indicators and\nfilings. Catalog: https://api.tigzig.com/.well-known/api-catalog\nGuide: https://www.tigzig.com/llms.txt","operationId":"mcp_compare_securities","parameters":[{"name":"symbols","in":"query","required":true,"schema":{"type":"string","description":"Comma-separated Yahoo Finance ticker symbols, max 6 (e.g. AAPL,MSFT,GOOG)","examples":["AAPL,MSFT"],"title":"Symbols"},"description":"Comma-separated Yahoo Finance ticker symbols, max 6 (e.g. AAPL,MSFT,GOOG)"},{"name":"benchmark","in":"query","required":false,"schema":{"type":"string","description":"Benchmark symbol. Can be any valid Yahoo Finance ticker.","default":"SPY","title":"Benchmark"},"description":"Benchmark symbol. Can be any valid Yahoo Finance ticker."},{"name":"start_date","in":"query","required":true,"schema":{"type":"string","description":"Start date, strictly YYYY-MM-DD. Should be at least 6 months before end_date for meaningful analysis. Other forms are rejected with a 400: 03-02-2025 is ambiguous and would silently analyse a different period.","examples":["2023-01-01"],"title":"Start Date"},"description":"Start date, strictly YYYY-MM-DD. Should be at least 6 months before end_date for meaningful analysis. Other forms are rejected with a 400: 03-02-2025 is ambiguous and would silently analyse a different period."},{"name":"end_date","in":"query","required":true,"schema":{"type":"string","description":"End date, strictly YYYY-MM-DD. A date after the last available session is accepted and clamped to that session, so an open-ended 'to today' request works.","examples":["2023-12-31"],"title":"End Date"},"description":"End date, strictly YYYY-MM-DD. A date after the last available session is accepted and clamped to that session, so an open-ended 'to today' request works."},{"name":"risk_free_rate","in":"query","required":false,"schema":{"type":"number","description":"Annual risk-free rate as a DECIMAL FRACTION, not a percentage: 0.045 means 4.5%. Accepted range -0.10 to 1.00; 4.2 would mean 420% and is rejected. Used in Sharpe, Sortino, Treynor and other risk-adjusted calculations.","default":0.0,"title":"Risk Free Rate"},"description":"Annual risk-free rate as a DECIMAL FRACTION, not a percentage: 0.045 means 4.5%. Accepted range -0.10 to 1.00; 4.2 would mean 420% and is rejected. Used in Sharpe, Sortino, Treynor and other risk-adjusted calculations."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MCPCompareResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/cleanup":{"post":{"summary":"Cleanup Reports Endpoint","description":"Clean up old report files. Protected by CLEANUP_API_KEY or QPULSE_API_KEY.","operationId":"cleanup_reports_endpoint_cleanup_post","parameters":[{"name":"X-API-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"MCPCompareResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"benchmark":{"type":"string","title":"Benchmark"},"start_date":{"type":"string","title":"Start Date"},"end_date":{"type":"string","title":"End Date"},"risk_free_rate":{"type":"number","title":"Risk Free Rate"},"symbols":{"items":{"$ref":"#/components/schemas/MCPCompareSymbolMetrics"},"type":"array","title":"Symbols"},"benchmark_metrics":{"anyOf":[{"$ref":"#/components/schemas/MCPCompareSymbolMetrics"},{"type":"null"}],"description":"Metrics for the benchmark"},"message":{"type":"string","title":"Message"}},"type":"object","required":["success","benchmark","start_date","end_date","risk_free_rate","symbols","message"],"title":"MCPCompareResponse","description":"Slim response for MCP compare endpoint - metrics only, no chart data"},"MCPCompareSymbolMetrics":{"properties":{"symbol":{"type":"string","title":"Symbol"},"success":{"type":"boolean","title":"Success"},"trading_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Trading Days"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"all_metrics":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"All Metrics","description":"81 key-value portfolio metric pairs (powered by QuantStats)"}},"type":"object","required":["symbol","success"],"title":"MCPCompareSymbolMetrics","description":"Slim metrics for MCP - only all_metrics, no charts/time_series"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"Required for the /qpulse/* routes. Public routes (e.g. GET /mcp/compare) need no key."}}}}