Metadata-Version: 2.4
Name: eqldata
Version: 1.7.1
Summary: Official Python client for Equal Solution realtime and historical market data
Author-email: Equalsolution <info@equalsolution.com>
License-Expression: MIT
Project-URL: Homepage, https://equalsolution.com
Project-URL: Repository, https://equalsolution.com
Project-URL: Documentation, https://pypi.org/project/eqldata/
Keywords: market-data,trading,realtime,historical-data,ohlc,eod,websocket,equal-solution
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.25.0
Requires-Dist: websocket-client>=1.2.0
Dynamic: license-file

# eqldata

Official Python client for Equal Solution realtime and historical market data.

`eqldata` is designed for traders, developers, analysts, charting tools, and backtesting workflows that need simple access to Equal Solution APIs from Python.

## What this package supports

- Login and API token generation
- Instrument list download
- Realtime websocket subscription
- Single-day EOD download
- Range-based EOD download
- After-market / current-day 1-minute data download
- Reading EOD range ZIP output in plain Python
- Reading EOD range ZIP output with pandas

---

## Installation

```bash
pip install eqldata
```

Upgrade to the latest version:

```bash
pip install --upgrade eqldata
```

Check installed version:

```python
import eqldata
print(eqldata.__version__)
```

---

## Important API session rule

Only the latest successful login token remains valid.

If the same user logs in again from another PC, server, or script, the previous API token becomes invalid automatically.

This protects the business rule:

```text
A client may login from any PC, but only one active API session is allowed at a time.
```

Use one fresh token per running client/script.

---

## Instrument naming

Always use exchange/segment-prefixed instruments.

Examples:

```python
"NSEEQ:ABB"
"NSEEQ:RELIANCE"
"NSEIDX:NIFTY_50"
"NSEIDX:INDIA_VIX"
"MCXFUT:CRUDEOILM_I"
```

Do not pass raw names like `"ABB"` unless a specific helper script adds the prefix for you.

---

## Function index

```python
from eqldata import (
    DataClient,
    EqualDataError,
    generate_auth_token,
    get_instrument_list,
    get_EOD,
    get_EOD_range,
    get_1MARKET_DATA,
    read_EOD_range_metadata,
    read_EOD_range_csv,
    read_EOD_range_csv_rows,
)
```

| Function | Purpose |
|---|---|
| `generate_auth_token()` | Login and get API token |
| `get_instrument_list()` | Get instruments available for the account |
| `DataClient` | Realtime websocket client |
| `get_EOD()` | Single-day EOD download |
| `get_EOD_range()` | Date-range EOD ZIP download |
| `get_1MARKET_DATA()` | 1-minute data for one date |
| `read_EOD_range_metadata()` | Read metadata from EOD range ZIP |
| `read_EOD_range_csv()` | Read CSV rows from EOD range ZIP |
| `read_EOD_range_csv_rows()` | Same as `read_EOD_range_csv()` |

---

## 1. Login and generate token

```python
from getpass import getpass
from eqldata import generate_auth_token

email = input("Email: ").strip()
password = getpass("Password: ")

auth_token = generate_auth_token(email, password)

if not auth_token:
    raise SystemExit("Login failed")

print("Login successful")
print("Token length:", len(auth_token))
```

Production style with error raising:

```python
from getpass import getpass
from eqldata import generate_auth_token, EqualDataError

try:
    auth_token = generate_auth_token(
        input("Email: ").strip(),
        getpass("Password: "),
        raise_on_error=True,
    )
    print("Login successful")

except EqualDataError as e:
    print("API login failed:", e)
```

---

## 2. Get instrument list

```python
from getpass import getpass
from eqldata import generate_auth_token, get_instrument_list

email = input("Email: ").strip()
password = getpass("Password: ")

auth_token = generate_auth_token(email, password)

instruments = get_instrument_list(auth_token)

print(type(instruments))
print(instruments)
```

Print only NSE equity instruments:

```python
from getpass import getpass
from eqldata import generate_auth_token, get_instrument_list

auth_token = generate_auth_token(input("Email: ").strip(), getpass("Password: "))

data = get_instrument_list(auth_token)

nseeq = data.get("NSEEQ", []) if isinstance(data, dict) else []

print("NSEEQ count:", len(nseeq))
print("First 20 instruments:")
for item in nseeq[:20]:
    print(item)
```

---

## 3. Realtime websocket data

```python
import json
from getpass import getpass
from eqldata import generate_auth_token, DataClient

auth_token = generate_auth_token(input("Email: ").strip(), getpass("Password: "))

topics = [
    "NSEIDX:NIFTY_50",
    "NSEIDX:INDIA_VIX",
]

client = DataClient(auth_token, topics)

try:
    while True:
        message = client.listen()
        print("RAW:", message)

        try:
            data = json.loads(message)
            print("JSON:", data)
        except Exception:
            pass

except KeyboardInterrupt:
    print("Stopping...")
    client.stop_listening()
    client.disconnect()
```

---

## 4. Single-day EOD download

Use `get_EOD()` for one specific date and one or more instruments.

