⌁tradecompanion
Markets Closed·01:20IST · Next open: 19 / 10 / 2026
🔔
Effective configuration, read-only. Edit the files in config/ and rerun tc run.
costs.yaml modified 2026-10-18 07:17
# Transaction cost tables. Percentages are of trade value unless noted. VERIFY against the broker's
# current charge sheet and NSE circulars before relying on expectancy numbers.
broker: zerodha
products:
  CNC:   # equity delivery
    brokerage_pct: 0.0
    brokerage_cap_inr: 0
    stt_buy_pct: 0.1
    stt_sell_pct: 0.1
    exchange_txn_pct: 0.00297
    sebi_fee_pct: 0.0001
    stamp_duty_buy_pct: 0.015
    gst_pct: 18                 # applied to brokerage + exchange txn + sebi fee
    dp_charge_sell_inr: 15.34   # per scrip per sell day
  MIS:   # equity intraday
    brokerage_pct: 0.03
    brokerage_cap_inr: 20
    stt_buy_pct: 0.0
    stt_sell_pct: 0.025
    exchange_txn_pct: 0.00297
    sebi_fee_pct: 0.0001
    stamp_duty_buy_pct: 0.003
    gst_pct: 18
    dp_charge_sell_inr: 0
fundamentals.yaml modified 2026-10-18 07:17
# How fundamentals are presented on the stock page (Fundamentals card) and the screener. Presentation only:
# the shield (shield_rules.yaml) and the F sub-score (scoring.yaml) decide; this file decides what the reader sees.
# Field names are the engine's own (see trendlyne.yaml parameter_map for how each is sourced).
# applies_to filters work like shield rules: a row is hidden when the stock's sector_type / bucket does not match.
# explain: one sentence a first-time reader sees on hover / in the Learn page.
# rule: optional shield rule id to show on the row when that rule tests a derived field (e.g. growth_trajectory).
source_label: Trendlyne

groups:
  - title: Quality
    fields:
      - { field: roce_3y_avg_pct,  label: ROCE, hint: 3-yr average, unit: "%", decimals: 1,
          explain: Return on capital employed. Operating profit per rupee the business ties up. Above 12% clears the shield; 20%+ is excellent. }
      - { field: roe_3y_avg_pct,   label: ROE, hint: 3-yr average, unit: "%", decimals: 1,
          explain: Return on equity. Profit per rupee of shareholder money. Above 12% clears the shield. }
      - { field: debt_to_equity,   label: Debt / equity, unit: "×", decimals: 2, applies_to: { sector_type: [non_financial] },
          explain: Total borrowings divided by equity. Below 0.5 means the business is not leaning on lenders. }
      - { field: capital_adequacy_ratio_pct, label: Capital adequacy, unit: "%", decimals: 1, applies_to: { sector_type: [financial] },
          explain: Capital a lender holds against its risk-weighted loans. Regulators want roughly 11%; the shield asks for 15%. }
      - { field: net_npa_pct,      label: Net NPA, unit: "%", decimals: 2, applies_to: { sector_type: [financial] },
          explain: Bad loans after provisions, as a share of the loan book. Below 1% is clean. }

  - title: Growth
    fields:
      - { field: revenue_cagr_3y_pct, label: Revenue growth, hint: 3-yr CAGR, unit: "%", decimals: 1, rule: growth_trajectory,
          explain: Compound annual growth in sales over three financial years. The shield wants both revenue and profit compounding. }
      - { field: pat_cagr_3y_pct,  label: Profit growth, hint: 3-yr CAGR, unit: "%", decimals: 1, rule: growth_trajectory,
          explain: Compound annual growth in net profit over three financial years. The shield wants both revenue and profit compounding. }

  - title: Who owns it
    fields:
      - { field: promoter_holding_pct, label: Promoter holding, unit: "%", decimals: 1,
          explain: Share held by the founders or parent. Skin in the game; the shield wants 40% for mid caps and 50% for small caps. }
      - { field: promoter_pledge_pct,  label: Promoter pledge, hint: of their holding, unit: "%", decimals: 1,
          explain: Share of the promoter stake pledged as loan collateral. Pledges above 2% can force selling when the price falls. }
      - { field: fii_pct,          label: Foreign institutions, hint: FII, unit: "%", decimals: 1,
          explain: Foreign portfolio investors. }
      - { field: dii_pct,          label: Domestic institutions, hint: DII, unit: "%", decimals: 1,
          explain: Mutual funds, insurers and other Indian institutions. }
      - { field: fii_dii_pct,      label: FII + DII, unit: "%", decimals: 1,
          explain: Combined institutional ownership. Above 10% means professionals have done the diligence. }
      - { field: fii_dii_change_4q_pp, label: Institutional change, hint: 4 quarters, unit: " pp", decimals: 2, signed: true,
          explain: Change in institutional holding over the last four quarters, in percentage points. Steady selling is a warning. }

