قیمت تعدیل‌شده چیست و چرا مهم است؟

تیم روشا · ۲۳ آبان ۱۴۰۵

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

در روشا با adjusted=true قیمت تعدیل‌شده را می‌گیرید. این نوشته روش محاسبه را با اعداد واقعی نماد فولاد نشان می‌دهد و در پایان کدی می‌دهد که محاسبه را خودتان بازبینی کنید.

چرا سابقهٔ خام قیمت می‌پرد؟

دو رویداد شرکتی قیمت سهم را بدون معامله جابه‌جا می‌کنند:

  • افزایش سرمایه. تعداد سهام شرکت زیاد می‌شود و ارزش شرکت میان سهام بیشتری تقسیم می‌شود. قیمت هر سهم پس از بازگشایی پایین‌تر است.
  • سود نقدی. پس از مجمع، سود تقسیمی از قیمت سهم کم می‌شود، چون خریدار پس از مجمع آن سود را نمی‌گیرد.

نمونهٔ واقعی: قیمت پایانی فولاد در ۲۳ بهمن ۱۳۹۷ برابر ۴٬۸۸۸ ریال بود. نماد چند روز متوقف شد و در ۳۰ بهمن با قیمت پایانی ۲٬۸۲۰ ریال برگشت. این افت ۴۲ درصدی حاصل افزایش سرمایه است، نه فروش سهام‌داران.

date close prev_close volume
2019-02-12 4888.0 4906.0 14448155
2019-02-13 4888.0 4888.0 0
2019-02-18 4888.0 4888.0 0
2019-02-19 2820.0 2820.0 0
2019-02-20 3046.0 2820.0 137718369

ضریب تعدیل چطور حساب می‌شود؟

TSETMC برای هر رویداد دو عدد منتشر می‌کند: قیمت پایانی پیش از تعدیل و قیمت پایانی پس از تعدیل. ضریب تعدیل، نسبت این دو است:

ضریب رویداد = close_adjusted ÷ close_unadjusted

رویدادها را از نقطهٔ پایانی adjustments می‌گیرید. رویداد بالا این‌طور ثبت شده است:

{"d":"2019-02-19","close_adjusted":2820.0,"close_unadjusted":4888.0}

ضریب این رویداد ۲٬۸۲۰ ÷ ۴٬۸۸۸ = ۰٫۵۷۶۹ است. این عدد با یک افزایش سرمایهٔ حدود ۷۳ درصدی سازگار است (۱ ÷ ۱٫۷۳۳).

سه قاعده، کل روش را می‌سازد:

  1. هر رویداد روزهایی را تغییر می‌دهد که هنوز بر مبنای قیمت قدیم‌اند. یعنی همهٔ روزهای پیش از تاریخ رویداد (D)، و خودِ روز D اگر نماد در آن روز متوقف بوده و هنوز قیمت قدیم را نشان می‌دهد. نشانهٔ آن ساده است: اگر prev_close خام روز D با close_adjusted رویداد برابر باشد، روز D از قیمت جدید شروع شده و دست نمی‌خورد؛ وگرنه روز D هم تعدیل می‌شود. در داده‌های TSETMC تا سال ۱۳۹۸ حالت اول و از ۱۳۹۹ حالت دوم رایج است.
  2. ضریب‌ها در هم ضرب می‌شوند. ضریب نهایی هر روز، حاصل‌ضرب ضریب همهٔ رویدادهایی است که آن روز را تغییر می‌دهند.
  3. حجم تعدیل نمی‌شود. فقط شش ستون قیمت تغییر می‌کند: open، high، low، close، last و prev_close. volume، value و trades همان اعداد خام هستند.

نتیجه با دو رقم اعشار گرد می‌شود. به این روش «تعدیل رو به عقب» می‌گویند: قیمت امروز همان قیمت واقعی بازار می‌ماند و گذشته به مقیاس امروز درمی‌آید.

یک مثال عددی کامل

فولاد تا آبان ۱۴۰۵ بیست‌وسه رویداد تعدیل دارد. نُه رویداد آخر و ضریب تجمعی آن‌ها این است:

تاریخ رویداد close_adjusted close_unadjusted ضریب رویداد ضریب تجمعی روزهای پیش از آن
2019-02-19 2820 4888 0.576923 0.321100
2019-07-22 3997 4297 0.930184 0.556574
2020-08-05 20720 20945 0.989258 0.598348
2021-08-03 10550 10950 0.963470 0.604846
2022-08-03 9470 11170 0.847807 0.627778
2023-07-22 4960 5460 0.908425 0.740473
2024-07-21 4800 5200 0.923077 0.815118
2025-08-04 2580 2860 0.902098 0.883044
2026-08-03 2549 2604 0.978879 0.978879

ستون آخر از پایین به بالا ساخته می‌شود. برای نمونه ۰٫۸۸۳۰۴۴ = ۰٫۹۰۲۰۹۸ × ۰٫۹۷۸۸۷۹.

