Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions interactive_examples/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Upstox Python Interactive Examples

> **47 working examples** showcasing Upstox API features — **Instrument Search**, **Analytics Token**, and **Market Data** — across futures spreads, options strategies, arbitrage, historical analysis, live market data, and more.
> **55 working examples** showcasing Upstox API features — **Instrument Search**, **Analytics Token**, **Market Data**, and **Fundamentals** — across futures spreads, options strategies, arbitrage, historical analysis, live market data, fundamentals analysis, and more.

[![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue)](https://www.python.org/)
[![upstox-python-sdk](https://img.shields.io/pypi/v/upstox-python-sdk?label=upstox-python-sdk)](https://pypi.org/project/upstox-python-sdk/)
Expand All @@ -22,7 +22,7 @@ python instrument_search/search_equity.py --token <TOKEN> --query RELIANCE

A Streamlit web app wraps every example with a UI — paste your token and run:

> **[▶ Open Live App](https://upstox-python-examples.streamlit.app)** *(coming soon — deploy steps below)*
> **[▶ Open Live App](https://upstox-interactive-python.streamlit.app/)**

Or run locally:

Expand Down Expand Up @@ -225,10 +225,58 @@ python market_data/live_depth_mcx.py --token <TOKEN> # Ctrl-C to stop

---

### Fundamentals Analysis
*Uses the [Upstox Fundamentals API](https://upstox.com/developer/api-documentation/fundamentals). Pass `--symbol` with a stock name — the ISIN is resolved automatically.*

| Script | What it does |
|---|---|
| `fundamentals/company_profile.py` | Sector, industry, market cap, employees and business overview |
| `fundamentals/key_ratios.py` | P/E, P/B, ROE, ROCE, D/E and more vs sector average |
| `fundamentals/balance_sheet.py` | Historical total assets, liabilities and derived equity |
| `fundamentals/income_statement.py` | Revenue, operating profit, net profit and EPS over time |
| `fundamentals/cash_flow.py` | Operating, investing and financing cash flows across periods |
| `fundamentals/corporate_actions.py` | Dividends, stock splits, bonuses — sorted most-recent first |
| `fundamentals/share_holdings.py` | Quarterly promoter / FII / DII / public shareholding |
| `fundamentals/competitors.py` | Peer companies in the same sector with market cap comparison |

```bash
python fundamentals/company_profile.py --token <TOKEN> --symbol RELIANCE
python fundamentals/key_ratios.py --token <TOKEN> --symbol TCS
python fundamentals/balance_sheet.py --token <TOKEN> --symbol HDFCBANK
python fundamentals/income_statement.py --token <TOKEN> --symbol INFY
python fundamentals/cash_flow.py --token <TOKEN> --symbol WIPRO
python fundamentals/corporate_actions.py --token <TOKEN> --symbol HDFCBANK
python fundamentals/share_holdings.py --token <TOKEN> --symbol RELIANCE
python fundamentals/competitors.py --token <TOKEN> --symbol TCS
```

### Market Information
*Uses the [Upstox Market Information API](https://upstox.com/developer/api-documentation/market-information). Note: **Market Holidays**, **Market Timings**, and **Exchange Status** live under `market_data/` above.*

| Script | What it does |
|---|---|
| `market_information/fii_data.py` | FII buy / sell / OI activity by segment (cash, futures, options) and interval |
| `market_information/dii_data.py` | DII buy / sell flow for the NSE cash market |
| `market_information/oi_data.py` | Per-strike call / put open interest for an underlying + expiry |
| `market_information/change_oi.py` | Per-strike change in OI over a configurable lookback |
| `market_information/max_pain.py` | Max pain strike + intraday max-pain vs spot |
| `market_information/pcr_data.py` | Overall PCR + intraday PCR / spot data points |

```bash
python market_information/fii_data.py --token <TOKEN> --data-type "NSE_EQ|CASH" --interval 1D
python market_information/dii_data.py --token <TOKEN> --interval 1M
python market_information/oi_data.py --token <TOKEN> --expiry 2026-05-29
python market_information/change_oi.py --token <TOKEN> --expiry 2026-05-29 --interval 5
python market_information/max_pain.py --token <TOKEN> --expiry 2026-05-29 --bucket-interval 60
python market_information/pcr_data.py --token <TOKEN> --expiry 2026-05-29 --bucket-interval 60
```

---


## 🌐 Deploy the Streamlit App

The `streamlit_app.py` wraps all 39 examples in a browser UI with interactive inputs and charts.
The `streamlit_app.py` wraps all 47 examples in a browser UI with interactive inputs and charts (including Plotly charts for fundamentals).

### Streamlit Cloud (free, ~5 minutes)

Expand Down Expand Up @@ -260,7 +308,9 @@ interactive_examples/
├── arbitrage/ # 3 scripts
├── historical_analysis/ # 7 scripts
├── portfolio_screening/ # 3 scripts
└── market_data/ # 8 scripts
├── market_data/ # 8 scripts
├── fundamentals/ # 8 scripts
└── market_information/ # 6 scripts
```

---
Expand Down
148 changes: 148 additions & 0 deletions interactive_examples/fundamentals/balance_sheet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"""
Balance Sheet — historical total assets, liabilities and equity over time.

Fetches balance sheet history from the Upstox Fundamentals API and displays
assets vs liabilities for each reporting period.

Usage:
python fundamentals/balance_sheet.py --token <TOKEN>
python fundamentals/balance_sheet.py --token <TOKEN> --symbol HDFCBANK
"""

import argparse
import sys
import os

sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from utils import get_api_client, search_instrument, die
import upstox_client

BOLD = "\033[1m"
GREEN = "\033[32m"
CYAN = "\033[36m"
RESET = "\033[0m"


def resolve_symbol(client, symbol: str):
resp = search_instrument(client, symbol, exchanges="NSE", segments="EQ", records=1)
hits = resp.data or []
if not hits:
die(f"No NSE equity instrument found for '{symbol}'.")
return hits[0].get("isin", ""), hits[0].get("instrument_key", "")


def _val(obj, key, default="—"):
if obj is None:
return default
if isinstance(obj, dict):
v = obj.get(key)
else:
v = getattr(obj, key, None)
return v if v is not None else default


def _fmt(v):
if v in (None, "—", ""):
return "—"
try:
return f"{float(v):>16,.2f}"
except (TypeError, ValueError):
return str(v)


def main():
parser = argparse.ArgumentParser(description="Balance Sheet via Fundamentals API")
parser.add_argument("--token", required=True, help="Upstox access or analytics token")
parser.add_argument("--symbol", default="RELIANCE", help="Stock symbol (default: RELIANCE)")
parser.add_argument("--type", default="consolidated",
choices=("consolidated", "standalone"),
help="Statement type (default: consolidated)")
parser.add_argument("--fs", default="false", choices=("true", "false"),
help="Full statement toggle — include detailed line-item breakdown (default: false)")
args = parser.parse_args()

client = get_api_client(args.token)
isin, _ = resolve_symbol(client, args.symbol)
if not isin:
die(f"Could not resolve ISIN for '{args.symbol}'.")

print(f"\nFetching balance sheet for {args.symbol.upper()} (ISIN: {isin})"
f" — type={args.type}, fs={args.fs}...\n")

api = upstox_client.FundamentalsApi(client)
try:
response = api.get_balance_sheet(isin, type=args.type, fs=args.fs)
except Exception as e:
die(f"API error: {e}")

data = response.data
if not data:
die("No data returned.")

raw = data.to_dict() if hasattr(data, "to_dict") else (data if isinstance(data, dict) else vars(data))

units = raw.get("units_in") or ""
period = raw.get("time_period") or ""
history = raw.get("history") or []

units_label = f" (in {units})" if units else ""
print(f" Period type : {period or '—'}")
print(f" Units : {units or '—'}")
print()

if history:
col_w = 14
print(f" {BOLD}{'Period':<{col_w}} {'Total Assets':>18} {'Total Liabilities':>20} {'Equity':>18}{RESET}")
print(" " + "─" * 72)

for entry in history:
if hasattr(entry, "to_dict"):
entry = entry.to_dict()
elif not isinstance(entry, dict):
entry = vars(entry) if hasattr(entry, "__dict__") else {}

p = str(_val(entry, "period", "—"))
ta = _val(entry, "total_asset")
tl = _val(entry, "total_liability")
eq = None
try:
if ta not in (None, "—") and tl not in (None, "—"):
eq = float(ta) - float(tl)
except (TypeError, ValueError):
pass

ta_s = _fmt(ta).strip()
tl_s = _fmt(tl).strip()
eq_s = _fmt(eq).strip() if eq is not None else "—"

print(f" {CYAN}{p:<{col_w}}{RESET} {ta_s:>18} {tl_s:>20} {GREEN}{eq_s:>18}{RESET}")
else:
# fall back to full_statement if history is empty
full = raw.get("full_statement") or []
if not full:
die("No balance sheet data in response.")

items = full if isinstance(full, list) else [full]
print(f" {BOLD}{'Particular':<40} {'Value':>18}{RESET}")
print(" " + "─" * 62)
for entry in items:
if hasattr(entry, "to_dict"):
entry = entry.to_dict()
elif not isinstance(entry, dict):
entry = vars(entry) if hasattr(entry, "__dict__") else {}
particular = str(entry.get("particular") or "—")
hist_vals = entry.get("history") or []
last = hist_vals[-1] if hist_vals else None
if isinstance(last, dict):
val = last.get("value")
elif hasattr(last, "to_dict"):
val = last.to_dict().get("value")
else:
val = last
print(f" {CYAN}{particular:<40}{RESET} {_fmt(val).strip():>18}")

print()


if __name__ == "__main__":
main()
154 changes: 154 additions & 0 deletions interactive_examples/fundamentals/cash_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
"""
Cash Flow — operating, investing and financing cash flows across periods.

Fetches the cash flow statement from the Upstox Fundamentals API and displays
operating / investing / financing activities alongside net cash flow over time.

Usage:
python fundamentals/cash_flow.py --token <TOKEN>
python fundamentals/cash_flow.py --token <TOKEN> --symbol WIPRO
"""

import argparse
import sys
import os

sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
from utils import get_api_client, search_instrument, die
import upstox_client

BOLD = "\033[1m"
GREEN = "\033[32m"
RED = "\033[31m"
CYAN = "\033[36m"
RESET = "\033[0m"

PRIORITY_ITEMS = (
"operating", "cash from operations", "net cash from operating",
"investing", "cash from investing", "net cash from investing",
"financing", "cash from financing", "net cash from financing",
"net cash", "net change",
)


def resolve_symbol(client, symbol: str):
resp = search_instrument(client, symbol, exchanges="NSE", segments="EQ", records=1)
hits = resp.data or []
if not hits:
die(f"No NSE equity instrument found for '{symbol}'.")
return hits[0].get("isin", ""), hits[0].get("instrument_key", "")


def _fmt(v):
if v in (None, "", "—"):
return "—"
try:
f = float(v)
color = GREEN if f > 0 else (RED if f < 0 else "")
return f"{color}{f:>14,.2f}{RESET}"
except (TypeError, ValueError):
return str(v)[:14]


def _is_priority(name: str) -> bool:
nl = name.lower()
return any(k in nl for k in PRIORITY_ITEMS)


def main():
parser = argparse.ArgumentParser(description="Cash Flow via Fundamentals API")
parser.add_argument("--token", required=True, help="Upstox access or analytics token")
parser.add_argument("--symbol", default="RELIANCE", help="Stock symbol (default: RELIANCE)")
parser.add_argument("--type", default="consolidated",
choices=("consolidated", "standalone"),
help="Statement type (default: consolidated)")
parser.add_argument("--fs", default="false", choices=("true", "false"),
help="Full statement toggle — include detailed line-item breakdown (default: false)")
args = parser.parse_args()

client = get_api_client(args.token)
isin, _ = resolve_symbol(client, args.symbol)
if not isin:
die(f"Could not resolve ISIN for '{args.symbol}'.")

print(f"\nFetching cash flow statement for {args.symbol.upper()} (ISIN: {isin})"
f" — type={args.type}, fs={args.fs}...\n")

api = upstox_client.FundamentalsApi(client)
try:
response = api.get_cash_flow(isin, type=args.type, fs=args.fs)
except Exception as e:
die(f"API error: {e}")

data = response.data
if not data:
die("No data returned.")

raw = data.to_dict() if hasattr(data, "to_dict") else (data if isinstance(data, dict) else vars(data))

units = raw.get("units_in") or ""
period = raw.get("time_period") or ""
stmts = raw.get("cash_flow") or raw.get("full_statement") or []

print(f" Period type : {period or '—'}")
print(f" Units : {units or '—'}")
print()

if not stmts:
die("No cash flow entries found.")

items = stmts if isinstance(stmts, list) else [stmts]

def _flat(hist):
out = []
for h in (hist or []):
if hasattr(h, "to_dict"):
h = h.to_dict()
if isinstance(h, dict):
out.append((h.get("period"), h.get("value")))
else:
out.append((None, h))
return out

entries = []
periods = []
max_cols = 6

for item in items:
if hasattr(item, "to_dict"):
item = item.to_dict()
elif not isinstance(item, dict):
item = vars(item) if hasattr(item, "__dict__") else {}
name = str(item.get("category") or item.get("particular") or item.get("name") or "—")
flat = _flat(item.get("history"))
if not periods and flat:
periods = [p if p is not None else f"P{i+1}" for i, (p, _) in enumerate(flat)]
entries.append((name, [v for _, v in flat]))

if not entries:
die("No line items found.")

periods = periods[:max_cols]
col_w = 16
label_w = 38

header_periods = " ".join(f"{str(p)[:col_w]:>{col_w}}" for p in periods)
print(f" {BOLD}{'Particular':<{label_w}} {header_periods}{RESET}")
print(" " + "─" * (label_w + (col_w + 2) * len(periods) + 12))

priority = [(n, h) for n, h in entries if _is_priority(n)]
rest = [(n, h) for n, h in entries if not _is_priority(n)]

for name, hist in priority + rest:
vals = (hist or [])[:max_cols]
while len(vals) < len(periods):
vals.append(None)
row_vals = " ".join(f"{_fmt(v):>{col_w + 9}}" for v in vals)
color = CYAN if _is_priority(name) else ""
print(f" {color}{str(name):<{label_w}}{RESET} {row_vals}")

print()


if __name__ == "__main__":
main()
Loading
Loading