# Columns added to the screener table, in order (fields from the groups above).
screener_columns: [roe_3y_avg_pct, debt_to_equity]

explanation: >
  Fundamentals are the balance-sheet side of the picture: how profitable the business is, whether it is growing,
  how much it owes, and who owns it. The shield turns the worst of these into hard disqualifiers; the F sub-score
  rewards the best. Ratios are annual and update when results are filed; holdings update each quarter.
holidays.yaml modified 2026-10-18 07:17
# Exchange calendar for the "Markets Open / Closed" chip in the dashboard header.
# Source: NSE trading-holiday circulars for the Capital Market segment (nseindia.com > Resources > Exchange
# communication > Holidays). NSE publishes next year's list in December; add the new year here, no code change.
# Dates missing from this file are treated as normal trading days, so a year not listed will show weekends only.

timezone: Asia/Kolkata
source: https://www.nseindia.com/resources/exchange-communication-holidays

hours:                 # IST, equity cash segment
  pre_open: "09:00"
  open: "09:15"
  close: "15:30"

holidays:
  # 2026 (NSE circulars NSE/CMTR/71200 series; 15 Jan added by addendum NSE/CMTR/72260)
  - {date: 2026-01-15, name: Municipal Corporation General Elections}
  - {date: 2026-01-26, name: Republic Day}
  - {date: 2026-02-15, name: Mahashivratri}                  # Sunday
  - {date: 2026-03-03, name: Holi}
  - {date: 2026-03-21, name: Id-Ul-Fitr (Ramadan Eid)}       # Saturday
  - {date: 2026-03-26, name: Shri Ram Navami}
  - {date: 2026-03-31, name: Shri Mahavir Jayanti}
  - {date: 2026-04-03, name: Good Friday}
  - {date: 2026-04-14, name: Dr. Baba Saheb Ambedkar Jayanti}
  - {date: 2026-05-01, name: Maharashtra Day}
  - {date: 2026-05-28, name: Bakri Id}
  - {date: 2026-06-26, name: Muharram}
  - {date: 2026-08-15, name: Independence Day}               # Saturday
  - {date: 2026-09-14, name: Ganesh Chaturthi}
  - {date: 2026-10-02, name: Mahatma Gandhi Jayanti}
  - {date: 2026-10-20, name: Dussehra}
  - {date: 2026-11-08, name: Diwali Laxmi Pujan}             # Sunday; Muhurat session, see special_sessions
  - {date: 2026-11-10, name: Diwali-Balipratipada}
  - {date: 2026-11-24, name: Prakash Gurpurb Sri Guru Nanak Dev}
  - {date: 2026-12-25, name: Christmas}

# Sessions held on an otherwise closed day, with their own hours (IST).
special_sessions:
  - {date: 2026-02-01, name: Union Budget special session, open: "09:15", close: "15:30"}   # Sunday, NSE/CMTR/72349
  # Muhurat trading 2026-11-08: NSE announces the one-hour evening slot by circular in late October. Add it here:
  # - {date: 2026-11-08, name: Muhurat trading, open: "18:15", close: "19:15"}
policy_tags.yaml modified 2026-10-18 07:17
# Hand-curated Government priority / incentivised sector mapping (v1). Refresh quarterly.
# A symbol is tagged if it appears in `symbols` or its `industry` (from the NSE index file) matches a sector.
sectors:
  - { name: Electronics manufacturing (PLI), industries: ["Consumer Durables", "Industrial Manufacturing"] }
  - { name: Renewable energy,                industries: ["Power"] }
  - { name: Defence,                         industries: ["Aerospace & Defense"] }
  - { name: Semiconductors,                  industries: [] }
  - { name: Electric vehicles / auto PLI,    industries: ["Automobile and Auto Components"] }