```python
from getpass import getpass
from eqldata import generate_auth_token, get_EOD

auth_token = generate_auth_token(input("Email: ").strip(), getpass("Password: "))

instruments = [
    "NSEEQ:ABB",
    "NSEIDX:NIFTY_50",
    "NSEIDX:INDIA_VIX",
]

for_date = "2026-06-22"

result = get_EOD(auth_token, instruments, for_date)

print(type(result))
print(result)
```

Save single-day EOD rows to CSV:

```python
import csv
from getpass import getpass
from eqldata import generate_auth_token, get_EOD

auth_token = generate_auth_token(input("Email: ").strip(), getpass("Password: "))

instruments = ["NSEEQ:ABB", "NSEIDX:NIFTY_50"]
for_date = "2026-06-22"

result = get_EOD(auth_token, instruments, for_date)

with open("single_day_eod.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["INSTRUMENT", "DTYYYYMMDD", "OPEN", "HIGH", "LOW", "CLOSE", "VOL", "OPENINT"])

    for group in result:
        for row in group:
            writer.writerow(row)

print("Saved: single_day_eod.csv")
```

---

## 5. EOD range download

Use `get_EOD_range()` for multiple dates and multiple instruments.

The API returns a ZIP file containing:

```text
EOD_RANGE.csv
EOD_RANGE_META.json
```

Basic example:

```python
from getpass import getpass
from eqldata import generate_auth_token, get_EOD_range

auth_token = generate_auth_token(input("Email: ").strip(), getpass("Password: "))

zip_path = get_EOD_range(
    auth_token=auth_token,
    instruments=[
        "NSEEQ:ABB",
        "NSEIDX:NIFTY_50",
        "NSEIDX:INDIA_VIX",
    ],
    from_date="2026-06-10",
    to_date="2026-06-22",
    output_path="EOD_RANGE.zip",
)

print("Downloaded:", zip_path)
```

### EOD range public limits

| Limit | Value |
|---|---:|
| Maximum instruments per request | 500 |
| Maximum calendar days per request | 366 |
| Output format | ZIP |
| CSV file inside ZIP | `EOD_RANGE.csv` |
| Metadata file inside ZIP | `EOD_RANGE_META.json` |

If your account has 5, 10, or 20 years of history access, download in yearly chunks.

---

## 6. Read EOD range metadata

```python
from getpass import getpass
from eqldata import generate_auth_token, get_EOD_range, read_EOD_range_metadata

auth_token = generate_auth_token(input("Email: ").strip(), getpass("Password: "))

zip_path = get_EOD_range(
    auth_token,
    ["NSEEQ:ABB", "NSEIDX:NIFTY_50"],
    "2026-06-10",
    "2026-06-22",
    output_path="EOD_RANGE.zip",
)

meta = read_EOD_range_metadata(zip_path)

print(meta)
```

Example metadata keys may include:

```text
from_date
to_date
symbols_requested
files_scanned
rows_written
format
compression
```

---

## 7. Read EOD range CSV rows

```python
from getpass import getpass
from eqldata import (
    generate_auth_token,
    get_EOD_range,
    read_EOD_range_csv,
)

auth_token = generate_auth_token(input("Email: ").strip(), getpass("Password: "))

zip_path = get_EOD_range(
    auth_token,
    ["NSEEQ:ABB", "NSEIDX:NIFTY_50"],
    "2026-06-10",
    "2026-06-22",
    output_path="EOD_RANGE.zip",
)

rows = read_EOD_range_csv(zip_path)

print("Rows:", len(rows))
print("First row:", rows[0] if rows else None)
```

Expected CSV columns:

```text
INSTRUMENT,DTYYYYMMDD,OPEN,HIGH,LOW,CLOSE,VOL,OPENINT
```

---

## 8. Read EOD range ZIP with pandas

```python
from getpass import getpass
import zipfile
import pandas as pd
from eqldata import generate_auth_token, get_EOD_range

auth_token = generate_auth_token(input("Email: ").strip(), getpass("Password: "))

zip_path = get_EOD_range(
    auth_token=auth_token,
    instruments=["NSEEQ:ABB", "NSEIDX:NIFTY_50"],
    from_date="2026-06-10",
    to_date="2026-06-22",
    output_path="EOD_RANGE.zip",
)

with zipfile.ZipFile(zip_path, "r") as z:
    with z.open("EOD_RANGE.csv") as f:
        df = pd.read_csv(f)

df["DTYYYYMMDD"] = pd.to_datetime(df["DTYYYYMMDD"].astype(str), format="%Y%m%d")

print(df.head())
print(df.tail())
print(df.shape)
```

Install pandas if needed:

```bash
pip install pandas
```

---

## 9. Download multiple years in yearly chunks

Because each EOD range request is capped to 366 calendar days, large history should be downloaded year by year.