حالا دو روز دو طرف رویداد بهمن ۱۳۹۷ را حساب کنیم:

  • ۲۳ بهمن ۱۳۹۷ (پیش از رویداد): قیمت خام ۴٬۸۸۸ ریال است. همهٔ نُه رویداد پس از این روز هستند. ۴٬۸۸۸ × ۰٫۳۲۱۱۰۰ = ۱٬۵۶۹٫۵۴ ریال.
  • ۳۰ بهمن ۱۳۹۷ (روز رویداد): قیمت خام ۲٬۸۲۰ ریال است و این روز از قیمت جدید شروع شده، پس رویداد خودش روی آن اثر ندارد. هشت رویداد پس از این روز هستند. ۲٬۸۲۰ × ۰٫۵۵۶۵۷۴ = ۱٬۵۶۹٫۵۴ ریال.

هر دو روز در سری تعدیل‌شده ۱٬۵۶۹٫۵۴ ریال هستند. پرش ۴۲ درصدی از بین رفته و بازده این چند روز صفر است، که درست است: نماد متوقف بود. پاسخ API با adjusted=true همین را می‌دهد:

date close prev_close volume
2019-02-12 1569.54 1575.32 14448155
2019-02-19 1569.54 1569.54 0
2019-02-20 1695.32 1569.54 137718369

چطور قیمت تعدیل‌شده را از روشا بگیرم؟

پارامتر adjusted=true را به سابقهٔ روزانه اضافه کنید:

Terminal window
curl "https://poolick.ir/v1/instruments/46348559193224090/daily?adjusted=true&limit=5" \
-H "Authorization: Bearer $ROSHA_KEY"

فیلد adjusted در پاسخ می‌گوید ردیف‌ها تعدیل‌شده‌اند یا نه. در MCP همین کار را ابزار get_daily_history با adjusted=true انجام می‌دهد. فایل CSV رایگان صفحهٔ هر نماد هم قیمت تعدیل‌شده دارد.

چطور محاسبه را خودم بازبینی کنم؟

کد زیر رویدادها و سابقهٔ خام را می‌گیرد، ضریب‌ها را با همان سه قاعده حساب می‌کند و نتیجه را با پاسخ adjusted=true مقایسه می‌کند:

import os
import time
import requests
BASE = "https://poolick.ir"
HEADERS = {"Authorization": f"Bearer {os.environ['ROSHA_KEY']}"}
REF = "46348559193224090" # فولاد
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()
events = get(f"/v1/instruments/{REF}/adjustments")
raw = get(f"/v1/instruments/{REF}/daily", params={"limit": 5000})["rows"]
adjusted = get(
f"/v1/instruments/{REF}/daily", params={"limit": 5000, "adjusted": "true"}
)["rows"]
prev_close = {r["d"]: r["prev_close"] for r in raw}
def applies(event, day):
"""True if `day` is still on the old price basis of `event`."""
if day != event["d"]:
return day < event["d"]
# On the event day itself: already on the new basis if it opens from the adjusted close.
opened_from = prev_close.get(day)
return opened_from is not None and abs(opened_from - event["close_adjusted"]) > 0.5
def factor(day):
f = 1.0
for e in events:
if e["close_unadjusted"] and applies(e, day):
f *= e["close_adjusted"] / e["close_unadjusted"]
return f
worst = max(
abs(round(r["close"] * factor(r["d"]), 2) - a["close"])
for r, a in zip(raw, adjusted)
)
print(len(events), "events,", len(raw), "rows, largest difference:", worst, "rials")

خروجی برای فولاد در آبان ۱۴۰۵:

23 events, 4680 rows, largest difference: 0.0 rials

تاریخ‌ها رشتهٔ ISO هستند و مقایسهٔ رشته‌ای آن‌ها همان مقایسهٔ تاریخی است.

چند نکته که در عمل مهم است

  • سری تعدیل‌شده ثابت نیست. با هر رویداد تازه، همهٔ قیمت‌های پیش از آن دوباره ضرب می‌شوند. اگر داده را در پایگاه دادهٔ خود نگه می‌دارید، قیمت خام و جدول رویدادها را ذخیره کنید و تعدیل را هنگام خواندن بسازید. روش آن در همگام‌سازی روزانه آمده است.
  • قیمت تعدیل‌شده یک عدد محاسبه‌ای است. هیچ معامله‌ای با ۱٬۵۶۹٫۵۴ ریال انجام نشده است. برای تطبیق با صورت‌معامله یا ارزش روز پرتفوی در گذشته، قیمت خام را به کار ببرید.
  • روزهای توقف را جدا کنید. در روزهایی که نماد معامله نشده، open، high و low تهی و volume صفر است. تاریخ ثبت رویداد معمولاً در همین روزهای توقف است. برای محاسبهٔ بازده و اندیکاتور، فقط ردیف‌های دارای معامله را به کار ببرید.
  • ضریب از دادهٔ TSETMC می‌آید. روشا ضریب را خودش برآورد نمی‌کند. اگر TSETMC رویدادی را ثبت نکرده باشد، در سری تعدیل‌شده هم نیست.
  • صندوق‌ها جدا هستند. این روش برای نمادهای TSETMC است. سود تقسیمی صندوق‌ها در فیلد dividends صندوق می‌آید.

تعریف کوتاه این مفهوم در واژه‌نامه است. این نوشته آموزشی است و هیچ بخشی از آن توصیهٔ سرمایه‌گذاری نیست.

Esc

نمونه: «فولاد»، «شاخص کل»، «عیار»، «rate limit»