symbols: {}       # explicit overrides, e.g. { "DIXON": "Electronics manufacturing (PLI)" }
scoring.yaml modified 2026-10-18 07:17
# Composite = sum(weights[k] * subscore_k). Each sub-score is 0..10, built from weighted components.
# A component maps a raw field to 0..10 by piecewise-linear interpolation over `map: [[x, y], ...]`,
# clamped at both ends (absolute thresholds, not rank — see docs/05-decisions.md).
weights: { F: 0.30, T: 0.25, M: 0.20, B: 0.25 }
setup_threshold: 8.0
watchlist_threshold: 6.5
neutral_score: 5.0            # used when a sub-score has no data (e.g. M in v1)

policy_multiplier:            # Govt priority / PLI tag
  applies_to: M
  tagged: 1.10
  cap: 10.0

F:
  components:
    - { field: roce_3y_avg_pct,   weight: 0.30, map: [[12, 4], [20, 8], [30, 10]] }
    - { field: roe_3y_avg_pct,    weight: 0.30, map: [[12, 4], [20, 8], [30, 10]] }
    - { field: revenue_cagr_3y_pct, weight: 0.20, map: [[0, 2], [10, 6], [20, 10]] }
    - { field: pat_cagr_3y_pct,   weight: 0.20, map: [[0, 2], [10, 6], [25, 10]] }

T:
  components:
    - { field: ret20_pct,        weight: 0.40, map: [[-5, 2], [0, 5], [10, 9], [20, 10]] }
    - { field: dist_ema50_pct,   weight: 0.30, map: [[-5, 2], [0, 5], [8, 9], [15, 10]] }
    - { field: rel_volume,       weight: 0.30, map: [[1.0, 4], [1.5, 7], [2.5, 10]] }

B:
  min_trades: 8
  insufficient_sample_score: 4.0
  components:
    - { field: win_rate_pct,     weight: 0.60, map: [[40, 2], [50, 5], [60, 8], [70, 10]] }
    - { field: expectancy_r,     weight: 0.40, map: [[0, 3], [0.3, 6], [0.6, 10]] }

M:
  components:
    - { field: broker_consensus_score, weight: 0.50, map: [[0, 0], [10, 10]] }
    - { field: sentiment_score,        weight: 0.50, map: [[0, 0], [10, 10]] }
settings.yaml modified 2026-10-18 07:17
# Global knobs. Everything here is read at runtime; no code change needed to tune.

capital:
  deployed_inr: 50000000        # ~Rs 5 Cr
  risk_per_trade_pct: 0.75      # % of capital lost if the stop is hit
  max_position_pct: 10          # cap on a single name as % of capital
  max_adv_participation_pct: 2  # order value cap as % of 20-session avg traded value
  max_open_positions: 12

holding:
  product: CNC                  # CNC = delivery (swing). MIS = intraday. Drives the cost table.
  max_sessions: 5               # time-stop: exit at close of this many sessions after entry
  entry: next_open              # next_open | next_close  (where the simulated fill happens)

risk:
  atr_period: 14
  stop_atr_multiple: 1.5        # stop = entry - k * ATR
  target_r_multiple: 2.0        # first target = entry + R * (entry - stop)  => R:R 1:2
  extra_targets_r: [3.0]        # additional targets, in R multiples
  min_reward_risk: 2.0          # cards below this are suppressed
  entry_zone_atr_fraction: 0.25 # entry zone = reference ± fraction * ATR

backtest:
  lookback_sessions: 250
  slippage_pct: 0.10            # per side, on top of the broker cost table
  require_healthy_fundamentals: true   # count only signals on days the shield passed (point-in-time)
  fundamentals_fill: ffill_bfill       # ffill_bfill (fields with no earlier value are back-filled; mild look-ahead) | ffill
  stop_before_target_same_bar: true    # conservative tie-break when both are touched in one bar

data:
  fundamentals_provider: trendlyne_mcp   # trendlyne_mcp | none  (config in trendlyne.yaml)
  provider: nse                 # nse (free bhavcopy, unadjusted) | kite (Zerodha Kite Connect, pending)
  kite:
    api_key_env: KITE_API_KEY
    access_token_env: KITE_ACCESS_TOKEN

handoff:
  brokers: [zerodha, groww]
  kite_publisher_api_key_env: KITE_PUBLISHER_API_KEY   # optional; enables pre-filled basket links

schedule:
  timezone: Asia/Kolkata
  earliest_run: "18:30"
  retry_until: "22:00"
