پایتون و pandas
برای کار با روشا در پایتون به requests و pandas نیاز دارید و یک تابع get که کلید را از ROSHA_KEY میخواند و برای 429 به اندازهٔ Retry-After صبر میکند. هر نمونهٔ این صفحه یک برنامهٔ کامل است و جدا اجرا میشود.
پیشنیاز
Section titled “پیشنیاز”پایتون ۳٫۹ یا بالاتر، دو کتابخانه و کلید در متغیر محیطی:
pip install requests pandasexport ROSHA_KEY="rosha_xxxxxxxx_your-secret"اگر کلید ندارید، شروع سریع را ببینید.
تابع کمکی get چیست؟
Section titled “تابع کمکی get چیست؟”get یک درخواست GET میفرستد، اگر 429 گرفت به اندازهٔ Retry-After صبر میکند و یک بار دیگر میفرستد، و برای هر خطای دیگر استثنا میدهد. همهٔ نمونههای پایتون این مستندات همین تابع را به کار میبرند.
import osimport time
import requests
BASE = "https://poolick.ir"HEADERS = {"Authorization": f"Bearer {os.environ['ROSHA_KEY']}"}
def get(path, **kwargs): """GET with one retry on 429, waiting as long as Retry-After says.""" for _ in range(2): r = requests.get(BASE + path, headers=HEADERS, timeout=30, **kwargs) if r.status_code != 429: break time.sleep(int(r.headers.get("Retry-After", "1"))) r.raise_for_status() return r.json()
print(get("/v1/instruments", params={"q": "فولاد", "limit": 3}))requests نماد فارسی را در مسیر و پارامترها خودش URL-encode میکند. معنای هر کد خطا در خطاها است.
چطور سابقهٔ روزانه را در DataFrame بریزم؟
Section titled “چطور سابقهٔ روزانه را در DataFrame بریزم؟”rows پاسخ را به pd.DataFrame بدهید و ستون d را ایندکس تاریخ کنید. این برنامه ۲۵۰ روز معاملاتی اخیر فولاد را با قیمت تعدیلشده میگیرد:
import osimport time
import pandas as pdimport requests
BASE = "https://poolick.ir"HEADERS = {"Authorization": f"Bearer {os.environ['ROSHA_KEY']}"}
def get(path, **kwargs): """GET with one retry on 429, waiting as long as Retry-After says.""" for _ in range(2): r = requests.get(BASE + path, headers=HEADERS, timeout=30, **kwargs) if r.status_code != 429: break time.sleep(int(r.headers.get("Retry-After", "1"))) r.raise_for_status() return r.json()
data = get( "/v1/instruments/فولاد/daily", params={"adjusted": "true", "limit": 250},)df = pd.DataFrame(data["rows"]).set_index("d")df.index = pd.to_datetime(df.index)print(data["ins_code"], data["ticker"]) # ins_code is a string: keep it that wayprint(df[["close", "volume"]].tail())ستونهای DataFrame همان فیلدهای سابقهٔ روزانه هستند: open، high، low، close، last، prev_close، volume، value، trades و jdate. قیمتها به ریال است.
قیمت تعدیلشده چه فرقی با قیمت خام دارد؟
Section titled “قیمت تعدیلشده چه فرقی با قیمت خام دارد؟”قیمت تعدیلشده اثر افزایش سرمایه و سود نقدی را از سری قیمت برمیدارد؛ قیمت خام همان عددی است که روی تابلو بوده است. این برنامه هر دو را برای یک بازه میگیرد و کنار هم میگذارد:
import osimport time
import pandas as pdimport requests
BASE = "https://poolick.ir"HEADERS = {"Authorization": f"Bearer {os.environ['ROSHA_KEY']}"}
def get(path, **kwargs): """GET with one retry on 429, waiting as long as Retry-After says.""" for _ in range(2): r = requests.get(BASE + path, headers=HEADERS, timeout=30, **kwargs) if r.status_code != 429: break time.sleep(int(r.headers.get("Retry-After", "1"))) r.raise_for_status() return r.json()
def closes(ref, adjusted, **params): """Closing prices of one instrument as a Series indexed by date.""" params["adjusted"] = "true" if adjusted else "false" rows = get(f"/v1/instruments/{ref}/daily", params=params)["rows"] s = pd.Series({r["d"]: r["close"] for r in rows}, dtype="float64") s.index = pd.to_datetime(s.index) return s
REF = "46348559193224090" # فولادboth = pd.DataFrame({ "raw": closes(REF, False, start="2024-07-10", end="2024-07-31"), "adjusted": closes(REF, True, start="2024-07-10", end="2024-07-31"),})both["factor"] = (both["adjusted"] / both["raw"]).round(4)print(both)در خروجی، ستون factor تا خودِ روز رویداد 2024-07-21 برابر 0.8151 و از 2024-07-22 برابر 0.8830 است. نماد در روز رویداد متوقف بوده و هنوز قیمت قدیم را نشان میدهد، پس آن روز هم تعدیل میشود. برای محاسبهٔ بازده و اندیکاتور از قیمت تعدیلشده استفاده کنید و برای مقایسه با تابلوی همان روز از قیمت خام. قاعدهٔ تعدیل در تعدیل قیمت آمده است.
چطور کل سابقهٔ یک نماد را بگیرم؟
Section titled “چطور کل سابقهٔ یک نماد را بگیرم؟”هر پاسخ حداکثر ۵٬۰۰۰ ردیف دارد؛ برای سابقهٔ بلندتر با تاریخ صفحهبندی کنید. هر بار end را روزِ قبل از قدیمیترین ردیف گرفتهشده بگذارید و وقتی تعداد ردیفها از اندازهٔ صفحه کمتر شد بایستید.
import osimport timefrom datetime import date, timedelta
import pandas as pdimport requests
BASE = "https://poolick.ir"HEADERS = {"Authorization": f"Bearer {os.environ['ROSHA_KEY']}"}PAGE = 5000 # the REST cap on `limit`
def get(path, **kwargs): """GET with one retry on 429, waiting as long as Retry-After says.""" for _ in range(2): r = requests.get(BASE + path, headers=HEADERS, timeout=30, **kwargs) if r.status_code != 429: break time.sleep(int(r.headers.get("Retry-After", "1"))) r.raise_for_status() return r.json()
def full_daily(ref, adjusted=True): """Every daily row of one instrument, oldest first, paged by date.""" rows, end = [], None while True: params = {"limit": PAGE, "adjusted": "true" if adjusted else "false"} if end: params["end"] = end batch = get(f"/v1/instruments/{ref}/daily", params=params)["rows"] rows = batch + rows # each page is older than the one before if len(batch) < PAGE: return rows oldest = date.fromisoformat(batch[0]["d"]) end = (oldest - timedelta(days=1)).isoformat()
rows = full_daily("46348559193224090") # فولادdf = pd.DataFrame(rows).set_index("d")df.index = pd.to_datetime(df.index)print(len(df), "rows from", df.index[0].date(), "to", df.index[-1].date())هر صفحه یک درخواست است. فولاد از اسفند ۱۳۸۵ تا آبان ۱۴۰۵ در مجموع ۴٬۶۸۰ ردیف دارد و با یک درخواست تمام میشود؛ حلقه برای نمادهایی است که بیش از ۵٬۰۰۰ روز سابقه دارند. همین الگو برای /client-type، /v1/indices/{ref}/daily و /v1/funds/{ref}/history هم کار میکند.
چطور NAV یک صندوق را بگیرم؟
Section titled “چطور NAV یک صندوق را بگیرم؟”/v1/funds/{ref}/history سابقهٔ روزانهٔ صندوق را میدهد. ref میتواند شمارهٔ ثبت یا نماد صندوق باشد. این برنامه NAV ابطال و خالص دارایی ۹۰ روز اخیر یک صندوق را میگیرد:
import osimport time
import pandas as pdimport requests
BASE = "https://poolick.ir"HEADERS = {"Authorization": f"Bearer {os.environ['ROSHA_KEY']}"}
def get(path, **kwargs): """GET with one retry on 429, waiting as long as Retry-After says.""" for _ in range(2): r = requests.get(BASE + path, headers=HEADERS, timeout=30, **kwargs) if r.status_code != 429: break time.sleep(int(r.headers.get("Retry-After", "1"))) r.raise_for_status() return r.json()
data = get("/v1/funds/10589/history", params={"limit": 90})nav = pd.DataFrame(data["rows"]).set_index("d")nav.index = pd.to_datetime(nav.index)print(data["reg_no"])print(nav[["cancel_nav", "issue_nav", "net_asset", "source"]].tail())cancel_nav، issue_nav و net_asset به ریال است. برای صندوق چتری این سابقه در سطح شمارهٔ ثبت است و NAV هیچ زیرصندوقی نیست؛ صندوقها را ببینید.
چطور بازده را حساب کنم؟
Section titled “چطور بازده را حساب کنم؟”بازده را از قیمت پایانی تعدیلشده حساب کنید؛ با قیمت خام، روز افزایش سرمایه مثل افت قیمت دیده میشود. این برنامه بازده روزانه، بازده کل بازه و بیشترین افت از قله را برای ۲۵۰ روز معاملاتی اخیر حساب میکند:
import osimport time
import pandas as pdimport requests
BASE = "https://poolick.ir"HEADERS = {"Authorization": f"Bearer {os.environ['ROSHA_KEY']}"}
def get(path, **kwargs): """GET with one retry on 429, waiting as long as Retry-After says.""" for _ in range(2): r = requests.get(BASE + path, headers=HEADERS, timeout=30, **kwargs) if r.status_code != 429: break time.sleep(int(r.headers.get("Retry-After", "1"))) r.raise_for_status() return r.json()
rows = get( "/v1/instruments/46348559193224090/daily", # فولاد params={"adjusted": "true", "limit": 250},)["rows"]close = pd.Series({r["d"]: r["close"] for r in rows}, dtype="float64")close.index = pd.to_datetime(close.index)
daily_return = close.pct_change().dropna()total_return = close.iloc[-1] / close.iloc[0] - 1drawdown = close / close.cummax() - 1
print(f"period: {close.index[0].date()} to {close.index[-1].date()}")print(f"total return: {total_return:.2%}")print(f"mean daily return: {daily_return.mean():.3%}")print(f"daily volatility: {daily_return.std():.3%}")print(f"max drawdown: {drawdown.min():.2%}")این اعداد توصیف گذشتهٔ قیمتاند و توصیهٔ سرمایهگذاری نیستند.
نکتههای pandas
Section titled “نکتههای pandas”ins_code: رشته است. اگر آن را در ستون میگذارید، نوع ستون راstrنگه دارید و بهint64یاfloatتبدیل نکنید.null: در روزهای بدون معاملهopen،highوlowبرابرNoneاست و در DataFrame بهNaNتبدیل میشود.- ترتیب: ردیفها از قدیم به جدید میآیند؛ نیازی به
sort_indexنیست. - سهمیه: هر فراخوانی
getیک درخواست از سهمیهٔ روزانه کم میکند. با پلن رایگان (تا آبان ۱۴۰۵: ۱٬۰۰۰ درخواست در روز) برای حلقه روی نمادهای زیاد، نتیجه را روی دیسک نگه دارید. روش در همگامسازی روزانه.
صفحهٔ اصلی · قیمتها · وضعیت · پنل · پشتیبانی
دادهها توصیهٔ سرمایهگذاری نیست. سلب مسئولیت