wiz-trader 0.37.0__py3-none-any.whl → 0.39.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wiz_trader/apis/client.py CHANGED
@@ -226,7 +226,68 @@ class WizzerClient:
226
226
  "kv.delete": "/kv/{strategy_id}/{key}",
227
227
  "kv.list": "/kv/{strategy_id}",
228
228
  "kv.keys": "/kv/{strategy_id}/keys",
229
- "kv.delete_all": "/kv/{strategy_id}/all"
229
+ "kv.delete_all": "/kv/{strategy_id}/all",
230
+
231
+ # Analytics API endpoints - Fundamentals
232
+ "analytics.fundamentals.net_profit_margin": "/analytics/fundamentals/margins/netProfit",
233
+ "analytics.fundamentals.roe": "/analytics/fundamentals/roe",
234
+ "analytics.fundamentals.roa": "/analytics/fundamentals/roa",
235
+ "analytics.fundamentals.ebit_margin": "/analytics/fundamentals/ebit-margin",
236
+ "analytics.fundamentals.ocf_netprofit_ratio": "/analytics/fundamentals/ocf-netprofit-ratio",
237
+ "analytics.fundamentals.eps_cagr": "/analytics/fundamentals/eps-cagr",
238
+ "analytics.fundamentals.book_to_market": "/analytics/fundamentals/valuation/book-to-market",
239
+ "analytics.fundamentals.marketcap_to_sales": "/analytics/fundamentals/valuation/marketcap-to-sales",
240
+ "analytics.fundamentals.cash_to_marketcap": "/analytics/fundamentals/liquidity/cash-to-marketcap",
241
+
242
+ # Analytics API endpoints - Valuation
243
+ "analytics.valuation.pe_ratio": "/analytics/valuation/pe-ratio",
244
+ "analytics.valuation.pb_ratio": "/analytics/valuation/pb-ratio",
245
+ "analytics.valuation.ev_ebitda": "/analytics/valuation/ev-ebitda",
246
+ "analytics.valuation.fcf_yield": "/analytics/valuation/fcf-yield",
247
+
248
+ # Analytics API endpoints - Returns
249
+ "analytics.returns.quarterly": "/analytics/returns/quarterly",
250
+ "analytics.returns.monthly": "/analytics/returns/monthly",
251
+ "analytics.returns.cagr": "/analytics/returns/cagr",
252
+
253
+ # Analytics API endpoints - Market Data
254
+ "analytics.marketdata.ohlcv_daily": "/analytics/marketdata/ohlcv-daily",
255
+ "analytics.marketdata.historical_prices": "/analytics/marketdata/historical-prices",
256
+ "analytics.marketdata.free_float_market_cap": "/analytics/marketdata/free-float-market-cap",
257
+ "analytics.marketdata.index_ohlc_daily": "/analytics/marketdata/index-ohlc-daily",
258
+
259
+ # Analytics API endpoints - Ownership
260
+ "analytics.ownership.fii_dii": "/analytics/ownership/fii-dii",
261
+ "analytics.ownership.fii_change": "/analytics/ownership/fii-change",
262
+ "analytics.ownership.dii_change": "/analytics/ownership/dii-change",
263
+
264
+ # Analytics API endpoints - Metrics
265
+ "analytics.metrics.sortino_ratio": "/analytics/metrics/sortino-ratio",
266
+ "analytics.metrics.upside_capture": "/analytics/metrics/upside-capture",
267
+
268
+ # Analytics API endpoints - Macro
269
+ "analytics.macro.risk_free_rate": "/analytics/macro/rates/risk-free",
270
+
271
+ # Analytics API endpoints - Risk
272
+ "analytics.risk.max_drawdown": "/analytics/risk/maxDrawdown",
273
+ "analytics.risk.returns_volatility": "/analytics/risk/returnsVolatility",
274
+
275
+ # Analytics API endpoints - Metadata
276
+ "analytics.metadata.sector": "/analytics/metadata/sector",
277
+
278
+ # Analytics API endpoints - Leverage
279
+ "analytics.leverage.debt_equity_ratio": "/analytics/leverage/debtEquityRatio",
280
+
281
+ # Analytics API endpoints - New Additions
282
+ "analytics.marketdata.average_volume": "/analytics/marketdata/averageVolume",
283
+ "analytics.index.max_drawdown": "/analytics/index/metrics/maxDrawdown",
284
+ "analytics.instrument.drawdown_duration": "/analytics/instrument/metrics/drawdownDuration",
285
+ "analytics.price.rolling_peak": "/analytics/analytics/price/rollingPeak",
286
+ "analytics.price.rolling_mean": "/analytics/analytics/price/rollingMean",
287
+ "analytics.volatility.realized": "/analytics/analytics/volatility/realized",
288
+ "analytics.risk.beta_90d": "/analytics/risk/beta90d",
289
+ "analytics.risk.beta_custom": "/analytics/risk/beta",
290
+
230
291
  }
231
292
 
