Metadata-Version: 2.4
Name: pydara
Version: 1.0.1
Summary: PyDara financial data modules: TSE quotes, history, typed exports and versioned local storage
Author: PyDara contributors
License-Expression: MIT
Project-URL: Source, https://github.com/H-Saberi/pydara
Project-URL: Issues, https://github.com/H-Saberi/pydara/issues
Classifier: Development Status :: 5 - Production/Stable
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Office/Business :: Financial
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: THIRD_PARTY_NOTICES.md
Requires-Dist: httpx<0.29,>=0.27
Requires-Dist: pandas<4,>=2.2
Requires-Dist: polars<2,>=1.30
Requires-Dist: pyarrow>=19
Requires-Dist: platformdirs<5,>=4
Requires-Dist: tzdata>=2024.1
Provides-Extra: excel
Requires-Dist: openpyxl<4,>=3.1.5; extra == "excel"
Provides-Extra: parquet
Requires-Dist: pyarrow>=19; extra == "parquet"
Provides-Extra: files
Requires-Dist: openpyxl<4,>=3.1.5; extra == "files"
Requires-Dist: pyarrow>=19; extra == "files"
Provides-Extra: test
Requires-Dist: pytest<10,>=8; extra == "test"
Requires-Dist: pytest-asyncio<2,>=0.24; extra == "test"
Provides-Extra: notebook
Requires-Dist: ipykernel>=6; extra == "notebook"
Requires-Dist: nbformat>=5; extra == "notebook"
Requires-Dist: nbclient>=0.10; extra == "notebook"
Dynamic: license-file

# PyDara