```python
from getpass import getpass
from pathlib import Path
from eqldata import generate_auth_token, get_EOD_range

auth_token = generate_auth_token(input("Email: ").strip(), getpass("Password: "))

instruments = [
    "NSEEQ:ABB",
    "NSEEQ:RELIANCE",
    "NSEIDX:NIFTY_50",
]

ranges = [
    ("2022-01-01", "2022-12-31"),
    ("2023-01-01", "2023-12-31"),
    ("2024-01-01", "2024-12-31"),
    ("2025-01-01", "2025-12-31"),
]

out_dir = Path("eod_yearly_downloads")
out_dir.mkdir(exist_ok=True)

for from_date, to_date in ranges:
    out_file = out_dir / f"EOD_{from_date}_to_{to_date}.zip"

    zip_path = get_EOD_range(
        auth_token=auth_token,
        instruments=instruments,
        from_date=from_date,
        to_date=to_date,
        output_path=out_file,
    )

    print("Downloaded:", zip_path)
```

---

## 10. 1-minute market data

Use `get_1MARKET_DATA()` for 1-minute data for one date.

```python
from getpass import getpass
from eqldata import generate_auth_token, get_1MARKET_DATA

auth_token = generate_auth_token(input("Email: ").strip(), getpass("Password: "))

instruments = [
    "NSEIDX:NIFTY_50",
    "NSEIDX:INDIA_VIX",
]

for_date = "2026-06-22"

result = get_1MARKET_DATA(auth_token, instruments, for_date)

print(type(result))
print(result)
```

Save 1-minute data to CSV:

```python
import csv
from getpass import getpass
from eqldata import generate_auth_token, get_1MARKET_DATA

auth_token = generate_auth_token(input("Email: ").strip(), getpass("Password: "))

result = get_1MARKET_DATA(auth_token, ["NSEIDX:NIFTY_50"], "2026-06-22")

with open("nifty_1min.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["INSTRUMENT", "TIMESTAMP", "OPEN", "HIGH", "LOW", "CLOSE", "VOL", "OPENINT"])

    for group in result:
        for row in group:
            writer.writerow(row)

print("Saved: nifty_1min.csv")
```

---

## 11. Error handling

Use `raise_on_error=True` when you want exceptions instead of returned error text.

```python
from getpass import getpass
from eqldata import generate_auth_token, get_EOD_range, EqualDataError

try:
    auth_token = generate_auth_token(
        input("Email: ").strip(),
        getpass("Password: "),
        raise_on_error=True,
    )

    zip_path = get_EOD_range(
        auth_token=auth_token,
        instruments=["NSEEQ:ABB"],
        from_date="2026-06-10",
        to_date="2026-06-22",
        output_path="EOD_RANGE.zip",
        raise_on_error=True,
    )

    print("Downloaded:", zip_path)

except EqualDataError as e:
    print("Equal Solution API error:", e)

except Exception as e:
    print("Unexpected error:", e)
```

Common API errors:

| Error | Meaning |
|---|---|
| `INVALID_TOKEN` | Login again and use the latest token |
| `MAX_DAYS_EXCEEDED` | Keep EOD range within 366 calendar days |
| `MAX_SYMBOLS_EXCEEDED` | Keep instruments within 500 per call |
| `HISTORY_RANGE_NOT_ALLOWED` | Your account is not entitled to that old history |
| `RATE_LIMIT_EXCEEDED` | Wait and retry later |
| `EOD_RANGE_BUSY` | Another range job is already running for the user or server |

---

## Examples folder

This package includes runnable sample scripts in the `examples/` folder:

```text
examples/
  01_login.py
  02_get_instrument_list.py
  03_get_eod_single_day.py
  04_get_eod_range_download_zip.py
  05_get_eod_range_pandas.py
  06_get_1market_data_single_day.py
  07_check_1min_range_save_csv.py
  08_realtime_websocket_subscribe.py
  09_error_handling.py
  10_eod_range_yearly_chunks.py
```

Run any example like this:

```bash
python examples/04_get_eod_range_download_zip.py
```

---

## Sample projects folder

Version 1.7.1 also includes more complete sample projects in the source distribution.
These projects are useful for clients who want a ready copy-paste starting point instead of only short code snippets.

```text
sample_projects/
  eod_range_pandas_project/
    main.py
    README.md
    requirements.txt

  one_minute_history_checker_project/
    main.py
    README.md
    requirements.txt

  realtime_websocket_project/
    main.py
    README.md
    requirements.txt
```

### EOD range pandas project

This project logs in, downloads EOD range ZIP data, reads `EOD_RANGE.csv`, converts the date column, and saves the final pandas DataFrame to CSV.

```bash
cd sample_projects/eod_range_pandas_project
pip install -r requirements.txt
python main.py
```

### 1-minute history checker project

This project checks 1-minute historical data date-by-date, saves daily CSV files, and writes a merged CSV file.

```bash
cd sample_projects/one_minute_history_checker_project
pip install -r requirements.txt
python main.py
```

### Realtime websocket project

This project logs in and subscribes to live market data using `DataClient`.

```bash
cd sample_projects/realtime_websocket_project
pip install -r requirements.txt
python main.py
```

---

## Support

For subscription, entitlement, or API access issues, contact Equal Solution support.

Website: https://equalsolution.com

Email: info@equalsolution.com

---

## License

MIT License.
