رفتن به محتوا

جاوااسکریپت

در Node.js 18 یا بالاتر، fetch داخلی برای کار با روشا کافی است و کتابخانهٔ دیگری لازم نیست. یک تابع get کلید را از ROSHA_KEY می‌خواند و برای 429 به اندازهٔ Retry-After صبر می‌کند. مهم‌ترین نکتهٔ جاوااسکریپت این است که ins_code را رشته نگه دارید.

Node.js 18 یا بالاتر و کلید در متغیر محیطی. نمونه‌ها await سطح بالا دارند؛ آن‌ها را در فایل .mjs بگذارید.

Terminal window
export ROSHA_KEY="rosha_xxxxxxxx_your-secret"
node example.mjs

get یک درخواست GET می‌فرستد، اگر 429 گرفت به اندازهٔ Retry-After صبر می‌کند و یک بار دیگر می‌فرستد، و برای هر خطای دیگر استثنا می‌دهد.

const BASE = "https://poolick.ir";
async function get(path) {
for (let attempt = 0; attempt < 2; attempt++) {
const res = await fetch(BASE + path, {
headers: { Authorization: `Bearer ${process.env.ROSHA_KEY}` },
});
if (res.status === 429) {
await new Promise((r) => setTimeout(r, 1000 * Number(res.headers.get("Retry-After") ?? 1)));
continue;
}
if (!res.ok) throw new Error(`${res.status} ${(await res.json()).detail}`);
return res.json();
}
throw new Error("rate limited");
}
const results = await get("/v1/instruments?q=" + encodeURIComponent("فولاد") + "&limit=3");
console.log(results.map((i) => [i.ins_code, i.ticker, i.name]));

مقدار پارامترهای فارسی را با encodeURIComponent یا URLSearchParams کدگذاری کنید. معنای هر کد خطا در خطاها است.

چطور سابقهٔ روزانه را بگیرم؟

Section titled “چطور سابقهٔ روزانه را بگیرم؟”

/v1/instruments/{ref}/daily را صدا بزنید و rows را بخوانید. این برنامه ۳۰ روز معاملاتی اخیر فولاد را با قیمت تعدیل‌شده می‌گیرد و بازده بازه را حساب می‌کند:

const BASE = "https://poolick.ir";
async function get(path) {
for (let attempt = 0; attempt < 2; attempt++) {
const res = await fetch(BASE + path, {
headers: { Authorization: `Bearer ${process.env.ROSHA_KEY}` },
});
if (res.status === 429) {
await new Promise((r) => setTimeout(r, 1000 * Number(res.headers.get("Retry-After") ?? 1)));
continue;
}
if (!res.ok) throw new Error(`${res.status} ${(await res.json()).detail}`);
return res.json();
}
throw new Error("rate limited");
}
const query = new URLSearchParams({ adjusted: "true", limit: "30" });
const data = await get(`/v1/instruments/46348559193224090/daily?${query}`); // فولاد
const rows = data.rows; // oldest first
const first = rows[0];
const last = rows.at(-1);
console.log(data.ticker, data.ins_code);
console.log(`${first.d} close ${first.close} -> ${last.d} close ${last.close}`);
console.log(`return: ${((last.close / first.close - 1) * 100).toFixed(2)}%`);

قیمت‌ها به ریال است. در روزهای بدون معامله open، high و low برابر null است؛ پیش از محاسبه آن را بررسی کنید. شرح فیلدها در سابقهٔ روزانه است.

چرا ins_code نباید عدد شود؟

Section titled “چرا ins_code نباید عدد شود؟”

ins_code از Number.MAX_SAFE_INTEGER (یعنی 9007199254740991) بزرگ‌تر است و Number رقم‌های آخر آن را عوض می‌کند. روشا آن را عمداً به شکل رشته برمی‌گرداند؛ همان رشته را نگه دارید.

const insCode = "32097828799138957"; // شاخص کل, as returned by the API
console.log(String(Number(insCode))); // "32097828799138956" (wrong: the last digit changed)
console.log(Number.isSafeInteger(Number(insCode))); // false
// Correct: keep it as a string everywhere (URLs, Map keys, database columns).
const url = `/v1/indices/${insCode}/daily?limit=1`;
console.log(url);
// Only if you really need numeric ordering, use BigInt, never Number.
console.log(BigInt(insCode) > BigInt("9007199254740991")); // true