232
293
  def __init__(
@@ -1768,11 +1829,37 @@ class WizzerClient:
1768
1829
  logger.debug("Fetching screener fields.")
1769
1830
  return self._make_request("GET", endpoint)
1770
1831
 
1832
+ def _normalize_params(self, params: Optional[Dict[str, Any]]) -> Optional[Dict[str, str]]:
1833
+ """
1834
+ Normalize parameters for HTTP requests, converting booleans to lowercase strings.
1835
+ Preserves spaces in string values to prevent automatic URL encoding by requests library.
1836
+
1837
+ Args:
1838
+ params (Optional[Dict[str, Any]]): Raw parameters dictionary.
1839
+
1840
+ Returns:
1841
+ Optional[Dict[str, str]]: Normalized parameters with proper string formatting.
1842
+ """
1843
+ if not params:
1844
+ return None
1845
+
1846
+ normalized = {}
1847
+ for key, value in params.items():
1848
+ if isinstance(value, bool):
1849
+ # Convert Python boolean to lowercase string for API compatibility
1850
+ normalized[key] = "true" if value else "false"
1851
+ elif value is not None:
1852
+ # Convert other values to strings, preserving spaces
1853
+ # This prevents requests library from automatically URL-encoding spaces to '+'
1854
+ normalized[key] = str(value)
1855
+
1856
+ return normalized
1857
+
1771
1858
  def _make_request(
1772
1859
  self,
1773
1860
  method: str,
1774
1861
  endpoint: str,
1775
- params: Optional[Dict[str, str]] = None,
1862
+ params: Optional[Dict[str, Any]] = None,
1776
1863
  json: Optional[Dict[str, Any]] = None,
1777
1864
  headers: Optional[Dict[str, str]] = None
1778
1865
  ) -> Any:
@@ -1782,7 +1869,7 @@ class WizzerClient:
1782
1869
  Args:
1783
1870
  method (str): HTTP method (GET, POST, etc.)
1784
1871
  endpoint (str): API endpoint path.
1785
- params (Optional[Dict[str, str]]): Query parameters for GET requests.
1872
+ params (Optional[Dict[str, Any]]): Query parameters for GET requests.
1786
1873
  json (Optional[Dict[str, Any]]): JSON payload for POST requests.
1787
1874
  headers (Optional[Dict[str, str]]): Custom headers to override the defaults.
1788
1875
 
@@ -1792,16 +1879,35 @@ class WizzerClient:
1792
1879
  Raises:
1793
1880
  requests.RequestException: If the request fails.
1794
1881
  """
1882
+ import urllib.parse
1883
+
1795
1884
  url = f"{self.base_url}{endpoint}"
1796
1885
  request_headers = headers if headers else self.headers
1797
1886
 
1887
+ # Normalize parameters to handle booleans correctly
1888
+ normalized_params = self._normalize_params(params)
1889
+
1890
+ # Handle URL construction manually to encode spaces as %20 instead of +
1891
+ if normalized_params and method.upper() == 'GET':
1892
+ # Construct query string manually to control encoding
1893
+ query_parts = []
1894
+ for key, value in normalized_params.items():
1895
+ # Use urllib.parse.quote to encode values, spaces become %20 instead of +
1896
+ encoded_key = urllib.parse.quote(str(key), safe='')
1897
+ encoded_value = urllib.parse.quote(str(value), safe='') # Properly encode all special chars
1898
+ query_parts.append(f"{encoded_key}={encoded_value}")
1899
+
1900
+ if query_parts:
1901
+ url = f"{url}?{'&'.join(query_parts)}"
1902
+ normalized_params = None # Don't pass params to requests since we built the URL manually
1903
+
1798
1904
  try:
1799
1905
  logger.debug("%s request to %s", method, url)
1800
1906
  response = requests.request(
1801
1907
  method=method,
1802
1908
  url=url,
1803
1909
  headers=request_headers,
1804
- params=params,
1910
+ params=normalized_params,
1805
1911
  json=json
1806
1912
  )
1807
1913
  response.raise_for_status()
@@ -2234,3 +2340,1538 @@ class WizzerClient:
2234
2340
 
2235
2341
  logger.debug("Deleting all KV pairs for strategy: %s", strategy_info["id"])
2236
2342
  return self._make_request("DELETE", endpoint)
2343
+
2344
+ # ===== ANALYTICS API METHODS =====
2345
+
2346
+ # --- Fundamentals Methods ---
2347
+
2348
+ def get_net_profit_margin(
2349
+ self,
2350
+ symbol: str,
2351
+ period: str = "quarterly",
2352
+ fiscal_year: Optional[str] = None,
2353
+ quarter: Optional[str] = None
2354
+ ) -> Dict[str, Any]:
2355
+ """
2356
+ Get net profit margin for a stock.
2357
+
2358
+ Args:
2359
+ symbol (str): Stock symbol (e.g., "INFY").
2360
+ period (str, optional): "quarterly" or "annual". Defaults to "quarterly".
2361
+ fiscal_year (str, optional): For annual reports (e.g., "2023").
2362
+ quarter (str, optional): For quarterly reports (e.g., "Q1FY24").
2363
+
2364
+ Returns:
2365
+ Dict[str, Any]: Net profit margin data.
2366
+
2367
+ Example Response:
2368
+ {
2369
+ "symbol": "INFY",
2370
+ "period": "quarterly",
2371
+ "quarter": "Q1FY24",
2372
+ "fiscalYear": null,
2373
+ "netProfitMargin": 16.8,
2374
+ "unit": "%"
2375
+ }
2376
+ """
2377
+ params = self._normalize_params({
2378
+ "symbol": symbol,
2379
+ "period": period
2380
+ })
2381
+
2382
+ if fiscal_year:
2383
+ params["fiscalYear"] = fiscal_year
2384
+ if quarter:
2385
+ params["quarter"] = quarter
2386
+
2387
+ logger.debug("Fetching net profit margin for %s", symbol)
2388
+ return self._make_request("GET", self._routes["analytics.fundamentals.net_profit_margin"], params=params)
2389
+
2390
+ def get_roe(
2391
+ self,
2392
+ symbol: str,
2393
+ period: str = "annual",
2394
+ consolidated: bool = True
2395
+ ) -> Dict[str, Any]:
2396
+ """
2397
+ Get Return on Equity (ROE) for a stock.
2398
+
2399
+ Args:
2400
+ symbol (str): Stock symbol.
2401
+ period (str, optional): "quarterly" or "annual". Defaults to "annual".
2402
+ consolidated (bool, optional): Use consolidated financials. Defaults to True.
2403
+
2404
+ Returns:
2405
+ Dict[str, Any]: ROE data.
2406
+
2407
+ Example Response:
2408
+ {
2409
+ "symbol": "TCS",
2410
+ "period": "annual",
2411
+ "roe": 42.5,
2412
+ "unit": "%"
2413
+ }
2414
+ """
2415
+ params = {
2416
+ "symbol": symbol,
2417
+ "period": period,
2418
+ "consolidated": consolidated
2419
+ }
2420
+
2421
+ logger.debug("Fetching ROE for %s", symbol)
2422
+ return self._make_request("GET", self._routes["analytics.fundamentals.roe"], params=params)
2423
+
2424
+ def get_roa(
2425
+ self,
2426
+ symbol: str,
2427
+ period: str = "annual",
2428
+ consolidated: bool = True
2429
+ ) -> Dict[str, Any]:
2430
+ """
2431
+ Get Return on Assets (ROA) for a stock.
2432
+
2433
+ Args:
2434
+ symbol (str): Stock symbol.
2435
+ period (str, optional): "quarterly" or "annual". Defaults to "annual".
2436
+ consolidated (bool, optional): Use consolidated financials. Defaults to True.
2437
+
2438
+ Returns:
2439
+ Dict[str, Any]: ROA data.
2440
+
2441
+ Example Response:
2442
+ {
2443
+ "symbol": "WIPRO",
2444
+ "period": "annual",
2445
+ "roa": 14.3,
2446
+ "unit": "%"
2447
+ }
2448
+ """
2449
+ params = {
2450
+ "symbol": symbol,
2451
+ "period": period,
2452
+ "consolidated": consolidated
2453
+ }
2454
+
2455
+ logger.debug("Fetching ROA for %s", symbol)
2456
+ return self._make_request("GET", self._routes["analytics.fundamentals.roa"], params=params)
2457
+
2458
+ def get_ebit_margin(
2459
+ self,
2460
+ symbol: str,
2461
+ period: str = "annual",
2462
+ consolidated: bool = True
2463
+ ) -> Dict[str, Any]:
2464
+ """
2465
+ Get EBIT margin for a stock.
2466
+
2467
+ Args:
2468
+ symbol (str): Stock symbol.
2469
+ period (str, optional): "quarterly" or "annual". Defaults to "annual".
2470
+ consolidated (bool, optional): Use consolidated financials. Defaults to True.
2471
+
2472
+ Returns:
2473
+ Dict[str, Any]: EBIT margin data.
2474
+
2475
+ Example Response:
2476
+ {
2477
+ "symbol": "INFY",
2478
+ "period": "annual",
2479
+ "ebit_margin": 24.1,
2480
+ "unit": "%"
2481
+ }
2482
+ """
2483
+ params = {
2484
+ "symbol": symbol,
2485
+ "period": period,
2486
+ "consolidated": consolidated
2487
+ }
2488
+
2489
+ logger.debug("Fetching EBIT margin for %s", symbol)
2490
+ return self._make_request("GET", self._routes["analytics.fundamentals.ebit_margin"], params=params)
2491
+
2492
+ def get_ocf_netprofit_ratio(
2493
+ self,
2494
+ symbol: str,
2495
+ period: str = "annual"
2496
+ ) -> Dict[str, Any]:
2497
+ """
2498
+ Get Operating Cash Flow to Net Profit ratio.
2499
+
2500
+ Args:
2501
+ symbol (str): Stock symbol.
2502
+ period (str, optional): "annual" or "ttm". Defaults to "annual".
2503
+
2504
+ Returns:
2505
+ Dict[str, Any]: OCF/Net Profit ratio data.
2506
+
2507
+ Example Response:
2508
+ {
2509
+ "symbol": "TCS",
2510
+ "period": "annual",
2511
+ "ocf_netprofit_ratio": 1.15,
2512
+ "unit": "ratio"
2513
+ }
2514
+
2515
+ Note: Ratio > 1 indicates strong cash generation.
2516
+ """
2517
+ params = {
2518
+ "symbol": symbol,
2519
+ "period": period
2520
+ }
2521
+
2522
+ logger.debug("Fetching OCF/Net Profit ratio for %s", symbol)
2523
+ return self._make_request("GET", self._routes["analytics.fundamentals.ocf_netprofit_ratio"], params=params)
2524
+
2525
+ def get_eps_cagr(
2526
+ self,
2527
+ symbol: str,
2528
+ start_year: int,
2529
+ end_year: int
2530
+ ) -> Dict[str, Any]:
2531
+ """
2532
+ Get EPS Compound Annual Growth Rate (CAGR).
2533
+
2534
+ Args:
2535
+ symbol (str): Stock symbol.
2536
+ start_year (int): Starting year (e.g., 2019).
2537
+ end_year (int): Ending year (e.g., 2023).
2538
+
2539
+ Returns:
2540
+ Dict[str, Any]: EPS CAGR data.
2541
+
2542
+ Example Response:
2543
+ {
2544
+ "symbol": "INFY",
2545
+ "startYear": 2019,
2546
+ "endYear": 2023,
2547
+ "epsCagr": 8.5,
2548
+ "epsStart": 35.2,
2549
+ "epsEnd": 48.7,
2550
+ "years": 4,
2551
+ "unit": "%"
2552
+ }
2553
+ """
2554
+ params = self._normalize_params({
2555
+ "symbol": symbol,
2556
+ "startYear": start_year,
2557
+ "endYear": end_year
2558
+ })
2559
+
2560
+ logger.debug("Fetching EPS CAGR for %s from %s to %s", symbol, start_year, end_year)
2561
+ return self._make_request("GET", self._routes["analytics.fundamentals.eps_cagr"], params=params)
2562
+
2563
+ def get_book_to_market(
2564
+ self,
2565
+ symbol: str,
2566
+ as_of: str,
2567
+ price_source: str = "avgQuarter",
2568
+ custom_price: Optional[float] = None,
2569
+ standalone: bool = False,
2570
+ currency: str = "INR"
2571
+ ) -> Dict[str, Any]:
2572
+ """
2573
+ Get book-to-market ratio for a stock.
2574
+
2575
+ Args:
2576
+ symbol (str): Stock symbol (e.g., "NSE:INFY").
2577
+ as_of (str): Reference date (YYYY-MM-DD).
2578
+ price_source (str, optional): Price source: spot, avgQuarter, custom. Defaults to "avgQuarter".
2579
+ custom_price (float, optional): Required if price_source=custom.
2580
+ standalone (bool, optional): Use standalone financials. Defaults to False.
2581
+ currency (str, optional): Output currency. Defaults to "INR".
2582
+
2583
+ Returns:
2584
+ Dict[str, Any]: Book-to-market ratio data.
2585
+
2586
+ Example Response:
2587
+ {
2588
+ "symbol": "NSE:INFY",
2589
+ "asOf": "2023-03-31",
2590
+ "bookToMarket": 0.1234,
2591
+ "bookValuePerShare": 184.50,
2592
+ "marketPricePerShare": 1495.75,
2593
+ "sourcePriceType": "avgQuarter",
2594
+ "quarterRef": "Q4FY23",
2595
+ "standalone": false,
2596
+ "unit": "ratio"
2597
+ }
2598
+ """
2599
+ params = self._normalize_params({
2600
+ "symbol": symbol,
2601
+ "asOf": as_of,
2602
+ "priceSource": price_source,
2603
+ "standalone": standalone,
2604
+ "currency": currency
2605
+ })
2606
+
2607
+ if custom_price is not None:
2608
+ params["customPrice"] = str(custom_price)
2609
+
2610
+ logger.debug("Fetching book-to-market ratio for %s", symbol)
2611
+ return self._make_request("GET", self._routes["analytics.fundamentals.book_to_market"], params=params)
2612
+
2613
+ def get_marketcap_to_sales(
2614
+ self,
2615
+ symbol: str,
2616
+ as_of: str,
2617
+ price_source: str = "avgQuarter",
2618
+ custom_price: Optional[float] = None,
2619
+ standalone: bool = False
2620
+ ) -> Dict[str, Any]:
2621
+ """
2622
+ Get market cap to sales ratio for a stock.
2623
+
2624
+ Args:
2625
+ symbol (str): Stock symbol.
2626
+ as_of (str): Reference date (YYYY-MM-DD).
2627
+ price_source (str, optional): Price source. Defaults to "avgQuarter".
2628
+ custom_price (float, optional): Custom price if price_source=custom.
2629
+ standalone (bool, optional): Use standalone financials. Defaults to False.
2630
+
2631
+ Returns:
2632
+ Dict[str, Any]: Market cap to sales ratio data.
2633
+
2634
+ Example Response:
2635
+ {
2636
+ "symbol": "INFY",
2637
+ "asOf": "2023-03-31",
2638
+ "marketcapToSales": 5.67,
2639
+ "marketCap": 615000,
2640
+ "sales": 108500,
2641
+ "pricePerShare": 1495.75,
2642
+ "sharesOutstanding": 4112000000,
2643
+ "revenueQuarter": "Q1",
2644
+ "standalone": false,
2645
+ "unit": "ratio"
2646
+ }
2647
+ """
2648
+ params = self._normalize_params({
2649
+ "symbol": symbol,
2650
+ "asOf": as_of,
2651
+ "priceSource": price_source,
2652
+ "standalone": standalone
2653
+ })
2654
+
2655
+ if custom_price is not None:
2656
+ params["customPrice"] = str(custom_price)
2657
+
2658
+ logger.debug("Fetching market cap to sales ratio for %s", symbol)
2659
+ return self._make_request("GET", self._routes["analytics.fundamentals.marketcap_to_sales"], params=params)
2660
+
2661
+ def get_cash_to_marketcap(
2662
+ self,
2663
+ symbol: str,
2664
+ as_of: str,
2665
+ price_source: str = "avgQuarter",
2666
+ custom_price: Optional[float] = None,
2667
+ standalone: bool = False
2668
+ ) -> Dict[str, Any]:
2669
+ """
2670
+ Get cash to market cap ratio for a stock.
2671
+
2672
+ Args:
2673
+ symbol (str): Stock symbol.
2674
+ as_of (str): Reference date (YYYY-MM-DD).
2675
+ price_source (str, optional): Price source. Defaults to "avgQuarter".
2676
+ custom_price (float, optional): Custom price if price_source=custom.
2677
+ standalone (bool, optional): Use standalone financials. Defaults to False.
2678
+
2679
+ Returns:
2680
+ Dict[str, Any]: Cash to market cap ratio data.
2681
+
2682
+ Example Response:
2683
+ {
2684
+ "symbol": "INFY",
2685
+ "asOf": "2023-03-31",
2686
+ "cashToMarketcap": 0.0456,
2687
+ "cashAndEquivalents": 28050,
2688
+ "marketCap": 615000,
2689
+ "pricePerShare": 1495.75,
2690
+ "sharesOutstanding": 4112000000,
2691
+ "reportingQuarter": "Q1",
2692
+ "standalone": false,
2693
+ "unit": "ratio"
2694
+ }
2695
+ """
2696
+ params = self._normalize_params({
2697
+ "symbol": symbol,
2698
+ "asOf": as_of,
2699
+ "priceSource": price_source,
2700
+ "standalone": standalone
2701
+ })
2702
+
2703
+ if custom_price is not None:
2704
+ params["customPrice"] = str(custom_price)
2705
+
2706
+ logger.debug("Fetching cash to market cap ratio for %s", symbol)
2707
+ return self._make_request("GET", self._routes["analytics.fundamentals.cash_to_marketcap"], params=params)
2708
+
2709
+ # --- Valuation Methods ---
2710
+
2711
+ def get_pe_ratio(
2712
+ self,
2713
+ symbol: str,
2714
+ date: Optional[str] = None,
2715
+ ttm: bool = False,
2716
+ consolidated: bool = True,
2717
+ standalone: bool = False
2718
+ ) -> Dict[str, Any]:
2719
+ """
2720
+ Get Price to Earnings (P/E) ratio.
2721
+
2722
+ Args:
2723
+ symbol (str): Stock symbol.
2724
+ date (str, optional): Date in "YYYY-MM-DD" format.
2725
+ ttm (bool, optional): Use trailing twelve months. Defaults to False.
2726
+ consolidated (bool, optional): Use consolidated financials. Defaults to True.
2727
+ standalone (bool, optional): Use standalone financials. Defaults to False.
2728
+
2729
+ Returns:
2730
+ Dict[str, Any]: P/E ratio data.
2731
+
2732
+ Example Response:
2733
+ {
2734
+ "symbol": "RELIANCE",
2735
+ "date": "2025-07-31",
2736
+ "pe_ratio": 27.5,
2737
+ "price": null,
2738
+ "eps": null,
2739
+ "ttm": false,
2740
+ "consolidated": true,
2741
+ "unit": "ratio"
2742
+ }
2743
+ """
2744
+ params = {
2745
+ "symbol": symbol,
2746
+ "ttm": ttm,
2747
+ "consolidated": consolidated,
2748
+ "standalone": standalone
2749
+ }
2750
+
2751
+ if date:
2752
+ params["date"] = date
2753
+
2754
+ logger.debug("Fetching P/E ratio for %s", symbol)
2755
+ return self._make_request("GET", self._routes["analytics.valuation.pe_ratio"], params=params)
2756
+
2757
+ def get_pb_ratio(
2758
+ self,
2759
+ symbol: str,
2760
+ date: Optional[str] = None,
2761
+ consolidated: bool = True,
2762
+ standalone: bool = False
2763
+ ) -> Dict[str, Any]:
2764
+ """
2765
+ Get Price to Book (P/B) ratio.
2766
+
2767
+ Args:
2768
+ symbol (str): Stock symbol.
2769
+ date (str, optional): Date in "YYYY-MM-DD" format.
2770
+ consolidated (bool, optional): Use consolidated financials. Defaults to True.
2771
+ standalone (bool, optional): Use standalone financials. Defaults to False.
2772
+
2773
+ Returns:
2774
+ Dict[str, Any]: P/B ratio data.
2775
+
2776
+ Example Response:
2777
+ {
2778
+ "symbol": "HDFC",
2779
+ "date": "2025-07-31",
2780
+ "pb_ratio": 3.2,
2781
+ "price": null,
2782
+ "book_value": null,
2783
+ "consolidated": true,
2784
+ "unit": "ratio"
2785
+ }
2786
+ """
2787
+ params = {
2788
+ "symbol": symbol,
2789
+ "consolidated": consolidated,
2790
+ "standalone": standalone
2791
+ }
2792
+
2793
+ if date:
2794
+ params["date"] = date
2795
+
2796
+ logger.debug("Fetching P/B ratio for %s", symbol)
2797
+ return self._make_request("GET", self._routes["analytics.valuation.pb_ratio"], params=params)
2798
+
2799
+ def get_ev_ebitda(
2800
+ self,
2801
+ symbol: str,
2802
+ date: Optional[str] = None,
2803
+ ttm: bool = False,
2804
+ consolidated: bool = True,
2805
+ standalone: bool = False
2806
+ ) -> Dict[str, Any]:
2807
+ """
2808
+ Get Enterprise Value to EBITDA ratio.
2809
+
2810
+ Args:
2811
+ symbol (str): Stock symbol.
2812
+ date (str, optional): Date in "YYYY-MM-DD" format.
2813
+ ttm (bool, optional): Use trailing twelve months. Defaults to False.
2814
+ consolidated (bool, optional): Use consolidated financials. Defaults to True.
2815
+ standalone (bool, optional): Use standalone financials. Defaults to False.
2816
+
2817
+ Returns:
2818
+ Dict[str, Any]: EV/EBITDA ratio data.
2819
+
2820
+ Example Response:
2821
+ {
2822
+ "symbol": "LTI",
2823
+ "date": "2025-06-30",
2824
+ "ev_ebitda": 22.8,
2825
+ "enterprise_value": null,
2826
+ "ebitda": null,
2827
+ "ttm": false,
2828
+ "unit": "ratio"
2829
+ }
2830
+ """
2831
+ params = {
2832
+ "symbol": symbol,
2833
+ "ttm": ttm,
2834
+ "consolidated": consolidated,
2835
+ "standalone": standalone
2836
+ }
2837
+
2838
+ if date:
2839
+ params["date"] = date
2840
+
2841
+ logger.debug("Fetching EV/EBITDA for %s", symbol)
2842
+ return self._make_request("GET", self._routes["analytics.valuation.ev_ebitda"], params=params)
2843
+
2844
+ def get_fcf_yield(
2845
+ self,
2846
+ symbol: str,
2847
+ date: Optional[str] = None,
2848
+ ttm: bool = False,
2849
+ consolidated: bool = True,
2850
+ standalone: bool = False
2851
+ ) -> Dict[str, Any]:
2852
+ """
2853
+ Get Free Cash Flow yield.
2854
+
2855
+ Args:
2856
+ symbol (str): Stock symbol.
2857
+ date (str, optional): Date in "YYYY-MM-DD" format.
2858
+ ttm (bool, optional): Use trailing twelve months. Defaults to False.
2859
+ consolidated (bool, optional): Use consolidated financials. Defaults to True.
2860
+ standalone (bool, optional): Use standalone financials. Defaults to False.
2861
+
2862
+ Returns:
2863
+ Dict[str, Any]: FCF yield data.
2864
+
2865
+ Example Response:
2866
+ {
2867
+ "symbol": "HDFCAMC",
2868
+ "date": "2025-06-30",
2869
+ "fcf_yield": 4.5,
2870
+ "fcf": null,
2871
+ "market_cap": null,
2872
+ "ttm": false,
2873
+ "unit": "%"
2874
+ }
2875
+ """
2876
+ params = {
2877
+ "symbol": symbol,
2878
+ "ttm": ttm,
2879
+ "consolidated": consolidated,
2880
+ "standalone": standalone
2881
+ }
2882
+
2883
+ if date:
2884
+ params["date"] = date
2885
+
2886
+ logger.debug("Fetching FCF yield for %s", symbol)
2887
+ return self._make_request("GET", self._routes["analytics.valuation.fcf_yield"], params=params)
2888
+
2889
+ # --- Returns Methods ---
2890
+
2891
+
2892
+ def get_quarterly_returns(
2893
+ self,
2894
+ symbol: str,
2895
+ start_date: str,
2896
+ end_date: str,
2897
+ adjusted: bool = True
2898
+ ) -> Dict[str, Any]:
2899
+ """
2900
+ Get quarterly returns for a stock.
2901
+
2902
+ Args:
2903
+ symbol (str): Stock symbol.
2904
+ start_date (str): Start date in "YYYY-MM-DD" format.
2905
+ end_date (str): End date in "YYYY-MM-DD" format.
2906
+ adjusted (bool, optional): Use adjusted prices. Defaults to True.
2907
+
2908
+ Returns:
2909
+ Dict[str, Any]: Quarterly returns data.
2910
+ """
2911
+ params = self._normalize_params({
2912
+ "symbol": symbol,
2913
+ "startDate": start_date,
2914
+ "endDate": end_date,
2915
+ "adjusted": adjusted
2916
+ })
2917
+
2918
+ logger.debug("Fetching quarterly returns for %s", symbol)
2919
+ return self._make_request("GET", self._routes["analytics.returns.quarterly"], params=params)
2920
+
2921
+ def get_monthly_returns(
2922
+ self,
2923
+ symbol: str,
2924
+ start_date: str,
2925
+ end_date: str,
2926
+ adjusted: bool = True
2927
+ ) -> Dict[str, Any]:
2928
+ """
2929
+ Get monthly returns for a stock.
2930
+
2931
+ Args:
2932
+ symbol (str): Stock symbol.
2933
+ start_date (str): Start date in "YYYY-MM-DD" format.
2934
+ end_date (str): End date in "YYYY-MM-DD" format.
2935
+ adjusted (bool, optional): Use adjusted prices. Defaults to True.
2936
+
2937
+ Returns:
2938
+ Dict[str, Any]: Monthly returns data.
2939
+ """
2940
+ params = self._normalize_params({
2941
+ "symbol": symbol,
2942
+ "startDate": start_date,
2943
+ "endDate": end_date,
2944
+ "adjusted": adjusted
2945
+ })
2946
+
2947
+ logger.debug("Fetching monthly returns for %s", symbol)
2948
+ return self._make_request("GET", self._routes["analytics.returns.monthly"], params=params)
2949
+
2950
+ # --- Market Data Methods ---
2951
+
2952
+ def get_analytics_ohlcv_daily(
2953
+ self,
2954
+ symbol: str,
2955
+ start_date: str,
2956
+ end_date: str,
2957
+ adjusted: bool = True
2958
+ ) -> Dict[str, Any]:
2959
+ """
2960
+ Get daily OHLCV data from analytics API.
2961
+
2962
+ Args:
2963
+ symbol (str): Stock symbol.
2964
+ start_date (str): Start date in "YYYY-MM-DD" format.
2965
+ end_date (str): End date in "YYYY-MM-DD" format.
2966
+ adjusted (bool, optional): Use adjusted prices. Defaults to True (ignored as adjusted data not available).
2967
+
2968
+ Returns:
2969
+ Dict[str, Any]: Daily OHLCV data.
2970
+
2971
+ Example Response:
2972
+ {
2973
+ "data": [
2974
+ {
2975
+ "date": "2025-01-01",
2976
+ "open": 2305.0,
2977
+ "high": 2340.0,
2978
+ "low": 2290.0,
2979
+ "close": 2325.0,
2980
+ "volume": 12500000,
2981
+ "symbol": "RELIANCE"
2982
+ }
2983
+ ]
2984
+ }
2985
+
2986
+ Note: Maximum 365 days per request.
2987
+ """
2988
+ params = self._normalize_params({
2989
+ "symbol": symbol,
2990
+ "startDate": start_date,
2991
+ "endDate": end_date,
2992
+ "adjusted": adjusted
2993
+ })
2994
+
2995
+ logger.debug("Fetching analytics OHLCV daily data for %s", symbol)
2996
+ return self._make_request("GET", self._routes["analytics.marketdata.ohlcv_daily"], params=params)
2997
+
2998
+ def get_analytics_historical_prices(
2999
+ self,
3000
+ symbol: str,
3001
+ start_date: str,
3002
+ end_date: str,
3003
+ adjusted: bool = True
3004
+ ) -> Dict[str, Any]:
3005
+ """
3006
+ Get historical prices from analytics API (same as OHLCV daily).
3007
+
3008
+ Args:
3009
+ symbol (str): Stock symbol.
3010
+ start_date (str): Start date in "YYYY-MM-DD" format.
3011
+ end_date (str): End date in "YYYY-MM-DD" format.
3012
+ adjusted (bool, optional): Use adjusted prices. Defaults to True.
3013
+
3014
+ Returns:
3015
+ Dict[str, Any]: Historical price data.
3016
+ """
3017
+ params = self._normalize_params({
3018
+ "symbol": symbol,
3019
+ "startDate": start_date,
3020
+ "endDate": end_date,
3021
+ "adjusted": adjusted
3022
+ })
3023
+
3024
+ logger.debug("Fetching analytics historical prices for %s", symbol)
3025
+ return self._make_request("GET", self._routes["analytics.marketdata.historical_prices"], params=params)
3026
+
3027
+ def get_free_float_market_cap(
3028
+ self,
3029
+ symbol: str,
3030
+ date: Optional[str] = None
3031
+ ) -> Dict[str, Any]:
3032
+ """
3033
+ Get free-float market capitalization.
3034
+
3035
+ Args:
3036
+ symbol (str): Stock symbol.
3037
+ date (str, optional): Date in "YYYY-MM-DD" format. Defaults to most recent.
3038
+
3039
+ Returns:
3040
+ Dict[str, Any]: Free-float market cap data.
3041
+
3042
+ Example Response:
3043
+ {
3044
+ "symbol": "RELIANCE",
3045
+ "date": "2025-07-31",
3046
+ "free_float_market_cap": 1054321.89,
3047
+ "market_cap": 1234567.89,
3048
+ "promoter_holding_percent": 14.6,
3049
+ "unit": "₹ Crores"
3050
+ }
3051
+ """
3052
+ params = {"symbol": symbol}
3053
+
3054
+ if date:
3055
+ params["date"] = date
3056
+
3057
+ logger.debug("Fetching free-float market cap for %s", symbol)
3058
+ return self._make_request("GET", self._routes["analytics.marketdata.free_float_market_cap"], params=params)
3059
+
3060
+ # --- Ownership Methods ---
3061
+
3062
+ def get_fii_dii_holdings(
3063
+ self,
3064
+ symbol: str,
3065
+ quarter: Optional[str] = None
3066
+ ) -> Dict[str, Any]:
3067
+ """
3068
+ Get FII and DII holdings percentages.
3069
+
3070
+ Args:
3071
+ symbol (str): Stock symbol.
3072
+ quarter (str, optional): Quarter in "Q1FY24" format. Defaults to latest.
3073
+
3074
+ Returns:
3075
+ Dict[str, Any]: FII and DII holdings data.
3076
+
3077
+ Example Response:
3078
+ {
3079
+ "symbol": "RELIANCE",
3080
+ "quarter": "Q4FY23",
3081
+ "fii_percentage": 24.3,
3082
+ "dii_percentage": 18.7,
3083
+ "institutional_total": 43.0,
3084
+ "unit": "%"
3085
+ }
3086
+ """
3087
+ params = {"symbol": symbol}
3088
+
3089
+ if quarter:
3090
+ params["quarter"] = quarter
3091
+
3092
+ logger.debug("Fetching FII/DII holdings for %s", symbol)
3093
+ return self._make_request("GET", self._routes["analytics.ownership.fii_dii"], params=params)
3094
+
3095
+ def get_fii_change(self, symbol: str) -> Dict[str, Any]:
3096
+ """
3097
+ Get FII holding change from previous quarter.
3098
+
3099
+ Args:
3100
+ symbol (str): Stock symbol.
3101
+
3102
+ Returns:
3103
+ Dict[str, Any]: FII change data.
3104
+
3105
+ Example Response:
3106
+ {
3107
+ "symbol": "INFY",
3108
+ "quarter": "2025-03-31",
3109
+ "fii_change": 1.2,
3110
+ "current_fii": 33.5,
3111
+ "previous_fii": 32.3,
3112
+ "unit": "%"
3113
+ }
3114
+ """
3115
+ params = {"symbol": symbol}
3116
+
3117
+ logger.debug("Fetching FII change for %s", symbol)
3118
+ return self._make_request("GET", self._routes["analytics.ownership.fii_change"], params=params)
3119
+
3120
+ def get_dii_change(self, symbol: str) -> Dict[str, Any]:
3121
+ """
3122
+ Get DII holding change from previous quarter.
3123
+
3124
+ Args:
3125
+ symbol (str): Stock symbol.
3126
+
3127
+ Returns:
3128
+ Dict[str, Any]: DII change data.
3129
+
3130
+ Example Response:
3131
+ {
3132
+ "symbol": "INFY",
3133
+ "quarter": "2025-03-31",
3134
+ "dii_change": -0.5,
3135
+ "current_dii": 15.2,
3136
+ "previous_dii": 15.7,
3137
+ "unit": "%"
3138
+ }
3139
+ """
3140
+ params = {"symbol": symbol}
3141
+
3142
+ logger.debug("Fetching DII change for %s", symbol)
3143
+ return self._make_request("GET", self._routes["analytics.ownership.dii_change"], params=params)
3144
+
3145
+ # --- Index Data Methods ---
3146
+
3147
+ def get_index_ohlc_daily(
3148
+ self,
3149
+ symbol: str,
3150
+ start_date: str,
3151
+ end_date: str
3152
+ ) -> Dict[str, Any]:
3153
+ """
3154
+ Get daily OHLC data for an index.
3155
+
3156
+ Args:
3157
+ symbol (str): Index symbol (e.g., "NIFTY50").
3158
+ start_date (str): Start date in "YYYY-MM-DD" format.
3159
+ end_date (str): End date in "YYYY-MM-DD" format.
3160
+
3161
+ Returns:
3162
+ Dict[str, Any]: Index OHLC data.
3163
+
3164
+ Example Response:
3165
+ {
3166
+ "data": [
3167
+ {
3168
+ "date": "2025-01-01",
3169
+ "open": 18250.0,
3170
+ "high": 18350.0,
3171
+ "low": 18200.0,
3172
+ "close": 18325.0,
3173
+ "symbol": "NIFTY50"
3174
+ }
3175
+ ]
3176
+ }
3177
+ """
3178
+ params = self._normalize_params({
3179
+ "index": symbol,
3180
+ "startDate": start_date,
3181
+ "endDate": end_date
3182
+ })
3183
+
3184
+ logger.debug("Fetching index OHLC daily data for %s", symbol)
3185
+ return self._make_request("GET", self._routes["analytics.marketdata.index_ohlc_daily"], params=params)
3186
+
3187
+ # --- Metrics Methods ---
3188
+
3189
+ def get_sortino_ratio(
3190
+ self,
3191
+ symbol: str,
3192
+ start_date: str,
3193
+ end_date: str,
3194
+ rf: float = 0.065,
3195
+ interval: str = "daily"
3196
+ ) -> Dict[str, Any]:
3197
+ """
3198
+ Get Sortino ratio for a stock/strategy/index.
3199
+
3200
+ Args:
3201
+ symbol (str): Stock/strategy/index symbol.
3202
+ start_date (str): Start date in "YYYY-MM-DD" format.
3203
+ end_date (str): End date in "YYYY-MM-DD" format.
3204
+ rf (float, optional): Annualized risk-free rate. Defaults to 0.065.
3205
+ interval (str, optional): Return frequency: daily, weekly, monthly. Defaults to "daily".
3206
+
3207
+ Returns:
3208
+ Dict[str, Any]: Sortino ratio data.
3209
+
3210
+ Example Response:
3211
+ {
3212
+ "symbol": "HDFCBANK",
3213
+ "startDate": "2024-01-01",
3214
+ "endDate": "2025-01-01",
3215
+ "interval": "daily",
3216
+ "rf": 0.065,
3217
+ "sortinoRatio": 1.8542,
3218
+ "annualizedReturn": 0.1234,
3219
+ "downsideDeviation": 0.0321,
3220
+ "unit": "ratio"
3221
+ }
3222
+ """
3223
+ params = self._normalize_params({
3224
+ "symbol": symbol,
3225
+ "startDate": start_date,
3226
+ "endDate": end_date,
3227
+ "rf": rf,
3228
+ "interval": interval
3229
+ })
3230
+
3231
+ logger.debug("Fetching Sortino ratio for %s", symbol)
3232
+ return self._make_request("GET", self._routes["analytics.metrics.sortino_ratio"], params=params)
3233
+
3234
+ def get_upside_capture(
3235
+ self,
3236
+ symbol: str,
3237
+ benchmark_symbol: str,
3238
+ start_date: str,
3239
+ end_date: str,
3240
+ interval: str = "daily"
3241
+ ) -> Dict[str, Any]:
3242
+ """
3243
+ Get upside capture ratio for a stock/portfolio.
3244
+
3245
+ Args:
3246
+ symbol (str): Stock/portfolio symbol.
3247
+ benchmark_symbol (str): Benchmark symbol.
3248
+ start_date (str): Start date in "YYYY-MM-DD" format.
3249
+ end_date (str): End date in "YYYY-MM-DD" format.
3250
+ interval (str, optional): Comparison interval. Defaults to "daily".
3251
+
3252
+ Returns:
3253
+ Dict[str, Any]: Upside capture ratio data.
3254
+
3255
+ Example Response:
3256
+ {
3257
+ "symbol": "ICICIBANK",
3258
+ "benchmarkSymbol": "NIFTY50",
3259
+ "startDate": "2024-01-01",
3260
+ "endDate": "2025-01-01",
3261
+ "interval": "daily",
3262
+ "upsideCaptureRatio": 112.50,
3263
+ "periodsAnalyzed": 250,
3264
+ "positiveBenchmarkPeriods": 135,
3265
+ "unit": "%"
3266
+ }
3267
+ """
3268
+ params = self._normalize_params({
3269
+ "symbol": symbol,
3270
+ "benchmarkSymbol": benchmark_symbol,
3271
+ "startDate": start_date,
3272
+ "endDate": end_date,
3273
+ "interval": interval
3274
+ })
3275
+
3276
+ logger.debug("Fetching upside capture ratio for %s vs %s", symbol, benchmark_symbol)
3277
+ return self._make_request("GET", self._routes["analytics.metrics.upside_capture"], params=params)
3278
+
3279
+ # --- Macro Methods ---
3280
+
3281
+ def get_risk_free_rate(
3282
+ self,
3283
+ start_date: str,
3284
+ end_date: str,
3285
+ tenor: str = "10Y",
3286
+ country: str = "IN",
3287
+ method: str = "average"
3288
+ ) -> Dict[str, Any]:
3289
+ """
3290
+ Get risk-free rates for various tenors and countries.
3291
+
3292
+ Args:
3293
+ start_date (str): Start date in "YYYY-MM-DD" format.
3294
+ end_date (str): End date in "YYYY-MM-DD" format.
3295
+ tenor (str, optional): Maturity: 3M, 6M, 1Y, 5Y, 10Y. Defaults to "10Y".
3296
+ country (str, optional): Country code. Defaults to "IN".
3297
+ method (str, optional): Calculation: average, start, end, daily_series. Defaults to "average".
3298
+
3299
+ Returns:
3300
+ Dict[str, Any]: Risk-free rate data.
3301
+
3302
+ Example Response:
3303
+ {
3304
+ "country": "IN",
3305
+ "tenor": "10Y",
3306
+ "startDate": "2024-01-01",
3307
+ "endDate": "2024-12-31",
3308
+ "method": "average",
3309
+ "riskFreeRate": 0.0735,
3310
+ "source": "RBI/FIMMDA (Default)",
3311
+ "unit": "decimal"
3312
+ }
3313
+ """
3314
+ params = self._normalize_params({
3315
+ "startDate": start_date,
3316
+ "endDate": end_date,
3317
+ "tenor": tenor,
3318
+ "country": country,
3319
+ "method": method
3320
+ })
3321
+
3322
+ logger.debug("Fetching risk-free rate for %s %s", country, tenor)
3323
+ return self._make_request("GET", self._routes["analytics.macro.risk_free_rate"], params=params)
3324
+
3325
+ # --- Risk Analysis Methods ---
3326
+
3327
+ def get_max_drawdown(
3328
+ self,
3329
+ symbol: str,
3330
+ start_date: str,
3331
+ end_date: str,
3332
+ adjusted: bool = True,
3333
+ interval: str = "daily"
3334
+ ) -> Dict[str, Any]:
3335
+ """
3336
+ Calculate maximum drawdown for a stock over a specified period.
3337
+
3338
+ Args:
3339
+ symbol (str): Stock symbol.
3340
+ start_date (str): Analysis start date (YYYY-MM-DD).
3341
+ end_date (str): Analysis end date (YYYY-MM-DD).
3342
+ adjusted (bool, optional): Use adjusted prices. Defaults to True.
3343
+ interval (str, optional): Data frequency: daily, weekly, monthly. Defaults to "daily".
3344
+
3345
+ Returns:
3346
+ Dict[str, Any]: Maximum drawdown data.
3347
+
3348
+ Example Response:
3349
+ {
3350
+ "symbol": "TCS",
3351
+ "startDate": "2024-01-01",
3352
+ "endDate": "2024-12-31",
3353
+ "adjusted": true,
3354
+ "interval": "daily",
3355
+ "maxDrawdown": -0.1847,
3356
+ "peakDate": "2024-03-15",
3357
+ "troughDate": "2024-06-08",
3358
+ "peakPrice": 4250.50,
3359
+ "troughPrice": 3465.75,
3360
+ "unit": "decimal"
3361
+ }
3362
+ """
3363
+ params = self._normalize_params({
3364
+ "symbol": symbol,
3365
+ "startDate": start_date,
3366
+ "endDate": end_date,
3367
+ "adjusted": adjusted,
3368
+ "interval": interval
3369
+ })
3370
+
3371
+ logger.debug("Fetching max drawdown for %s", symbol)
3372
+ return self._make_request("GET", self._routes["analytics.risk.max_drawdown"], params=params)
3373
+
3374
+ def get_returns_volatility(
3375
+ self,
3376
+ symbol: str,
3377
+ start_date: str,
3378
+ end_date: str,
3379
+ frequency: str = "daily",
3380
+ periods: Optional[int] = None
3381
+ ) -> Dict[str, Any]:
3382
+ """
3383
+ Calculate returns volatility using standard deviation over a specified period.
3384
+
3385
+ Args:
3386
+ symbol (str): Stock symbol.
3387
+ start_date (str): Analysis start date (YYYY-MM-DD).
3388
+ end_date (str): Analysis end date (YYYY-MM-DD).
3389
+ frequency (str, optional): Return frequency: daily, weekly, monthly. Defaults to "daily".
3390
+ periods (int, optional): Rolling window size (5-250). Auto-calculated if not provided.
3391
+
3392
+ Returns:
3393
+ Dict[str, Any]: Volatility analysis data.
3394
+
3395
+ Example Response:
3396
+ {
3397
+ "symbol": "INFY",
3398
+ "startDate": "2024-01-01",
3399
+ "endDate": "2024-06-30",
3400
+ "frequency": "daily",
3401
+ "volatility": 0.0243,
3402
+ "annualizedVolatility": 0.3856,
3403
+ "periods": 125,
3404
+ "unit": "decimal"
3405
+ }
3406
+ """
3407
+ params = self._normalize_params({
3408
+ "symbol": symbol,
3409
+ "startDate": start_date,
3410
+ "endDate": end_date,
3411
+ "frequency": frequency
3412
+ })
3413
+
3414
+ if periods is not None:
3415
+ params["periods"] = str(periods)
3416
+
3417
+ logger.debug("Fetching returns volatility for %s", symbol)
3418
+ return self._make_request("GET", self._routes["analytics.risk.returns_volatility"], params=params)
3419
+
3420
+ # --- Metadata Methods ---
3421
+
3422
+ def get_sector_classification(
3423
+ self,
3424
+ symbol: str
3425
+ ) -> Dict[str, Any]:
3426
+ """
3427
+ Get comprehensive sector and industry classification for a stock.
3428
+
3429
+ Args:
3430
+ symbol (str): Stock symbol.
3431
+
3432
+ Returns:
3433
+ Dict[str, Any]: Sector classification data.
3434
+
3435
+ Example Response:
3436
+ {
3437
+ "symbol": "TATASTEEL",
3438
+ "sector": "Basic Materials",
3439
+ "industry": "Steel",
3440
+ "subIndustry": "Integrated Steel",
3441
+ "classification": "Industrial Metals & Mining",
3442
+ "lastUpdated": "2024-01-15"
3443
+ }
3444
+ """
3445
+ params = self._normalize_params({
3446
+ "symbol": symbol
3447
+ })
3448
+
3449
+ logger.debug("Fetching sector classification for %s", symbol)
3450
+ return self._make_request("GET", self._routes["analytics.metadata.sector"], params=params)
3451
+
3452
+ # --- Leverage Analysis Methods ---
3453
+
3454
+ def get_debt_equity_ratio(
3455
+ self,
3456
+ symbol: str,
3457
+ date: Optional[str] = None,
3458
+ consolidated: bool = True
3459
+ ) -> Dict[str, Any]:
3460
+ """
3461
+ Calculate debt-to-equity ratio using most recent financial data.
3462
+
3463
+ Args:
3464
+ symbol (str): Stock symbol.
3465
+ date (str, optional): Specific quarter-end date (YYYY-MM-DD). Latest if not provided.
3466
+ consolidated (bool, optional): Use consolidated financials. Defaults to True.
3467
+
3468
+ Returns:
3469
+ Dict[str, Any]: Debt-to-equity ratio data.
3470
+
3471
+ Example Response:
3472
+ {
3473
+ "symbol": "RELIANCE",
3474
+ "date": "2024-09-30",
3475
+ "deRatio": 0.3247,
3476
+ "totalDebt": 287450.25,
3477
+ "shareholderEquity": 884972.10,
3478
+ "source": "consolidated",
3479
+ "quarter": "Q2FY25",
3480
+ "unit": "ratio"
3481
+ }
3482
+ """
3483
+ params = self._normalize_params({
3484
+ "symbol": symbol,
3485
+ "consolidated": consolidated
3486
+ })
3487
+
3488
+ if date:
3489
+ params["date"] = date
3490
+
3491
+ logger.debug("Fetching debt-to-equity ratio for %s", symbol)
3492
+ return self._make_request("GET", self._routes["analytics.leverage.debt_equity_ratio"], params=params)
3493
+
3494
+ def get_cagr(
3495
+ self,
3496
+ symbol: str,
3497
+ start_date: str,
3498
+ end_date: str,
3499
+ adjusted: bool = True
3500
+ ) -> Dict[str, Any]:
3501
+ """
3502
+ Calculate Compound Annual Growth Rate (CAGR) over a specified time period.
3503
+
3504
+ Args:
3505
+ symbol (str): Stock symbol.
3506
+ start_date (str): Investment start date (YYYY-MM-DD).
3507
+ end_date (str): Investment end date (YYYY-MM-DD).
3508
+ adjusted (bool, optional): Use adjusted prices. Defaults to True.
3509
+
3510
+ Returns:
3511
+ Dict[str, Any]: CAGR calculation data.
3512
+
3513
+ Example Response:
3514
+ {
3515
+ "symbol": "TCS",
3516
+ "startDate": "2020-01-01",
3517
+ "endDate": "2024-12-31",
3518
+ "cagr": 12.45,
3519
+ "startPrice": 2150.30,
3520
+ "endPrice": 3847.60,
3521
+ "years": 5.0,
3522
+ "adjusted": true,
3523
+ "unit": "%"
3524
+ }
3525
+ """
3526
+ params = self._normalize_params({
3527
+ "symbol": symbol,
3528
+ "startDate": start_date,
3529
+ "endDate": end_date,
3530
+ "adjusted": adjusted
3531
+ })
3532
+
3533
+ logger.debug("Fetching CAGR for %s from %s to %s", symbol, start_date, end_date)
3534
+ return self._make_request("GET", self._routes["analytics.returns.cagr"], params=params)
3535
+
3536
+ # --- New Analytics Methods ---
3537
+
3538
+ def get_average_traded_volume(
3539
+ self,
3540
+ symbol: str,
3541
+ start_date: str,
3542
+ end_date: str,
3543
+ interval: str = "daily"
3544
+ ) -> Dict[str, Any]:
3545
+ """
3546
+ Get average traded volume for a stock over a specified period.
3547
+
3548
+ Args:
3549
+ symbol (str): Stock symbol (e.g., HDFCBANK, NSE:RELIANCE).
3550
+ start_date (str): Period start date (YYYY-MM-DD).
3551
+ end_date (str): Period end date (YYYY-MM-DD).
3552
+ interval (str, optional): Time interval ('daily', 'weekly', 'monthly'). Defaults to 'daily'.
3553
+
3554
+ Returns:
3555
+ Dict[str, Any]: Average volume data.
3556
+
3557
+ Example Response:
3558
+ {
3559
+ "symbol": "HDFCBANK",
3560
+ "startDate": "2024-04-01",
3561
+ "endDate": "2024-06-30",
3562
+ "interval": "daily",
3563
+ "averageVolume": 1234567,
3564
+ "totalDays": 61,
3565
+ "unit": "shares"
3566
+ }
3567
+ """
3568
+ params = self._normalize_params({
3569
+ "symbol": symbol,
3570
+ "startDate": start_date,
3571
+ "endDate": end_date,
3572
+ "interval": interval
3573
+ })
3574
+
3575
+ logger.debug("Fetching average traded volume for %s from %s to %s", symbol, start_date, end_date)
3576
+ return self._make_request("GET", self._routes["analytics.marketdata.average_volume"], params=params)
3577
+
3578
+ def get_index_max_drawdown(
3579
+ self,
3580
+ index_symbol: str,
3581
+ start_date: str,
3582
+ end_date: str,
3583
+ interval: str = "daily"
3584
+ ) -> Dict[str, Any]:
3585
+ """
3586
+ Get maximum drawdown for an index over a specified period.
3587
+
3588
+ Args:
3589
+ index_symbol (str): Index symbol (e.g., NIFTY50, BANKNIFTY, SENSEX).
3590
+ start_date (str): Period start date (YYYY-MM-DD).
3591
+ end_date (str): Period end date (YYYY-MM-DD).
3592
+ interval (str, optional): Time interval ('daily', 'weekly', 'monthly'). Defaults to 'daily'.
3593
+
3594
+ Returns:
3595
+ Dict[str, Any]: Maximum drawdown data.
3596
+
3597
+ Example Response:
3598
+ {
3599
+ "indexSymbol": "NIFTY50",
3600
+ "startDate": "2023-01-01",
3601
+ "endDate": "2024-01-01",
3602
+ "interval": "daily",
3603
+ "maxDrawdown": -15.23,
3604
+ "drawdownDate": "2023-10-26",
3605
+ "peakDate": "2023-09-19",
3606
+ "unit": "%"
3607
+ }
3608
+ """
3609
+ params = self._normalize_params({
3610
+ "indexSymbol": index_symbol,
3611
+ "startDate": start_date,
3612
+ "endDate": end_date,
3613
+ "interval": interval
3614
+ })
3615
+
3616
+ logger.debug("Fetching index max drawdown for %s from %s to %s", index_symbol, start_date, end_date)
3617
+ return self._make_request("GET", self._routes["analytics.index.max_drawdown"], params=params)
3618
+
3619
+ def get_drawdown_duration(
3620
+ self,
3621
+ symbol: str,
3622
+ start_date: str,
3623
+ end_date: str,
3624
+ interval: str = "daily"
3625
+ ) -> Dict[str, Any]:
3626
+ """
3627
+ Get drawdown duration analysis for a stock or index.
3628
+
3629
+ Args:
3630
+ symbol (str): Stock or index symbol (e.g., INFY, NIFTY50).
3631
+ start_date (str): Start of analysis period (YYYY-MM-DD).
3632
+ end_date (str): End of analysis period (YYYY-MM-DD).
3633
+ interval (str, optional): Time interval ('daily', 'weekly', 'monthly'). Defaults to 'daily'.
3634
+
3635
+ Returns:
3636
+ Dict[str, Any]: Drawdown duration data.
3637
+
3638
+ Example Response:
3639
+ {
3640
+ "symbol": "INFY",
3641
+ "startDate": "2022-01-01",
3642
+ "endDate": "2024-01-01",
3643
+ "interval": "daily",
3644
+ "maxDrawdownDuration": 147,
3645
+ "averageDrawdownDuration": 23.5,
3646
+ "totalDrawdowns": 12,
3647
+ "unit": "days"
3648
+ }
3649
+ """
3650
+ params = self._normalize_params({
3651
+ "symbol": symbol,
3652
+ "startDate": start_date,
3653
+ "endDate": end_date,
3654
+ "interval": interval
3655
+ })
3656
+
3657
+ logger.debug("Fetching drawdown duration for %s from %s to %s", symbol, start_date, end_date)
3658
+ return self._make_request("GET", self._routes["analytics.instrument.drawdown_duration"], params=params)
3659
+
3660
+ def get_rolling_peak_price(
3661
+ self,
3662
+ symbol: str,
3663
+ start_date: str,
3664
+ end_date: str,
3665
+ window: int,
3666
+ adjusted: bool = True,
3667
+ interval: str = "daily"
3668
+ ) -> Dict[str, Any]:
3669
+ """
3670
+ Get rolling peak price analysis for a stock.
3671
+
3672
+ Args:
3673
+ symbol (str): Stock symbol (e.g., RELIANCE, NSE:TCS).
3674
+ start_date (str): Start of evaluation period (YYYY-MM-DD).
3675
+ end_date (str): End of evaluation period (YYYY-MM-DD).
3676
+ window (int): Rolling window size in days (1-252).
3677
+ adjusted (bool, optional): Adjust for corporate actions. Defaults to True.
3678
+ interval (str, optional): Time interval ('daily', 'weekly', 'monthly'). Defaults to 'daily'.
3679
+
3680
+ Returns:
3681
+ Dict[str, Any]: Rolling peak price data.
3682
+
3683
+ Example Response:
3684
+ {
3685
+ "symbol": "NSE:TCS",
3686
+ "startDate": "2024-01-01",
3687
+ "endDate": "2024-06-30",
3688
+ "window": 20,
3689
+ "interval": "daily",
3690
+ "adjusted": true,
3691
+ "data": [
3692
+ {"date": "2024-01-01", "price": 3500.0, "rollingPeak": 3500.0},
3693
+ {"date": "2024-01-02", "price": 3520.0, "rollingPeak": 3520.0}
3694
+ ]
3695
+ }
3696
+ """
3697
+ params = self._normalize_params({
3698
+ "symbol": symbol,
3699
+ "startDate": start_date,
3700
+ "endDate": end_date,
3701
+ "window": window,
3702
+ "adjusted": adjusted,
3703
+ "interval": interval
3704
+ })
3705
+
3706
+ logger.debug("Fetching rolling peak price for %s from %s to %s with window %d", symbol, start_date, end_date, window)
3707
+ return self._make_request("GET", self._routes["analytics.price.rolling_peak"], params=params)
3708
+
3709
+ def get_rolling_price_mean(
3710
+ self,
3711
+ symbol: str,
3712
+ start_date: str,
3713
+ end_date: str,
3714
+ window: int,
3715
+ adjusted: bool = True,
3716
+ interval: str = "daily"
3717
+ ) -> Dict[str, Any]:
3718
+ """
3719
+ Get rolling mean price analysis for a stock.
3720
+
3721
+ Args:
3722
+ symbol (str): Stock symbol (e.g., HDFCBANK, NSE:WIPRO).
3723
+ start_date (str): Start of evaluation period (YYYY-MM-DD).
3724
+ end_date (str): End of evaluation period (YYYY-MM-DD).
3725
+ window (int): Rolling window size in days (1-252).
3726
+ adjusted (bool, optional): Adjust for corporate actions. Defaults to True.
3727
+ interval (str, optional): Time interval ('daily', 'weekly', 'monthly'). Defaults to 'daily'.
3728
+
3729
+ Returns:
3730
+ Dict[str, Any]: Rolling mean price data.
3731
+
3732
+ Example Response:
3733
+ {
3734
+ "symbol": "NSE:HDFCBANK",
3735
+ "startDate": "2024-01-01",
3736
+ "endDate": "2024-06-30",
3737
+ "window": 20,
3738
+ "interval": "daily",
3739
+ "adjusted": true,
3740
+ "data": [
3741
+ {"date": "2024-01-01", "price": 1500.0, "rollingMean": 1500.0},
3742
+ {"date": "2024-01-02", "price": 1510.0, "rollingMean": 1505.0}
3743
+ ]
3744
+ }
3745
+ """
3746
+ params = self._normalize_params({
3747
+ "symbol": symbol,
3748
+ "startDate": start_date,
3749
+ "endDate": end_date,
3750
+ "window": window,
3751
+ "adjusted": adjusted,
3752
+ "interval": interval
3753
+ })
3754
+
3755
+ logger.debug("Fetching rolling mean price for %s from %s to %s with window %d", symbol, start_date, end_date, window)
3756
+ return self._make_request("GET", self._routes["analytics.price.rolling_mean"], params=params)
3757
+
3758
+ def get_realized_volatility(
3759
+ self,
3760
+ symbol: str,
3761
+ start_date: str,
3762
+ end_date: str,
3763
+ adjusted: bool = True,
3764
+ interval: str = "daily"
3765
+ ) -> Dict[str, Any]:
3766
+ """
3767
+ Get realized price volatility for a stock.
3768
+
3769
+ Args:
3770
+ symbol (str): Stock symbol or instrument ID (e.g., NSE:ITC, BHARTIARTL).
3771
+ start_date (str): Start of volatility calculation window (YYYY-MM-DD).
3772
+ end_date (str): End of volatility calculation window (YYYY-MM-DD).
3773
+ adjusted (bool, optional): Adjust prices for corporate actions. Defaults to True.
3774
+ interval (str, optional): Time interval ('daily', 'weekly', 'monthly'). Defaults to 'daily'.
3775
+
3776
+ Returns:
3777
+ Dict[str, Any]: Realized volatility data.
3778
+
3779
+ Example Response:
3780
+ {
3781
+ "symbol": "NSE:ITC",
3782
+ "startDate": "2024-01-01",
3783
+ "endDate": "2024-06-30",
3784
+ "interval": "daily",
3785
+ "adjusted": true,
3786
+ "realizedVolatility": 22.45,
3787
+ "annualizedVolatility": 35.67,
3788
+ "unit": "%"
3789
+ }
3790
+ """
3791
+ params = self._normalize_params({
3792
+ "symbol": symbol,
3793
+ "startDate": start_date,
3794
+ "endDate": end_date,
3795
+ "adjusted": adjusted,
3796
+ "interval": interval
3797
+ })
3798
+
3799
+ logger.debug("Fetching realized volatility for %s from %s to %s", symbol, start_date, end_date)
3800
+ return self._make_request("GET", self._routes["analytics.volatility.realized"], params=params)
3801
+
3802
+ def get_beta_90d(
3803
+ self,
3804
+ symbol: str,
3805
+ benchmark: str = "NIFTY50"
3806
+ ) -> Dict[str, Any]:
3807
+ """
3808
+ Get 90-day CAPM Beta for a stock relative to a benchmark.
3809
+
3810
+ Args:
3811
+ symbol (str): Stock symbol (e.g., RELIANCE, ITC).
3812
+ benchmark (str, optional): Benchmark index. Defaults to 'NIFTY50'.
3813
+
3814
+ Returns:
3815
+ Dict[str, Any]: 90-day beta data.
3816
+
3817
+ Example Response:
3818
+ {
3819
+ "symbol": "ITC",
3820
+ "benchmark": "NIFTY50",
3821
+ "period": "90d",
3822
+ "beta": 0.85,
3823
+ "correlation": 0.72,
3824
+ "rSquared": 0.52,
3825
+ "alpha": 0.03,
3826
+ "unit": "ratio"
3827
+ }
3828
+ """
3829
+ params = self._normalize_params({
3830
+ "symbol": symbol,
3831
+ "benchmark": benchmark
3832
+ })
3833
+
3834
+ logger.debug("Fetching 90d beta for %s vs %s", symbol, benchmark)
3835
+ return self._make_request("GET", self._routes["analytics.risk.beta_90d"], params=params)
3836
+
3837
+ def get_beta_custom_period(
3838
+ self,
3839
+ symbol: str,
3840
+ benchmark: str,
3841
+ start_date: str,
3842
+ end_date: str
3843
+ ) -> Dict[str, Any]:
3844
+ """
3845
+ Get CAPM Beta for a stock relative to a benchmark over a custom period.
3846
+
3847
+ Args:
3848
+ symbol (str): Stock symbol.
3849
+ benchmark (str): Benchmark index symbol.
3850
+ start_date (str): Start date (YYYY-MM-DD).
3851
+ end_date (str): End date (YYYY-MM-DD).
3852
+
3853
+ Returns:
3854
+ Dict[str, Any]: Custom period beta data.
3855
+
3856
+ Example Response:
3857
+ {
3858
+ "symbol": "ITC",
3859
+ "benchmark": "NIFTY50",
3860
+ "startDate": "2023-01-01",
3861
+ "endDate": "2025-01-01",
3862
+ "beta": 0.87,
3863
+ "correlation": 0.74,
3864
+ "rSquared": 0.55,
3865
+ "alpha": 0.02,
3866
+ "unit": "ratio"
3867
+ }
3868
+ """
3869
+ params = self._normalize_params({
3870
+ "symbol": symbol,
3871
+ "benchmark": benchmark,
3872
+ "startDate": start_date,
3873
+ "endDate": end_date
3874
+ })
3875
+
3876
+ logger.debug("Fetching custom period beta for %s vs %s from %s to %s", symbol, benchmark, start_date, end_date)
3877
+ return self._make_request("GET", self._routes["analytics.risk.beta_custom"], params=params)