shield_rules.yaml modified 2026-10-18 07:17
# The "Wizard's Shield". Any failing rule disqualifies the stock (composite capped at disqualified_score_cap).
# Each rule: field (from the fundamentals/ownership snapshot), op, value, optional applies_to filters
# (bucket: large|mid|small, sector_type: financial|non_financial), on_missing: fail|skip (default fail).
disqualified_score_cap: 4.9
rules:
  - id: de_ratio
    description: Debt-to-Equity < 0.5 for non-financials
    field: debt_to_equity
    op: "<"
    value: 0.5
    applies_to: { sector_type: [non_financial] }

  - id: car
    description: Capital adequacy ratio > 15% for banks/NBFCs
    field: capital_adequacy_ratio_pct
    op: ">"
    value: 15
    applies_to: { sector_type: [financial] }

  - id: net_npa
    description: Net NPA < 1.0% for banks/NBFCs
    field: net_npa_pct
    op: "<"
    value: 1.0
    applies_to: { sector_type: [financial] }

  - id: promoter_holding_mid
    description: Promoter holding >= 40% (mid caps; waived for large caps per decision 2026-10-17)
    field: promoter_holding_pct
    op: ">="
    value: 40
    applies_to: { bucket: [mid] }

  - id: promoter_holding_small
    description: Promoter holding >= 50% (small caps)
    field: promoter_holding_pct
    op: ">="
    value: 50
    applies_to: { bucket: [small] }

  - id: promoter_pledge
    description: Promoter pledge < 2% of promoter holding
    field: promoter_pledge_pct
    op: "<"
    value: 2

  - id: institutional_holding
    description: FII + DII > 10% (mid and small caps)
    field: fii_dii_pct
    op: ">"
    value: 10
    applies_to: { bucket: [mid, small] }

  - id: institutional_trend
    description: FII + DII not falling by more than 0.5 pp over 4 quarters
    field: fii_dii_change_4q_pp
    op: ">="
    value: -0.5
    applies_to: { bucket: [mid, small] }
    on_missing: skip

  - id: surveillance
    description: Not in any ASM / GSM stage
    field: in_asm_gsm
    op: "=="
    value: false

  - id: audit_clean
    description: No audit qualification or mid-term auditor resignation in last 3 FY
    field: audit_red_flag_3y
    op: "=="
    value: false
    on_missing: skip      # TEMPORARY (decision 2026-10-17): no data source yet; revert to fail once sourced

  - id: roce_3y
    description: 3-year average ROCE >= 12% (non-financials)
    field: roce_3y_avg_pct
    op: ">="
    value: 12
    applies_to: { sector_type: [non_financial] }

  - id: roe_3y
    description: 3-year average ROE >= 12%
    field: roe_3y_avg_pct
    op: ">="
    value: 12

  - id: us_revenue
    description: US revenue share < 5% (mid and small; waived for large caps per decision 2026-10-17)
    field: us_revenue_share_pct
    op: "<"
    value: 5
    applies_to: { bucket: [mid, small] }
    on_missing: skip

  - id: growth_trajectory
    description: Positive revenue and PAT compounding over last 3 FY
    field: revenue_pat_positive_cagr_3y
    op: "=="
    value: true
    on_missing: skip
signals.yaml modified 2026-10-18 07:17
# Technical signal score shown on the screener and stock pages. Each check is a pandas expression over the
# OHLCV + indicator columns from triggers.yaml. Points sum to the score; labels are what the reader sees.
# This is deliberately simple and explainable; the composite conviction score (scoring.yaml) sits on top.
bullish_at: 2          # score >= 2 => bullish
bearish_at: -2         # score <= -2 => bearish
volume_confirm_x: 1.5  # breakout counts as volume-confirmed at >= this multiple of 20-day average volume
volume_spike_x: 2.0    # "volume spike" for breadth counts
explanation: >
  Score sums trend (close vs 50-day average), MACD vs its signal line, RSI momentum and 20-day
  breakouts/breakdowns. +2 or more is bullish, -2 or less is bearish. A breakout is volume-confirmed when
  volume is at least 1.5x its 20-day average. Informational only, not advice.
checks:
  - { id: trend_up,     when: "close > sma50",             points: 1,  label: "Price above 50-DMA" }
  - { id: trend_down,   when: "close < sma50",             points: -1, label: "Price below 50-DMA" }
  - { id: macd_up,      when: "macd_line > macd_signal",   points: 1,  label: "MACD above signal" }
  - { id: macd_down,    when: "macd_line < macd_signal",   points: -1, label: "MACD below signal" }
  - { id: rsi_up,       when: "rsi14 > 55",                points: 1,  label: "RSI momentum positive (>55)" }
  - { id: rsi_down,     when: "rsi14 < 45",                points: -1, label: "RSI momentum negative (<45)" }
  - { id: rsi_ob,       when: "rsi14 > 70",                points: 0,  label: "RSI overbought (>70)" }
  - { id: rsi_os,       when: "rsi14 < 30",                points: 0,  label: "RSI oversold (<30)" }
  - { id: breakout,     when: "close > hh20",              points: 1,  label: "Close above 20-day high" }
  - { id: breakdown,    when: "close < ll20",              points: -1, label: "Close below 20-day low" }
  - { id: vol_confirm,  when: "volume >= 1.5 * vol_avg20", points: 0,  label: "Volume above 1.5x 20-day average" }
trendlyne.yaml modified 2026-10-18 07:17
# Trendlyne MCP as the fundamentals/ownership source. Requires an MCP plan (Max recommended: 1,000 calls/day).
# Secrets never live here: the server URL (which carries your subscription identity) comes from the env var below.
connection:
  url_env: TRENDLYNE_MCP_URL            # paste the "MCP Server URL" from Trendlyne > profile > MCP page
  bearer_token_env: TRENDLYNE_MCP_TOKEN # optional; only if Trendlyne issues a separate token/header
  timeout_seconds: 60

budget:
  max_calls_per_run: 800                # stay under the plan's daily cap (Max = 1,000/day)
  symbols_per_call: 10                  # server limit: 10 stock codes per call
  params_per_call: 10                   # server limit: 10 parameter tokens per call

# Tool names pinned after `tc mcp-discover` on 2026-10-17.
tools:
  search:     { hint: "search_entities",             name: search_entities }
  parameters: { hint: "get_stock_parameter_values",  name: get_stock_parameter_values }
  ownership:  { hint: "get_ownership_deals_insider", name: get_ownership_deals_insider_sast }
  overview:   { hint: "get_overview_news_corp",      name: get_overview_news_corp_events }
  param_search: { hint: "search_financial_parameters", name: search_financial_parameters }

# get_stock_parameter_values accepts <= 10 stock codes and <= 10 parameter tokens per call.
# Our field name -> Trendlyne masked parameter token(s), tried in order. Resolved via
# search_financial_parameters on 2026-10-17 (see docs/08-trendlyne-mapping.md).
parameter_map:
  debt_to_equity:              ["debtcea"]                                  # Total Debt to Total Equity Annual
  roce_3y_avg_pct:             ["roceaave3"]                                # ROCE Annual 3Yr Avg %
  roe_3y_avg_pct:              ["roeaave3"]                                 # ROE Annual 3Yr Avg %
  revenue_cagr_3y_pct:         ["rev3a"]                                    # Revenue Annual 3Yr Growth %
  pat_cagr_3y_pct:             ["ni3a"]                                     # Net Profit 3Yr Growth %
  capital_adequacy_ratio_pct:  ["capitaladequacyratiosa"]                   # CAR Annual % (the Qtr token returns 0)
  net_npa_pct:                 ["nnparatq", "nnparata"]                     # Net NPA ratio % Qtr, Annual
  promoter_holding_pct:        ["prompct"]                                  # Promoter holding latest %
  promoter_pledge_pct:         ["prompledge"]                               # Promoter pledge % of holding, Qtr
  fii_pct:                     ["fiihold"]                                  # FII holding current Qtr %
  mf_pct:                      ["mfhold"]                                   # MF holding current Qtr %
  institutional_pct:           ["instihold"]                                # Institutional holding current Qtr %
  fii_dii_change_4q_pp:        ["instipct4q"]                               # Institutional holding change 4Qtr (pp)