این خطا بی‌صدا است: برای بعضی کدها عدد درست از آب درمی‌آید و برای بعضی نه. پس آزمودن با یک نماد کافی نیست.

قاعده‌ها:

  • ins_code را با Number()، parseInt() یا + تبدیل نکنید.
  • اگر JSON را با کتابخانه‌ای می‌خوانید که رشته‌های عددی را خودکار عدد می‌کند، این رفتار را برای ins_code خاموش کنید.
  • BigInt در JSON.stringify خطا می‌دهد؛ پیش از سریال‌کردن آن را به رشته برگردانید.
  • در پایگاه داده، ستون را متنی بگیرید.

چطور کل سابقهٔ یک نماد را بگیرم؟

Section titled “چطور کل سابقهٔ یک نماد را بگیرم؟”

هر پاسخ حداکثر ۵٬۰۰۰ ردیف دارد؛ برای سابقهٔ بلندتر با تاریخ صفحه‌بندی کنید. هر بار end را روزِ قبل از قدیمی‌ترین ردیف گرفته‌شده بگذارید و وقتی تعداد ردیف‌ها از اندازهٔ صفحه کمتر شد بایستید.

const BASE = "https://poolick.ir";
const PAGE = 5000; // the REST cap on `limit`
async function get(path) {
for (let attempt = 0; attempt < 2; attempt++) {
const res = await fetch(BASE + path, {
headers: { Authorization: `Bearer ${process.env.ROSHA_KEY}` },
});
if (res.status === 429) {
await new Promise((r) => setTimeout(r, 1000 * Number(res.headers.get("Retry-After") ?? 1)));
continue;
}
if (!res.ok) throw new Error(`${res.status} ${(await res.json()).detail}`);
return res.json();
}
throw new Error("rate limited");
}
function dayBefore(isoDate) {
const d = new Date(isoDate + "T00:00:00Z");
d.setUTCDate(d.getUTCDate() - 1);
return d.toISOString().slice(0, 10);
}
async function fullDaily(ref, adjusted = true) {
let rows = [];
let end = null;
for (;;) {
const query = new URLSearchParams({ limit: String(PAGE), adjusted: String(adjusted) });
if (end) query.set("end", end);
const batch = (await get(`/v1/instruments/${ref}/daily?${query}`)).rows;
rows = batch.concat(rows); // each page is older than the one before
if (batch.length < PAGE) return rows;
end = dayBefore(batch[0].d);
}
}
const rows = await fullDaily("46348559193224090"); // فولاد
console.log(`${rows.length} rows from ${rows[0].d} to ${rows.at(-1).d}`);

همین الگو برای /client-type، /v1/indices/{ref}/daily و /v1/funds/{ref}/history هم کار می‌کند. شرح روش در قراردادها و قالب‌ها است.

  • زمان‌ها: فیلدهای زمان مثل ts با اختلاف ساعت تهران (+03:30) می‌آیند و new Date(ts) آن‌ها را درست می‌خواند. فیلدهای تاریخ مثل d فقط روز هستند؛ برای مقایسه همان رشتهٔ ISO کافی است.
  • اعداد بزرگ دیگر: value و net_asset عدد اعشاری‌اند و ممکن است از بازهٔ عدد صحیح امن بزرگ‌تر شوند. برای نمایش و محاسبهٔ معمول مشکلی نیست؛ برای حسابداری دقیق تا آخرین ریال، این را در نظر بگیرید.
  • سهمیه: هر فراخوانی get یک درخواست از سقف دقیقه‌ای و سهمیهٔ روزانه کم می‌کند؛ محدودیت‌ها و پلن‌ها.
  • TypeScript: نوع ins_code را string تعریف کنید.

صفحهٔ اصلی · قیمت‌ها · وضعیت · پنل · پشتیبانی
داده‌ها توصیهٔ سرمایه‌گذاری نیست. سلب مسئولیت