[فارسی](#fa) | [English](#en)

<a id="fa"></a>

# پای‌دارا 1.0.1 — راهنمای فارسی

[فارسی](README.fa.md) | [English](README.en.md) | [مرجع API](docs/fa/API.md)


## امکانات نسخهٔ 1.0.1

توابع DataFrame-first جدید شامل `states`، `thresholds`، `intraday`، `orderhistory`، `lasttrades`، `companies`، `relations` و `dataset.update/status/read` هستند. `MarketStream` تشخیص رویداد از polling و اشتراک مشترکان را فراهم می‌کند؛ **WebSocket رسمی نیست**. ماژول `pydara.analyze` محاسبات جدولی را اضافه می‌کند. جزئیات و محدودیت‌های تاریخ/SQLite/Async: [راهنمای فارسی افزوده‌ها](docs/fa/NEW_API.md) | [English extension guide](docs/en/NEW_API.md). محدودیت‌های دادهٔ زنده در گزارش اعتبارسنجی ثبت شده‌اند.

## محدوده و وضعیت نسخه

پای‌دارا داده‌های مالی را از زیرماژول پیاده‌سازی‌شدهٔ `pydara.tse` دریافت می‌کند.
نام توزیع، namespace و پوشهٔ کد همگی `pydara` هستند.
نسخهٔ جاری `1.0.1` شامل اصلاح گزارش وضعیت ذخیره‌سازی پس از بازیابی است. ماژول پایهٔ `pydara.analyze` اکنون دارای تحلیل جدولی است؛ رمزارز، ارز و یادگیری ماشین هنوز ایجاد نشده‌اند.
بستهٔ تحویلی شامل کد، Wheel، بستهٔ منبع، آزمون، مثال و گزارش اعتبارسنجی است؛
**مخزن کامل وابستگی‌ها برای نصب کاملاً آفلاین نیست**.

هفت تابع اصلی عبارت‌اند از `markets`، `assets`، `prices`، `history`،
`traders`، `orders` و `trades`. کلاینت‌ها و رابط‌های سطح پایین موجود، رکوردهای نوع‌دار،
خروجی فایل، کش و SQLite و مسیرهای سازگار با داده‌های قبلی حفظ شده‌اند.

## نصب و ارتقا

```console
python -m pip install --upgrade pydara==1.0.1
```

حداقل نسخهٔ اعلام‌شده **Python 3.12** است. فقط نسخه‌هایی که در `AI_VERIFICATION.json`
و `reports/VALIDATION.md` صریحاً آمده‌اند، در این ساخت آزمایش شده‌اند.
وجود یک نسخه در ماتریس CI به معنی اجرای موفق آزمون آن نسخه نیست.

بسته را استخراج کنید، محیط پایتون موردنظر را فعال کنید و اجرا کنید:

```console
python install.py
python -c "import pydara; print(pydara.__version__)"
```

نصب‌کننده ابتدا checksum فایل Wheel انتخاب‌شده را بررسی می‌کند و کش، پایگاه داده یا
محیط شما را حذف نمی‌کند. وابستگی‌ها شامل HTTPX، Pandas، Polars، PyArrow، platformdirs
و tzdata هستند و ممکن است به دانلود نیاز داشته باشند.
گزینهٔ `python install.py --core-only` امکانات اضافی فایل و Notebook را کنار می‌گذارد،
نه وابستگی اصلی Polars و Arrow را.
پس از ارتقای کتابخانه‌ای که قبلاً در ژوپیتر import شده، Kernel را دوباره راه‌اندازی کنید.

نصب یا ارتقا از PyPI: `python -m pip install --upgrade pydara==1.0.1`.

## شروع سریع همگام

مثال‌های این بخش به منبع واقعی درخواست می‌فرستند؛ دسترسی شبکه و کامل‌بودن تاریخچه تضمین نیست.
آزمایش را با چند نماد شروع کنید، نه ریزمعاملات همهٔ بازار.

```python
from pydara import tse as ts

mrk = ts.markets("TSE", include_traders=False)
ass = ts.assets(markets=mrk, symbol=["فولاد", "وبملت"])
pr = ts.prices(assets=ass, include_queues=False)

print(pr[["symbol", "date", "closingprice", "volume", "returnpct"]])
print(ts.metadata(pr).get("errors", []))
```

جدول بازار، بازارها را انتخاب می‌کند و جدول دارایی، ابزارها را. خروجی ممکن است شامل صندوق،
حق‌تقدم و مشتقه باشد، نه فقط سهام عادی. تطبیق با `inscode` دقیق انجام می‌شود و
شناسه برای پردازش از نوع اعشاری عبور نمی‌کند.

## گردش‌کار ناهمگام واقعی

`AsyncSession` یک کلاینت ناهمگام HTTPX را بین درخواست‌ها نگه می‌دارد. هفت متد اصلی آن
قرارداد جدولی مشابه `tse` دارند. متدهای قبلی `AsyncClient` نیز با schema سطح پایین خود
باقی مانده‌اند.

در Jupyter Notebook یا JupyterLab کد زیر با `await` در سطح سلول اجرا می‌شود:

```python
from pydara import tse as ts

config = ts.ClientConfig(
    network=ts.NetworkConfig(
        max_concurrency=4,
        requests_per_second=2,
        request_budget=5000,
        max_retries=2,
        operation_timeout=60,
    )
)

async with ts.AsyncSession(config=config) as session:
    ass = await session.assets(symbol=["فولاد", "وبملت"])
    pr = await session.prices(
        assets=ass,
        include_queues=False,
        max_quote_fallback=None,
        on_error="collect",
    )

display(pr)
print(ts.metadata(pr))
```

در فایل `.py` بخش ناهمگام را داخل `async def main():` قرار دهید و با
`asyncio.run(main())` اجرا کنید. در حلقهٔ فعال ژوپیتر، `asyncio.run` تو در تو نسازید.
یک نشست را بین چند حلقهٔ رویداد یا کلاینت همگام مشترک را بین چند نخ استفاده نکنید.
`max_concurrency` تعداد درخواست‌های هم‌زمان را محدود می‌کند؛ نرخ درخواست در ثانیه،
تعداد تلاش مجدد، timeout و بودجهٔ کل عمر کلاینت، کنترل‌های جداگانه‌اند.
افزایش هم‌زمانی محدودیت منبع بورس را از بین نمی‌برد.

ترتیب خروجی به ترتیب ورودی است، نه زمان رسیدن پاسخ‌ها. ابزارهای شناسایی‌شدهٔ تکراری
در گردش‌کارهای اصلی یک بار دریافت می‌شوند. لغو عملیات به workerها منتقل می‌شود و
کارهای معلق متوقف و جمع‌آوری می‌شوند. checkpointهای کامل‌شده باقی می‌مانند.

## پردازش Polars و تعامل Pandas

برای دادهٔ تحلیلی معمول، مسیرهای جدید Polars ساخت و تبدیل نوع بردارها، فیلتر کلیدها،
محاسبهٔ بازده و اتصال و محاسبهٔ صف‌ها را انجام می‌دهند. خروجی پیش‌فرض کاربر Pandas است.
**مهاجرت موتور داخلی در این نسخه تدریجی است**: رابط‌های ذخیره‌سازی و خروجی نوع‌دار،
مسیر دقیق Decimal، بخشی از مونتاژ فراداده و جدول‌های کلاینت قدیمی، مسیر سازگاری Pandas
را نگه داشته‌اند. تمام عملیات داخلی را Polars-only معرفی نکنید.

```python
# Pandas default / Polars explicit
pr_pd = ts.prices(assets=ass, include_queues=False)
pr_pl = ts.prices(assets=ass, include_queues=False, engine="polars")

# Conversion of an existing result: no second network acquisition
pr_pl = ts.to_polars(pr_pd)
pr_pd = ts.to_pandas(pr_pl)
meta = ts.metadata(pr_pl)

# Native Polars transformations may not retain Python metadata
import polars as pl
filtered = ts.with_metadata(
    pr_pl.filter(pl.col("closingprice") > 0),
    pr_pl,
)
```

`engine` **نوع خروجی** را انتخاب می‌کند، نه سرویس شبکه‌ای سریع‌تر.
تمام هفت تابع اصلی همگام و متد متناظر AsyncSession این ورودی را دارند.
در AsyncSession می‌توان `engine="polars"` را یک بار تنظیم کرد و در هر متد تغییر داد.
جدول Polars برای ورودی‌های انتخاب دارایی یا بازار نیز پذیرفته می‌شود.

شناسه‌ها، مقادیر تهی و timestamp دارای منطقهٔ زمانی در مرز تبدیل حفظ می‌شوند.
مقادیر دقیق Decimal از float عبور نمی‌کنند؛ Decimal بسیار بزرگ یا اشیای مخلوط ممکن است
به ستون Object در Polars نیاز داشته باشند. خروجی‌گیر بومی Polars الزاماً این ستون‌ها را
نمی‌پذیرد؛ برای خروجی نوع‌دار پای‌دارا ابتدا `ts.to_pandas` را به کار ببرید.
فراداده متعلق به همان جدول بازگشتی است و تضمینی نیست که عملیات بومی Polars آن را کپی کند.
از `ts.metadata` و در صورت موجه بودن `ts.with_metadata` استفاده کنید؛ منشأ سطری هم مهم است.

نسخهٔ منبع با وابستگی ناقص می‌تواند مسیر صریحاً علامت‌گذاری‌شدهٔ
`pandas_dependency_fallback` را اجرا کند؛ این **آزمون یا بنچمارک Polars نیست**.
درخواست `engine="polars"` بدون نصب Polars خطا می‌دهد و بی‌اطلاع Pandas تحویل نمی‌دهد.

## قیمت گروهی، دادهٔ ناقص و بازده

انتخاب بزرگ ابتدا از پاسخ مشترک دیده‌بان استفاده می‌کند. `bulk_backend` یکی از
`"auto"`، `"json"` یا `"legacy"` است. مسیر خودکار پیش از تکمیل مستقل می‌تواند یک
پاسخ گروهی جایگزین را امتحان کند. قیمت گروهی قابل استفاده باید قیمت پایانی مثبت و
متناهی و تاریخ واقعی گزارش‌شدهٔ معامله داشته باشد؛ بازده معتبر، مرجع قبلی مثبت و
متناهی هم می‌خواهد.

پیش‌فرض ایمن `max_quote_fallback=20` حفظ شده است. صفر، تکمیل مستقل را غیرفعال می‌کند؛
**None اجازهٔ صریح تکمیل همهٔ موارد ناقص است**. حالت آخر ممکن است تعداد زیادی درخواست
ایجاد کند و بهتر است با AsyncSession مشترک و بودجهٔ کافی صریح به کار رود.
این گزینه دریافت کامل از سرویس قطع‌شده یا منبع ناقص را تضمین نمی‌کند.

فرمول‌ها:
`change = closingprice - previousreferenceprice`،
`return = change / previousreferenceprice`،
`returnpct = 100 * return`.
این‌ها **بازده قیمت** تعدیل‌نشده‌اند و سود نقدی را شامل نمی‌شوند. قیمت صفر یا نامعتبر
به بازده ساختگی منفی صددرصد تبدیل نمی‌شود. تاریخ ساعت سیستم جای تاریخ مفقود منبع
گذاشته نمی‌شود.

مسیر تکمیل، حجم، ارزش و تعداد معاملاتِ خود قیمت را حفظ می‌کند. اتصال سفارش‌های دیده‌بان
امروز نباید مقادیر مربوط به رکورد قبلی را با صفرهای امروز بازنویسی کند.
زمان صف (`queuedate` و `queuefetchedat`) مستقل است. فیلدهای هویتی مفقود از جدول ورودی
با شناسهٔ دقیق تکمیل می‌شوند؛ تعارض گزارش می‌شود و حدس زده نمی‌شود.

در فراداده این موارد را بررسی کنید: `valid_bulk_quotes`، `repair_attempted`،
`repair_succeeded`، `repair_deferred`، `unresolved_quotes`، `repair_errors` و `components`.
خروجی گروهی ستون‌های `quotevalid`، `returnvalid` و `quotestatus` دارد.
`returned_rows` تعداد خروجی انتخاب‌شده است، نه کل دیده‌بان پیش از فیلتر.
معتبر بودن عدد قیمت به معنی جاری یا نهایی بودن جلسهٔ معاملاتی نیست.

## تاریخچه، حقیقی و حقوقی، سفارش و ریزمعامله

```python
bars = ts.history(
    assets=ass,
    start="1403-01-01",
    end="1403-12-29",
    calendar="jalali",
)
investors = ts.traders(
    assets=ass,
    start="1403-01-01",
    end="1403-12-29",
    calendar="jalali",
)
book = ts.orders(assets=ass, depth=5, side="all")
```

`history` رکورد روزانهٔ واقعی را می‌دهد و جلسهٔ معاملاتی مفقود تولید نمی‌کند.
`traders` اطلاعات گزارش‌شدهٔ حقیقی، حقوقی و DDD را حفظ و دادهٔ گزارش‌شده را از شاخص
محاسباتی ورود پول جدا می‌کند. `orders` سطوح برتر گزارش‌شده است، نه تضمین همهٔ رویدادهای سفارش.

```python
async with ts.AsyncSession(config=config) as session:
    ticks = await session.trades(
        ins_code=["46348559193224090", "778253364357513"],
        start="1403-01-05",
        end="1403-01-05",
        calendar="jalali",
        starttime="09:00:00",
        endtime="12:30:00",
        maxassets=20,
        workers=4,
        progress=True,
        checkpoint="./trades_checkpoints",
    )
print(ts.metadata(ticks).get("errors", []))
```

در `trades` انتخاب ابزار لازم است. سقف‌های پیش‌فرض ۲۰ ابزار و ۳۱ روز تقویمی حفظ شده‌اند.
در مسیر همگام همچنان فقط `workers=1` مجاز است. در مسیر ناهمگام workerها به
`network.max_concurrency` محدود می‌شوند و مقدار درخواستی ۱ تا ۶۴ است.
بدون تاریخ، روز درخواستی امروز **Asia/Tehran** است. فقط `end` یعنی همان روز؛ فقط
`start` یعنی تا امروز. بازهٔ ساعت پیش‌فرض 00:00:00 تا 23:59:59 است و **در هر روز**
بازه تکرار می‌شود. جابه‌جایی پنهانی به روز معاملاتی قبل انجام نمی‌شود.

checkpoint اختیاری، تصویری ذخیره‌شده در قالب نوع‌دار غیر pickle و با نوشتن اتمی است.
قالب‌های قبلی نیز قابل خواندن مانده‌اند. درخواست تازه یا آنلاین صریح و محدودیت سن
دادهٔ آفلاین با checkpoint دور زده نمی‌شود. استفادهٔ مجدد علامت دارد و داده زنده نامیده
نمی‌شود. کارهای قطع‌شده یا ناقص ممکن است نیازمند تکرار باشند. هنگام فیلتر ساعت محدود،
رکورد با ساعت نامعلوم کنار گذاشته می‌شود؛ معاملات هم‌ثانیه حذف یا یکی نمی‌شوند.

## خطا، کش و کیفیت داده

`on_multiple` با مقادیر `"all"`، `"first"` و `"error"` ابهام هویتی را کنترل می‌کند.
`on_conflict` برابر `"error"` یا `"ins_code"` است؛ دومی شناسهٔ صریح می‌خواهد.
`on_error` برابر `"collect"` یا `"raise"`، خطای دریافت را کنترل می‌کند، نه معنای هویت.
درخواست ناموفق را با ردیف ساختگی یا صفر عددی جایگزین نکنید.

```python
policy = ts.FetchPolicy.offline(allow_stale=True)
cached = ts.prices(
    assets=ass,
    include_queues=False,
    policy=policy,
)
print(ts.metadata(cached))
```

آفلاین هیچ درخواست HTTP نمی‌فرستد. مجوز استفاده از دادهٔ منقضی باید صریح باشد.
`FetchPolicy.fresh()` دریافت تازه از منبع می‌خواهد، اما در بازار بسته معاملهٔ جدید
نمی‌سازد. `fromcache`، `isstale`، `fetchedat`، تاریخ منبع و پرچم پوشش مفاهیم متفاوت‌اند.
`assets()` **مجموعهٔ مشاهده‌شدهٔ جاری** است، نه فهرست جامع تاریخی.
خروجی خالی اثبات نبود فعالیت بورس نیست.

کلاینت پیش‌فرض همگام را پیش از اولین درخواست تنظیم کنید. برای تغییر تنظیمات کلاینت
ساخته‌شده، ابتدا `report = ts.close()`، سپس بررسی گزارش و بعد `ts.configure(...)`.
بودجهٔ مصرف‌شدهٔ درخواست‌ها بی‌اطلاع صفر نمی‌شود.

## ذخیره‌سازی و خروجی

```python
# Explicit consent is required to overwrite an existing export.
ts.save(ts.to_pandas(pr), "quotes.csv", overwrite=False)
restored = ts.load("quotes.csv")
```

نام‌های قدیمی schema کش و SQLite برای سازگاری داده حفظ شده‌اند و به معنی وابستگی
به بسته‌ای با نام دیگر نیستند. ستون Object مخصوص Polars باید در مرز خروجی نوع‌دار
تبدیل شود. CSV، Excel، SQLite و Parquet با قرارداد نوع‌دار قبلی و وابستگی‌های لازم
همچنان پشتیبانی می‌شوند.

## راهنما، آزمون و شواهد

در ژوپیتر از `help(ts.prices)`، `ts.prices?` یا `help(ts.AsyncSession.prices)` استفاده کنید.
داک‌استرینگ هفت تابع اصلی اکنون فارسی را بالاتر از انگلیسی دارد و پارامترهای واقعی،
پیش‌فرض، مقادیر مجاز، ستون خروجی، محدودیت و مثال را توضیح می‌دهد.

```console
python -m pytest -q
python benchmarks/measure.py --output reports/benchmark_local.json
python tools/live_check.py --allow-network --output reports/live_local.json
```

بنچمارک از دادهٔ مصنوعی و تأخیر صریحاً ساختگی استفاده می‌کند و ادعای سرعت واقعی بورس
نیست. نبود وابستگی یا شبکه باید نتیجهٔ Polars و کتابخانهٔ مقایسه‌ای را «آزمایش‌نشده»
بگذارد. گزارش این نسخه، تعداد دقیق آزمون‌های اجراشده، موارد ردشده به علت وابستگی،
زمان‌ها و محدودیت‌های باقی‌مانده را مشخص می‌کند. تنظیمات CI آماده است،
**نه اینکه اجرای آن تأیید شده باشد**.

## انتشار و ادامهٔ توسعه

صفحهٔ اصلی و توضیح بلند PyPI فارسی را پیش از انگلیسی نشان می‌دهند؛ برای هر زبان
راهنمای کامل هم‌تراز وجود دارد. کد، Wheel، بستهٔ منبع، manifest و checksum با هم ساخته
می‌شوند. رمز، توکن، محیط نصب‌شده، کش یا پایگاه کاربر در بستهٔ انتشار جایی ندارد.
مجوز MIT و اطلاعیه‌های قبلی حقوق مؤلف حفظ شده‌اند؛ کتابخانه‌های بیرونی وابستگی‌اند،
نه کد کپی‌شده داخل پروژه.

## تاریخ‌های مستقل قیمت و بازار در نسخهٔ 1.0.1

در این نسخه، `prices()` برای **دریافت تک‌نمادی و گروهی** ستون‌های زمانی یکسان می‌سازد:

- `date` و `datevalid`: تاریخ **رکورد قیمت نماد** که صریحاً از منبع استخراج شده؛ اگر `dEven=0` باشد ناشناخته باقی می‌ماند.
- `sessiondate` و `sessiondatevalid`: تاریخ فعالیت بازار از خروجی مستقل `markets()`/`GetMarketOverview`؛ این تاریخ **اثبات معامله‌کردن هر نماد در آن روز نیست**.
- `lasttradedate` و `lasttradedatebasis`: آخرین روز معاملهٔ **قابل استنتاج از همان رکورد**، فقط وقتی رکورد نماد تاریخ معتبر، حجم مثبت و تعداد معاملات مثبت دارد؛ برای رکوردهای بی‌تاریخ ناشناخته است.
- `sessiondatemismatch`: اختلاف میان تاریخ گزارش‌شدهٔ نماد و تاریخ گزارش فعالیت بازار، بدون دستکاری هیچ‌یک.
- `sourcetimestamp` و `fetchedat`: زمان ثبت در منبع و زمان دریافت، جدا از تاریخ معامله.

راه **بدون درخواست اضافه**، استفادهٔ مجدد از خلاصهٔ بازار است:

```python
from pydara import tse as ts
mrk = ts.markets()
assets = ts.assets(markets="TSE")
prices = ts.prices(assets=assets, include_queues=False,
                   max_quote_fallback=0, session_dates=mrk)
print(prices[["symbol", "date", "datevalid", "sessiondate",
              "sessiondatevalid", "lasttradedate"]].head())
```

اگر جدول بازار را ندارید، `session_dates="fetch"` گزارش بازار را **حداکثر یک بار برای هر بورس/فرابورس موجود** دریافت می‌کند. مقدار پیش‌فرض `None` هیچ درخواست اضافه‌ای نمی‌فرستد. تاریخ مفقود هر نماد با تاریخ جلسه یا زمان دریافت جعل نمی‌شود. هم `ts.prices()` و هم `await session.prices()` این پارامتر را می‌پذیرند. `session_dates` می‌تواند DataFrame از Pandas یا Polars باشد و خروجی پیش‌فرض Pandas باقی می‌ماند.

## اصلاح دیده‌بان در نسخهٔ 0.8.0a2

در دادهٔ فشردهٔ `GetMarketWatch`، فیلدهای `pcl` (قیمت پایانی)، `pdv`
(آخرین قیمت)، `py` (قیمت مرجع)، `qtj` (حجم گزارش‌شده)، `qtc` (ارزش
گزارش‌شده) و `ztt` (تعداد معاملات) به فیلدهای اصلی خروجی تبدیل می‌شوند.
`pc` و `pcpc` در شکل فشرده تغییر قیمت‌اند و نباید جای قیمت پایانی را
بگیرند. در صورت حضور ساختار نام‌دار قدیمی، نگاشت قدیمی نیز پشتیبانی می‌شود.

اگر سرویس `dEven=0` گزارش کند، `date` خالی باقی می‌ماند، حتی اگر قیمت
مشاهده‌شده مثبت باشد. `quotevalid=True` فقط اعتبار عددی قیمت مشاهده‌شده در
دیده‌بان را نشان می‌دهد؛ `datevalid=False` یعنی تاریخ معامله به‌طور مستقل
تأیید نشده است. مقدار `fetchedat` زمان دریافت داده است نه تاریخ معامله.
`returnvalid` نشان می‌دهد قیمت پایانی و قیمت مرجع برای محاسبهٔ بازده
معتبرند. داده‌های زنده ممکن است با معاملات روز نهایی تفاوت داشته باشند.
برای بررسی تعداد نمادهای بدون تاریخ: `ts.metadata(pr).get("undated_quote_rows")`.

مثالِ دریافت سریع قیمت‌ها بدون درخواست مستقل برای تکمیل:

```python
from pydara import tse as ts
ass = ts.assets(markets='TSE')
pr = ts.prices(assets=ass, include_queues=False, max_quote_fallback=0)
print(ts.metadata(pr).get('valid_quote_rows'))
print(ts.metadata(pr).get('undated_quote_rows'))
```

`max_quote_fallback=0` به معنای ممنوعیت درخواست تکمیلی تک‌نمادی است؛
برای نمادهای فاقد قیمت معتبر می‌توان سقف جداگانه تعیین کرد. دادهٔ
مفقود به‌صورت مصنوعی پر نمی‌شود.


---

<a id="en"></a>

# PyDara 1.0.1 — English guide

[فارسی](README.fa.md) | [English](README.en.md) | [API reference](docs/en/API.md)
## Independent quote and market dates in 1.0.1

`prices()` now returns the same temporal columns in **both bulk and individual** paths:

- `date`, `datevalid`: per-instrument source-reported quote date; `dEven=0` remains missing.
- `sessiondate`, `sessiondatevalid`: market activity date from an independent `markets()`/`GetMarketOverview` report; **not proof this instrument traded on that day**.
- `lasttradedate`, `lasttradedatebasis`: dated per-instrument quote with both positive reported trade volume and count. Unknown where this evidence is missing; not an exact last-execution time.
- `sessiondatemismatch`: reported market and instrument dates differ; neither is overwritten.
- `sourcetimestamp`, `fetchedat`: source time and local acquisition time remain separate.

Reuse an existing overview **without any additional market-overview request**:

```python
from pydara import tse as ts
markets = ts.markets()
assets = ts.assets(markets="TSE")
quotes = ts.prices(assets=assets, include_queues=False,
                   max_quote_fallback=0, session_dates=markets)
print(quotes[["symbol", "date", "datevalid", "sessiondate",
              "sessiondatevalid", "lasttradedate"]].head())
```

Alternatively `session_dates="fetch"` requests at most one market overview per present supported exchange; default `None` adds no network I/O. Missing per-asset quote dates are never replaced with overview or fetch dates. Both synchronous `prices` and `AsyncSession.prices` accept the option and either Pandas/Polars market tables.


## Scope and release status

PyDara provides financial-data acquisition for the implemented `pydara.tse` module.
The distribution name, import namespace and source directory stay `pydara`.
Version `1.0.1` includes storage recovery diagnostics and tabular analysis in
`pydara.analyze`. Cryptocurrency, foreign exchange and ML modules are not implemented. The development bundle includes source, a wheel,
an sdist, tests, examples and validation reports; it is **not an offline wheelhouse**.

The seven primary table APIs are `markets`, `assets`, `prices`, `history`,
`traders`, `orders` and `trades`. Existing lower-level clients, typed records,
file exports, cache/SQLite and historical-data compatibility paths remain.

## Install and update

```console
python -m pip install --upgrade pydara==1.0.1
```

Python **3.12 or later** is declared. Only the versions actually listed in
`AI_VERIFICATION.json` and `reports/VALIDATION.md` have been tested in this build.
Do not interpret a future CI matrix as executed tests.

Extract the bundle, activate the intended Python environment, then run:

```console
python install.py
python -c "import pydara; print(pydara.__version__)"
```

The bundled installer verifies the selected wheel checksum before installing.
It does not delete your cache, databases or environment. Dependencies include
HTTPX, Pandas, Polars, PyArrow, platformdirs and tzdata. They may require downloads.
`python install.py --core-only` skips file/notebook extras, **not** core Polars/Arrow.
Restart the Jupyter kernel after updating an already imported package.

Install or upgrade from PyPI: `python -m pip install --upgrade pydara==1.0.1`.

## Synchronous quick start

These examples contact the source; availability and historical completeness are
not guaranteed. Start with a small selection rather than all-market intraday data.

```python
from pydara import tse as ts

mrk = ts.markets("TSE", include_traders=False)
ass = ts.assets(markets=mrk, symbol=["فولاد", "وبملت"])
pr = ts.prices(assets=ass, include_queues=False)

print(pr[["symbol", "date", "closingprice", "volume", "returnpct"]])
print(ts.metadata(pr).get("errors", []))
```

A market table selects markets; an asset table selects instruments. A result can
include funds, rights and derivatives, not just ordinary shares. Exact `inscode`
matching is used; a code is never converted through a floating-point identifier.

## Native asynchronous workflow

`AsyncSession` reuses one native HTTPX async client. Its seven methods have the
same primary table conventions as `tse`. Existing `AsyncClient` methods remain
available with their existing lower-level schemas.

In Jupyter Notebook/JupyterLab, execute this directly with top-level `await`:

```python
from pydara import tse as ts

config = ts.ClientConfig(
    network=ts.NetworkConfig(
        max_concurrency=4,
        requests_per_second=2,
        request_budget=5000,
        max_retries=2,
        operation_timeout=60,
    )
)

async with ts.AsyncSession(config=config) as session:
    ass = await session.assets(symbol=["فولاد", "وبملت"])
    pr = await session.prices(
        assets=ass,
        include_queues=False,
        max_quote_fallback=None,
        on_error="collect",
    )

display(pr)
print(ts.metadata(pr))
```

In a `.py` script, put the async block in `async def main():` and call
`asyncio.run(main())`. Do not nest `asyncio.run` in an already running notebook loop.
Do not share a session between event loops or call a shared sync client from many
threads. `max_concurrency` limits simultaneous requests; requests-per-second,
retries, timeouts and the lifetime request budget remain separate controls.
Increasing concurrency does not override a source rate limit.

Concurrent result order follows the input order. Duplicate confirmed instruments
are fetched once within primary workflows. Cancellation propagates and pending
workers are cancelled/awaited. Successfully completed checkpoints survive.

## Polars processing and Pandas interaction

For ordinary analysis data the new Polars paths build/cast table vectors, filter
selection keys, calculate returns and join/calculate queue fields. Pandas is the
default user representation. **This is an incremental internal migration**:
existing storage/typed-export interfaces, exact Decimal handling, parts of
metadata assembly and legacy client tables still use Pandas compatibility paths.
Do not describe every internal operation as Polars-only.

```python
# Pandas default / Polars explicit
pr_pd = ts.prices(assets=ass, include_queues=False)
pr_pl = ts.prices(assets=ass, include_queues=False, engine="polars")

# Conversion of an existing result: no second network acquisition
pr_pl = ts.to_polars(pr_pd)
pr_pd = ts.to_pandas(pr_pl)
meta = ts.metadata(pr_pl)

# Native Polars transformations may not retain Python metadata
import polars as pl
filtered = ts.with_metadata(
    pr_pl.filter(pl.col("closingprice") > 0),
    pr_pl,
)
```

`engine` selects the **output type**, not a faster network service. Every primary
sync function and AsyncSession counterpart accepts it. An AsyncSession can set
`engine="polars"` once; a method-level value overrides that session default.
Polars input tables are accepted for `assets`/`markets` selection arguments.

Identifiers, nulls and timezone-aware timestamps are preserved at conversion
boundaries. Exact Decimal values are not cast through floats; unusually wide
Decimals/mixed objects can require a Polars Object column. Native Polars file
writers may not support these columns; convert with `ts.to_pandas` before the
typed PyDara exporter. Metadata belongs to the returned frame; native Polars
transformations are not promised to copy it. Use `ts.metadata` and, when justified,
`ts.with_metadata`. Row-level provenance remains important.

A dependency-incomplete source checkout may run a clearly reported
`pandas_dependency_fallback`; that is **not a Polars test or benchmark**.
Requesting `engine="polars"` without Polars raises, instead of returning Pandas.

## Bulk quotes, missing data and returns

Large selections use a shared market-watch response first. `bulk_backend` accepts
`"auto"`, `"json"` or `"legacy"`. The auto path can try one alternate bulk response
before independent repair. A valid bulk quote needs a finite positive closing
price and an actual reported trade date; a valid return also needs a finite
positive previous reference.

`max_quote_fallback=20` remains the safe default. Use `0` to disable individual
repair or **`None` to explicitly permit all incomplete candidates**. The last
option may generate many requests and is best used with a reused AsyncSession and
an explicitly adequate budget. It does not guarantee a complete response from an
unavailable or incomplete exchange service.

The return definitions are:
`change = closingprice - previousreferenceprice`,
`return = change / previousreferenceprice`,
`returnpct = 100 * return`.
They are unadjusted **price returns**, excluding dividends. A zero or invalid
close is not reported as a fabricated −100% return. Dates never come from the
machine's clock as a substitute for a missing source trade date.

The repair path preserves source quote volume, turnover and tradecount. Attaching
current order-book fields must not replace historical quote metrics with current
watch zeros. Queue timing (`queuedate`, `queuefetchedat`) is separate. Missing
identity fields can be enriched from input assets by exact code; conflicts are
reported rather than guessed.

Inspect metadata: `valid_bulk_quotes`, `repair_attempted`, `repair_succeeded`,
`repair_deferred`, `unresolved_quotes`, `repair_errors`, `components`.
Bulk results add `quotevalid`, `returnvalid`, `quotestatus`. `returned_rows` counts
the returned selection rather than the entire unfiltered watch response.
A valid numeric quote is not a guarantee of a current or final trading session.

## History, investors, orders and executions

```python
bars = ts.history(
    assets=ass,
    start="1403-01-01",
    end="1403-12-29",
    calendar="jalali",
)
investors = ts.traders(
    assets=ass,
    start="1403-01-01",
    end="1403-12-29",
    calendar="jalali",
)
book = ts.orders(assets=ass, depth=5, side="all")
```

`history` returns actual daily records and does not manufacture missing sessions.
`traders` retains reported individual/legal/DDD data and distinguishes reported
values from derived money-flow calculations. `orders` returns reported best-limit
levels, not a certified full order-event tape.

```python
async with ts.AsyncSession(config=config) as session:
    ticks = await session.trades(
        ins_code=["46348559193224090", "778253364357513"],
        start="1403-01-05",
        end="1403-01-05",
        calendar="jalali",
        starttime="09:00:00",
        endtime="12:30:00",
        maxassets=20,
        workers=4,
        progress=True,
        checkpoint="./trades_checkpoints",
    )
print(ts.metadata(ticks).get("errors", []))
```

`trades` requires explicit instruments. Default safety limits remain 20 instruments
and 31 calendar days. Sync `workers=1` remains enforced. Async workers are bounded
by `network.max_concurrency`; the requested value can be 1 through 64.
Without dates, the requested day is today in **Asia/Tehran**. Only `end` means that
single day; only `start` means through today. The default clock window is
00:00:00–23:59:59 and applies **to each day** in the date range.
No implicit switch to the previous trading session occurs.

Checkpoints are opt-in snapshots, stored atomically using a typed, non-pickle
format. Existing checkpoint formats are still readable. Explicit fresh/online
and offline age restrictions cannot be bypassed by a checkpoint. Resumed data is
marked as such, never relabeled live. Interrupted/incomplete requests may need
retrying. Unknown execution times are excluded when a restrictive clock filter
cannot be verified; same-second executions are not collapsed.

## Errors, cache and data quality

`on_multiple`: `"all"`, `"first"`, `"error"` controls ambiguous identity matches.
`on_conflict`: `"error"` or `"ins_code"`; the latter requires an explicit code.
`on_error`: `"collect"` or `"raise"` controls acquisition failures, not identity
meaning. Do not replace failed requests with fake rows or numeric zeros.

```python
policy = ts.FetchPolicy.offline(allow_stale=True)
cached = ts.prices(
    assets=ass,
    include_queues=False,
    policy=policy,
)
print(ts.metadata(cached))
```

Offline sends no HTTP. Permission to use stale data must be explicit.
`FetchPolicy.fresh()` requests a fresh source acquisition; it still cannot create
a new trade on a closed market. `fromcache`, `isstale`, `fetchedat`, source dates
and coverage flags describe different things. `assets()` is a **current observed
universe**, not the complete historical registry. Empty frames are not proof of
no exchange activity.

For the module-level sync client, configure before the first request. To change an
existing default client, call `report = ts.close()`, inspect the report, and then
`ts.configure(...)`. The library does not reset a consumed request budget silently.

## Storage and exports

```python
# Explicit consent is required to overwrite an existing export.
ts.save(ts.to_pandas(pr), "quotes.csv", overwrite=False)
restored = ts.load("quotes.csv")
```

Cache/SQLite schema names from earlier versions are retained for data compatibility,
not dependencies on a differently named package. Polars-specific object columns
should be converted at the typed-export boundary. CSV/Excel/SQLite/Parquet support
continues under the existing type-preserving export contracts and optional extras.

## Help, tests and evidence

Use `help(ts.prices)`, `ts.prices?` or `help(ts.AsyncSession.prices)` in the notebook.
The seven primary docstrings now include Persian first, English, actual
parameters/defaults/allowed values, output columns, limitations and examples.

```console
python -m pytest -q
python benchmarks/measure.py --output reports/benchmark_local.json
python tools/live_check.py --allow-network --output reports/live_local.json
```

The benchmark is synthetic and labels its artificial latency. It is not a live
exchange throughput claim. Polars and competitor results must remain unverified
when those dependencies or live network access are unavailable. See the release
validation report for the exact tests executed, skipped dependencies, timings and
remaining limitations. CI configurations are prepared, **not claimed executed**.

## Publishing and further development

README and PyPI long description show Persian before English, with matching full
guides for each language. Source/Wheel/sdist, manifests and checksum files are
rebuilt together. No credentials, installed environment, user cache or database
belongs in a release artifact. The MIT license and existing third-party notices
are retained; external libraries are dependencies, not copied vendor code.

## Market-watch compact fields in 0.8.0a2

The compact `GetMarketWatch` representation reports `pcl` as closing price,
`pdv` as last price, `py` as previous reference, `qtj` as reported volume,
`qtc` as turnover, and `ztt` as trade count. In this form `pc` and `pcpc`
are price *differences*, not closing prices. Long-form and earlier legacy
fields remain supported when the corresponding compact shape is absent.

When `dEven=0`, the source trade date is **unknown**, not today. `date` stays
missing; `datevalid` distinguishes this from `quotevalid` (positive finite
observed close). `returnvalid` needs a positive previous reference as well.
`fetchedat` is an observation retrieval timestamp, **not** a trade date.
These are observed market-watch values, not a certified final daily tape.
Check `ts.metadata(df)["undated_quote_rows"]` for the count of positive
quotes without a reported date.

For bulk-only acquisition without per-symbol repair:

```python
from pydara import tse as ts
assets = ts.assets(markets='TSE')
prices = ts.prices(assets=assets, include_queues=False, max_quote_fallback=0)
print(ts.metadata(prices).get('valid_quote_rows'))
print(ts.metadata(prices).get('undated_quote_rows'))
```


## GitHub source installation

```console
git clone https://github.com/H-Saberi/pydara.git
cd pydara
python -m pip install .
```

`python install.py` requires the matching wheel in `dist/`. To rebuild both release archives, run `python -m pip install "setuptools>=77"` then `python tools/build.py`.