# The parameter tool returns sections titled with abbreviated labels, in arbitrary order, so sections are
# matched to tokens by label. Observed labels (2026-10-17); unknown labels fall back to fuzzy matching against
# the helping_text returned by search_financial_parameters, and are logged.
labels:
  debtcea:                 ["Total Debt to Total Equity Ann."]
  roceaave3:               ["ROCE Ann. 3Y Avg %"]
  roeaave3:                ["ROE Ann. 3Y Avg %"]
  rev3a:                   ["Rev. Ann. 3Y Growth %"]
  ni3a:                    ["Net Profit 3Y Growth %"]
  capitaladequacyratiosa:  ["Capital Adequacy Ratios Ann. %"]
  nnparatq:                ["Net NPA ratio % Qtr"]
  nnparata:                ["Net NPA % Ann. %"]
  prompct:                 ["Promoter holding latest %"]
  prompledge:              ["Promoter holding pledge percentage % Qtr"]
  fiihold:                 ["FII holding current Qtr %"]
  mfhold:                  ["MF holding current Qtr %"]
  instihold:               ["Institutional holding current Qtr %"]
  instipct4q:              ["Institutional holding change 1Y %", "Institutional holding change 4Qtr %"]

derived:                         # computed after fetch (Python expressions over fetched fields)
  fii_dii_pct: "institutional_pct"          # Trendlyne 'Institutional' = FII + DII (verified: TCS 9.07 + 13.4 ~ 22.5)
  revenue_pat_positive_cagr_3y: "revenue_cagr_3y_pct > 0 and pat_cagr_3y_pct > 0"

# get_ownership_deals_insider_sast(type=shareholding) returns ~6 quarters of promoter / pledge / FII / DII /
# institutional history. One call per symbol; builds point-in-time ownership snapshots for the backtest.
ownership_history:
  enabled: true
  filing_lag_days: 21            # as_of = quarter end + lag (shareholding patterns are due within 21 days)
triggers.yaml modified 2026-10-18 07:17
# Technical triggers are rules, not code. Indicators are computed first (by name), then each trigger's
# `entry` expression is evaluated with pandas DataFrame.eval over the OHLCV columns plus indicator columns.
# Available indicator fns: sma, ema, rolling_max, rolling_min, rolling_mean, atr, rsi, pct_change.
# `shift: 1` means "as of the previous session" (use it to avoid including today's bar in a breakout level).
indicators:
  - { name: hh20,      fn: rolling_max,  column: high,   window: 20, shift: 1 }
  - { name: vol_avg20, fn: rolling_mean, column: volume, window: 20, shift: 1 }
  - { name: tv_avg20,  fn: rolling_mean, column: traded_value, window: 20, shift: 1 }
  - { name: atr14,     fn: atr,          window: 14 }
  - { name: ema20,     fn: ema,          column: close,  window: 20 }
  - { name: ema50,     fn: ema,          column: close,  window: 50 }
  - { name: rsi14,     fn: rsi,          column: close,  window: 14 }
  - { name: ret20,     fn: pct_change,   column: close,  window: 20 }
  - { name: ll20,      fn: rolling_min,  column: low,    window: 20, shift: 1 }
  - { name: sma20,     fn: sma,          column: close,  window: 20 }
  - { name: sma50,     fn: sma,          column: close,  window: 50 }
  - { name: sma200,    fn: sma,          column: close,  window: 200 }
  - { name: macd_line, fn: macd_line,    column: close }
  - { name: macd_signal, fn: macd_signal, column: close }
  - { name: macd_hist, fn: macd_hist,    column: close }
  - { name: chg1d,     fn: day_change_pct, column: close }

triggers:
  - id: breakout_20d_volume
    enabled: true
    description: Close above prior 20-session high with volume > 1.5x the prior 20-session average
    entry: "close > hh20 and volume > 1.5 * vol_avg20"

  - id: ema_pullback
    enabled: false
    description: Uptrend (close > EMA50), dips to EMA20 and closes back above it
    entry: "close > ema50 and low <= ema20 and close > ema20"

combine: any      # any | all  (how multiple enabled triggers are merged into one signal)
universe.yaml modified 2026-10-18 07:17
# Universe construction. Buckets are filled from an NSE index list, filtered by liquidity, then
# ranked by average traded value. target_count is a ceiling, not a promise (see docs/04-caveats.md §3).
traded_value_window_sessions: 125     # ~6 months
buckets:
  large:
    target_count: 20
    source_index: NIFTY 50
    min_avg_traded_value_cr: 0
  mid:
    target_count: 50
    source_index: NIFTY MIDCAP 100
    min_avg_traded_value_cr: 50
  small:
    target_count: 100
    source_index: NIFTY SMALLCAP 250
    min_avg_traded_value_cr: 10
overrides:
  include: {}      # symbol: bucket   e.g. { "KAYNES": small }
  exclude: []      # symbols never to trade