Skip to content

Instantly share code, notes, and snippets.

@5j9
Last active September 10, 2026 07:53
Show Gist options
  • Select an option

  • Save 5j9/a69ce57302c36a617da4662a76eec43e to your computer and use it in GitHub Desktop.

Select an option

Save 5j9/a69ce57302c36a617da4662a76eec43e to your computer and use it in GitHub Desktop.
Calculate one-month returns of Iranian silver ETFs
# /// script
# requires-python = ">=3.14"
# dependencies = [
# "aiohttp",
# "jdatetime",
# "polars",
# "pydantic",
# "tsetmc",
# ]
# ///
"""
Answer to the Bashgah question:
https://bashgah.com/Question/140506016/
Question:
در یک ماه اخیر منتهی به ۱۶ شهریور بازدهی صندوق نقراط و میانگین صندوق‌ها نقره چند درصد بوده؟
پاسخ خود را انتخاب کنید:
گزینه 1: نقراط ۲۵ درصد و میانگین صندوق‌های نقره ۲۰ درصد
گزینه 2: نقراط ۲۸ درصد و میانگین صندوق‌های نقره ۲۰ درصد
گزینه 3: نقراط ۲۵ درصد و میانگین صندوق‌های نقره ۲۳ درصد
گزینه 4: نقراط ۲۸ درصد و میانگین صندوق‌های نقره ۱۵ درصد
The script gets the silver ETF list from TradersArena, then uses TSETMC
closing prices to calculate the one-month returns.
"""
import asyncio
from datetime import date
import aiohttp
from jdatetime import datetime as jdt
from polars import col
from pydantic import BaseModel
from tsetmc.instruments import Instrument
TRADERSARENA_URL = 'https://tradersarena.ir/data/industries/silver-funds/snapshot?timeframe=12'
class Classification(BaseModel):
tag: str
class Fund(BaseModel):
classification: Classification
class Static(BaseModel):
fund: Fund
class Row(BaseModel):
symbol: str
static: Static
class Snapshot(BaseModel):
rows: list[Row]
async def get_silver_l18s() -> list[str]:
async with aiohttp.ClientSession() as session:
async with session.get(TRADERSARENA_URL) as response:
response.raise_for_status()
data = await response.json()
snapshot = Snapshot.model_validate(data)
return [
row.symbol
for row in snapshot.rows
if row.static.fund.classification.tag == 'silver'
]
def price_on_or_before(history, target_date: date) -> tuple[date, int]:
row = (
history.filter(col('date') <= target_date)
.sort('date', descending=True)
.select('date', 'pc')
.head(1)
.collect()
)
if row.is_empty():
raise ValueError(f'No price history before {target_date}')
return row['date'][0], row['pc'][0]
async def get_return(
l18: str,
start_date: date,
end_date: date,
) -> tuple[date, int, date, int, float]:
instrument = await Instrument.from_l18(l18)
history = await instrument.price_history()
actual_start_date, start_price = price_on_or_before(history, start_date)
actual_end_date, end_price = price_on_or_before(history, end_date)
return_pct = (end_price / start_price - 1) * 100
return (
actual_start_date,
start_price,
actual_end_date,
end_price,
return_pct,
)
async def main():
# ۱۶ مرداد ۱۴۰۵ تا ۱۶ شهریور ۱۴۰۵
start_date = jdt(1405, 5, 16).togregorian()
end_date = jdt(1405, 6, 16).togregorian()
l18s = await get_silver_l18s()
print(f'Silver funds: {len(l18s)}')
print(f'Period: {start_date}{end_date}')
print()
results = {}
for l18 in l18s:
try:
result = await get_return(l18, start_date, end_date)
except Exception as e:
print(f'{l18}: ERROR: {e}')
continue
results[l18] = result
(
actual_start_date,
start_price,
actual_end_date,
end_price,
return_pct,
) = result
print(
f'{l18:10} '
f'{actual_start_date}{actual_end_date} '
f'{start_price:,}{end_price:,} '
f'{return_pct:7.2f}%'
)
if not results:
raise RuntimeError('No returns calculated')
average = sum(result[-1] for result in results.values()) / len(results)
print()
print(f'Average: {average:.2f}%')
noghrat_return = results['نقراط'][-1]
print(f'نقراط: {noghrat_return:.2f}%')
print()
if round(noghrat_return) == 28 and round(average) == 15:
print('Answer: گزینه ۴')
elif round(noghrat_return) == 25 and round(average) == 20:
print('Answer: گزینه ۱')
elif round(noghrat_return) == 28 and round(average) == 20:
print('Answer: گزینه ۲')
elif round(noghrat_return) == 25 and round(average) == 23:
print('Answer: گزینه ۳')
else:
print('No answer choice matched.')
if __name__ == '__main__':
asyncio.run(main())
@5j9

5j9 commented Sep 10, 2026

Copy link
Copy Markdown
Author
Silver funds: 14
Period: 2026-08-07 00:00:00 → 2026-09-07 00:00:00

سیگلو      2026-08-05 → 2026-09-07  11,251 → 12,802    13.79%
نقرین      2026-08-05 → 2026-09-07  9,127 → 10,406    14.01%
یاس: ERROR: No price history before 2026-08-07 00:00:00
سیلور      2026-08-05 → 2026-09-07  11,847 → 13,123    10.77%
پلاتا      2026-07-13 → 2026-09-07  10,000 → 12,134    21.34%
نقرفام     2026-08-05 → 2026-09-07  8,246 → 9,283    12.58%
سیان: ERROR: No price history before 2026-08-07 00:00:00
سیمین      2026-08-05 → 2026-09-07  11,561 → 12,922    11.77%
نقران      2026-08-05 → 2026-09-07  10,971 → 12,343    12.51%
نقرآمد: ERROR: No price history before 2026-08-07 00:00:00
نقرسا      2026-08-05 → 2026-09-07  8,621 → 9,871    14.50%
نقراط      2026-07-14 → 2026-09-07  10,000 → 12,843    28.43%
سیلوا: ERROR: No price history before 2026-08-07 00:00:00
نقرابی     2026-08-05 → 2026-09-07  11,926 → 13,335    11.81%

Average: 15.15%
نقراط: 28.43%

Answer: گزینه ۴

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment