مرجع المؤشرات المخصّصة — Sovereign Chart
للاستخدام مع الذكاء الاصطناعي: ارفع هذا الملف مع كود PineScript إلى Claude أو ChatGPT واطلب التحويل. البرومبت الجاهز في نهاية الملف.
١. البنية
المؤشر ملف JavaScript واحد فيه شيئان فقط:
const meta = { /* وصف المؤشر */ };
function compute(candles, inputs) { /* الحساب */ }
لا تبويبات، ولا this._context، ولا فهارس رقمية للمدخلات. هذا كل شيء.
const meta = {
name: 'RSI', // ≤16 حرفاً — يظهر في وسيلة الشارت
label: 'Relative Strength Index',
color: '#4CAF50', // لون الرسم الرئيسي
overlay: false, // true = فوق الشموع | false = لوحة سفلية
levels: [70, 30], // خطوط مرجعية أفقية (اختياري)
levelAlpha: 0.15, // شفافيتها (اختياري)
showLastValue: false, // إخفاء تسمية القيمة على المحور (اختياري)
styleDefaults: { lineWidth: 2, lineStyle: 0 }, // 0=متصل 1=منقّط 2=متقطّع
inputs: [
{ id: 'period', type: 'int', label: 'الفترة', def: 14, min: 2, max: 200, control: 'slider' },
{ id: 'mult', type: 'float', label: 'المضاعف', def: 3.0, min: 0.1, max: 10, step: 0.1 },
{ id: 'fast', type: 'int', label: 'سريع', def: 12, min: 1, max: 200, group: 'سريع / بطيء' },
],
};
قواعد inputs:
id يجب أن يكون معرّفاً صالحاً ([a-zA-Z_][a-zA-Z0-9_]*) — يُستخدم مفتاحاً في inputscontrol: 'slider' يعرض منزلقاً، وبدونه حقل رقميsection يبدأ مجموعة بعنوان · inline يجمع عدّة بارامترات في صفّ واحدtooltip نصّ توضيحي عند المرور- الحدّ الأقصى ٨٠ بارامتراً
الأنواع التسعة:
type | القيمة في inputs | الواجهة | تحويل PineScript |
int / float | رقم | حقل أو منزلق | input.int / input.float |
bool | true/false | مفتاح | input.bool |
color | '#2962FF' | منتقي ألوان | input.color |
string | نصّ | حقل نصّي | input.string |
select | أحد options | قائمة منسدلة | input.string(options=[...]) |
session | '0930-1100' | منتقيا وقت | input.session |
time | '0930' | منتقي وقت | — |
timezone | 'America/New_York' | قائمة مناطق | input.timezone |
symbol | 'US30' أو '' | قائمة رموز المنصّة | input.symbol |
source | 'close' … أو ناتج مؤشر آخر | قائمة المصادر | input.source |
source — يصل compute مصفوفة قيم لا نصّاً، وقائمته تشمل نواتج المؤشرات النشطة الأخرى فتُركَّب المؤشرات بعضها على بعض. التفصيل في القسم ٣.٦.
{ id: 'src', type: 'source', label: 'المصدر', def: 'close' }
symbol — لا تكتب قائمة أزواج بيدك في select. هذا النوع يفتح قائمة رموز المنصّة نفسها (ببحثها وتصنيفاتها ومفضّلتها) كما يفعل input.symbol في TradingView، فتبقى القائمة صحيحة كلّما أُضيف زوج. قيمته نصّ رمز يُمرَّر إلى request.security مباشرةً، والفارغ '' يعني «اتبع رمز الشارت» — وهو خيار ظاهر في أعلى القائمة، فلا يعلق المستخدم على رمز ثابت.
{ id: 'partner', type: 'symbol', label: 'الرمز المقارَن', def: 'US30' }
{ id: 'useAsia', type: 'bool', label: 'آسيا', def: true, inline: 'asia', section: 'الجلسات' },
{ id: 'asiaName', type: 'string', label: 'الاسم', def: 'Asia', inline: 'asia' },
{ id: 'asiaSess', type: 'session', label: 'الوقت', def: '2000-0000', inline: 'asia' },
{ id: 'asiaColor', type: 'color', label: 'اللون', def: '#2962FF', inline: 'asia' },
المنطقة الزمنية — مترجم أوقات الجلسات
ثلاث طبقات زمنية منفصلة، تماماً كما في TradingView:
| الطبقة | من يحدّدها |
| طابع الشمعة | المغذّي (Binance بـUTC، وMT5 بتوقيت خادمه +GMT3 — يُصحَّح تلقائياً) |
| منطقة المؤشر | أنت: بها يُفسَّر نصّ session |
| منطقة المحور | المستخدم من ترس المحور — وهي وحدها ما يظهر أسفل الشارت |
الفرق بين الأخيرتين هو الإزاحة التي يراها المستخدم: جلسة مكتوبة 1300-2200 بتوقيت نيويورك تُرسم عند 20:00 على محور مضبوط ببغداد. هذا مقصود لا خلل.
منطقة المؤشر تُعلَن بإحدى طريقتين:
var meta = {
timezone: 'chart', // إعلان ثابت، أو…
inputs: [
{ id: 'zone', type: 'timezone', label: 'المنطقة الزمنية', def: 'America/New_York' }
]
};
المدخل يتقدّم على الإعلان الثابت — فالمستخدم يملك الكلمة الأخيرة. القيم: معرّف IANA ('Europe/London')، أو 'UTC'، أو 'chart' (يتبع منطقة المحور، فما يكتبه المستخدم هو ما يراه) أو 'exchange' (نيويورك للفوركس، UTC للكريبتو).
تحذير توافقية: مؤشر لا يعلن منطقته يبقى على السلوك القديم — الافتراض نيويورك، وبلا تصحيح توقيت مغذّي MT5. أضِف timezone وستنزاح جلساته عمّا اعتاده مستخدموه، فاذكر ذلك عند تحديث مؤشر منشور.
داخل compute كل شيء يتبع تلك المنطقة تلقائياً:
TA.inSession(c[i].time, S.ny) // بمنطقة المؤشر
TA.inSession(c[i].time, S.ny, 'Asia/Tokyo') // بمنطقة صريحة تتجاوزها
TA.tz(c[i].time).h // الساعة بمنطقة المؤشر
TA.tzName() // اسم المنطقة السارية — لوسم على الشارت
الوسيط الصريح نهائيّ — كما هو الوسيط الثالث في time() عند Pine. مؤشر لا يعلن meta.timezone يتبع منطقة المحور، لكنها افتراضه عند النداء الأعزل فقط ولا تدهس منطقةً مكتوبة في النداء. لو كانت تدهسها لانزاحت جلسات كل مؤشر محوَّل من Pine بفارق محور المستخدم عن المنطقة المكتوبة في أصله.
المناطق الجاهزة دائماً: UTC، نيويورك، شيكاغو، لندن، برلين، دبي، الهند، شنغهاي، طوكيو، سيدني — إضافةً إلى منطقة المؤشر ومنطقة المحور. غيرها يحتاج إعلانها في timezone. الاختصارات 'NY'/'LDN'/'TOK'/'SYD' مدعومة.
قواعد levels: إمّا أرقام [70, 30]، أو كائنات [{ value: 0, alpha: 0.1 }].
لا تحدّد color صريحاً للمستويات إلا لضرورة — اللون الافتراضي يتكيّف مع الثيم الفاتح والداكن تلقائياً، والصريح لا يتكيّف.
٣. دالة compute
function compute(candles, inputs) {
// candles: [{ time, open, high, low, close, volume }, ...] مرتّبة تصاعدياً
// inputs : { period: 14, mult: 3.0, ... } قيم المستخدم الحالية
return [ /* رسومات */ ];
}
شكل الرسم الواحد
{
id: 'main',
type: 'line', // انظر «أنواع السلاسل» أدناه
primary: true, // الرسم الرئيسي — يأخذ لون المستخدم وسُمكه
data: [{ time: 1700000000, value: 42.5 }, ...]
}
أنواع السلاسل
قيمة لكل شمعة — data: [{ time, value }] (فجوة = نقطة بلا value):
type | الشكل | مقابل Pine |
line | خطّ متصل | plot() |
histogram | أعمدة (لون لكل عمود في النقطة) | plot(style=histogram/columns) |
area | خطّ بتعبئة متدرّجة نحو القاع | plot(style=area) |
baseline | لونان حول مرجع (baseValue) — أخضر فوق/أحمر تحت | plot(style=baseline) |
stepline | خطّ يقفز بلا قطر — للمستويات الثابتة | plot(style=stepline) |
baseline يحتاج مرجعاً: { type: 'baseline', baseValue: 0, data: [...] }.
شموع OHLC — data: [{ time, open, high, low, close }] (لون اختياري لكل شمعة عبر color/wickColor/borderColor؛ فجوة = نقطة بـtime فقط):
type | الشكل | مقابل Pine |
candle | شموع يابانية مخصّصة (Heikin Ashi، فريم أعلى…) | plotcandle() |
bar | أعمدة OHLC | plotbar() |
تعبئة بين حدّين (band) — سحابة بين خطّين، بديل fill() الأنظف. تعمل على الشارت الرئيسي وفي اللوحة السفلية معاً:
{ id: 'cloud', type: 'band', color: 'rgba(38,166,154,0.15)',
data: [{ time: t, upper: 105, lower: 95 }, { time: t2 }, ...] } // نقطة بلا upper/lower = فجوة
مثال سحابة Bollinger:
const b = TA.bb(TA.src(candles), 20, 2); // { basis, upper, lower, offset }
return [
{ id: 'basis', type: 'line', primary: true, data: TA.pt(candles, b.basis, b.offset) },
{ id: 'band', type: 'band', color: 'rgba(90,200,250,0.12)',
data: candles.map((c, i) => { const k = i - b.offset;
return k >= 0 ? { time: c.time, upper: b.upper[k], lower: b.lower[k] } : { time: c.time }; }) },
];
خلفية الشمعة (bgcolor) — عمود ملوّن كامل الارتفاع خلف الشمعة، بديل bgcolor() في Pine. التفصيل والقواعد في القسم ٣.٥:
{ id: 'regime', type: 'bgcolor',
data: [{ time: t, color: 'rgba(38,166,154,0.08)' }, ...] } // شمعة بلا لون: لا تُدرجها
رسم ثانوي بنمط ثابت (لا يتأثر بإعدادات المستخدم):
{
id: 'signal', type: 'line',
style: { color: '#FF9800', lineWidth: 1, lineStyle: 0, title: '' },
data: [...]
}
تلوين كل عمود على حدة (للهيستوغرام) — اللون داخل نقطة البيانات:
{ id: 'hist', type: 'histogram',
data: [{ time: t, value: v, color: v >= 0 ? '#26a69a' : '#ef5350' }, ...] }
فجوات وخطوط قُطرية: نقطة بلا value = فجوة. الخط يصل بين النقاط ذات القيم متجاوزاً الفجوات — بهذا تُرسم أنماط zigzag والقمم/القيعان.
data.push({ time: t }); // فجوة
data.push({ time: t, value: 100 }); // نقطة
٣.١ الأشكال — صناديق وخطوط ونصوص
نموذج «قيمة لكل شمعة» لا يستطيع رسم مستطيل يمتد من شمعة ٥٠ إلى شمعة ٩٠، ولا كتابة نصّ عند سعر معيّن. لذلك يوجد نوع مخرجات ثالث:
{ id: 'zones', type: 'shapes', shapes: [ /* … */ ] }
الإحداثيات بـ (وقت يونكس بالثواني، سعر) — نفس time الموجود في الشموع.
xloc: 'bar_index' — الإحداثي برقم الشمعة (مقابل xloc.bar_index في Pine)
أضِف xloc: 'bar_index' إلى الشكل فيصير كل إحداثي أفقي فيه رقم شمعة لا وقتاً:
{ kind: 'line', xloc: 'bar_index', x1: i, x2: i + 20, y1: p1, y2: p2 }
يسري على box وline وlabel وcircle وfill (حقل t) وlinefill. الأرقام خارج المدى تُسقَط بخطوة الفريم (n + 5 = خمس شمعات بعد الأخيرة، والسالب قبل الأولى) — لكن الأوقات المستقبلية تبقى خارج المقياس الزمني كما هي موضّح في ملاحظة الإسقاط أدناه. بلا xloc يبقى x وقتاً حرفياً، فلا يتغيّر أي مؤشر قائم.
box — مستطيل
{ kind: 'box',
x1: startTime, y1: topPrice, x2: endTime, y2: bottomPrice,
bgColor: 'rgba(41,98,255,0.3)', // التعبئة
borderColor: '#2962FF',
borderWidth: 1,
style: 'solid', // 'solid' | 'dotted' | 'dashed'
text: 'Asia', // نصّ في وسط الصندوق (اختياري)
textColor: '#2962FF',
font: 'mono' } // 'sans' (افتراضي) | 'mono' | 'serif'
line — خط بين نقطتين
{ kind: 'line',
x1: t1, y1: p1, x2: t2, y2: p2,
color: '#26a69a',
width: 1,
style: 'dotted',
extend: 'right' } // 'none' | 'left' | 'right' | 'both'
الخط القُطري يُرسم بجعل y1 ≠ y2 — بهذا تُرسم خطوط الاتجاه وربط القمم بالقيعان.
الإسقاط المستقبلي: extend: 'right' يمدّ الخطّ إلى حافة اللوحة محافظاً على ميله (لخطوط الاتجاه والتنبؤ). عرّف الخطّ بنقطتين حقيقيتين ضمن البيانات ثم مدّده — لا تضع طرفاً عند وقت مستقبلي (بعد آخر شمعة) لأن الأوقات المستقبلية خارج المقياس الزمني فتُقصّ إلى الحافة بلا ميل. للإسقاط عند نقطة مستقبلية محدّدة، يجب أن تشغل تلك الأوقات سلسلة (شموع/خطّ تنبؤ) حتى تدخل المحور.
label — نصّ عند نقطة
{ kind: 'label',
x: time, y: price,
text: 'NYAM.H',
textColor: '#089981',
bgColor: 'rgba(0,0,0,0.6)', // خلفية للنصّ (اختياري)
size: 11, // 7 – 28
position: 'above', // 'above' | 'below' | 'middle'
align: 'left', // 'left' | 'center' | 'right'
font: 'mono', // 'sans' (افتراضي) | 'mono' | 'serif'
bold: true }
أنماط التسمية — style (مقابل label.style_*)
بلا style (أو style: 'none') يبقى السلوك أعلاه: نصّ حرّ عند النقطة. ومع نمط label_* تُرسم بطاقة بمؤشّر مثلّث يشير إلى النقطة — وهي تسمية Pine المعتادة:
{ kind: 'label',
x: time, y: price,
text: 'CHoCH',
style: 'label_right', // البطاقة يسار النقطة والمؤشّر يشير يميناً
bgColor: '#2962FF', // لون البطاقة (افتراضي أزرق)
textColor: '#ffffff',
borderColor: '#ffffff' } // إطار اختياري
style | موضع البطاقة | مقابل Pine |
label_right | يسار النقطة | label.style_label_right |
label_left | يمينها | label.style_label_left |
label_up | تحتها | label.style_label_up |
label_down | فوقها | label.style_label_down |
label_center | عليها تماماً (بلا مؤشّر) | label.style_label_center |
label_upper_left / label_upper_right | قطرياً أعلى | نفس الأسماء |
label_lower_left / label_lower_right | قطرياً أسفل | نفس الأسماء |
داخل البطاقة align يوسّط النصّ افتراضاً، وposition (above/below) لا أثر له — النمط هو من يحدّد الموضع. نمطٌ غير معروف يسقط إلى 'none' بدل أن يُرفض الشكل.
polyline — مسار حرّ ومضلّع (بديل polyline.new)
box محاذٍ للمحورين وfill يحتاج حدّين على نفس الوقت — فلا يرسم أيّهما مثلّثاً ولا رباعيّاً مائلاً. هذا الشكل مسارٌ واحد عبر N نقاط، يُملأ أو يُحدّ أو الاثنان، وعليه تُبنى البروفايلات «ثلاثية الأبعاد» والمثلّثات ومسارات الزيجزاج المصمتة.
{ kind: 'polyline',
points: [ { x: t1, y: p1 }, { x: t2, y: p2 }, { x: t3, y: p3 } ],
color: '#2962FF', // لون الحدّ ('width: 0' = بلا حدّ)
width: 1,
style: 'solid', // 'solid' | 'dotted' | 'dashed'
fillColor: 'rgba(41,98,255,0.25)', // اختياري — بدونه مسار بلا تعبئة
closed: true, // يُغلق الضلع الأخير (لازم للمضلّع)
curved: false } // تنعيم تربيعي (مقابل curved=true)
- الحدّ ٢٠٠٠ نقطة للمضلّع الواحد؛ والمسار نداءُ رسمٍ واحد مهما طال، فمضلّع من ٥٠٠ نقطة أرخص من ٥٠٠ صندوق.
- نقطة إحداثيها خارج مدى البيانات تُتخطّى ولا تقطع المسار — القطع كان سيفتح المضلّع فتسيل تعبئته.
xloc: 'bar_index' يسري على x في كل النقاط.- مضلّع بنقطة واحدة صالحة يُسقَط (لا مسار من نقطة).
circle — نقطة دائرية
{ kind: 'circle',
x: time, y: price,
radius: 4, // 1 – 30 بكسل
color: '#26a69a', // التعبئة
borderColor: '#ffffff', // اختياري
borderWidth: 1 }
fill — تعبئة بين حدّين (سحابة)
بديل fill() في PineScript. شكل واحد يحمل سلسلة نقاط، كل نقطة تحدّد الحدّ العلوي والسفلي عند وقت معيّن.
{ kind: 'fill',
color: 'rgba(90,200,250,0.12)',
points: [
{ t: time1, top: 105, bottom: 95 },
{ t: time2 }, // نقطة بلا top/bottom = فجوة تقطع التعبئة
{ t: time3, top: 108, bottom: 97 },
] }
الفجوة تُنهي المقطع الحالي وتبدأ آخر — استخدمها أثناء فترة الإحماء وإلا امتدّت التعبئة عبر الفراغ. الحدّ الأقصى ٦٠٬٠٠٠ نقطة لكل تعبئة.
linefill — تعبئة بين خطّين مائلين (بديل linefill.new)
fill تحتاج سلسلة نقاط بمحاذاة الشموع، وهي الأنسب للسحب والأشرطة. أمّا التعبئة بين خطّين اعتباطيّين — قناة، مروحة، مثلّث — فهذه:
{ kind: 'linefill',
color: 'rgba(41,98,255,0.15)',
extend: 'right', // يسري على الخطّين معاً
line1: { x1: t1, y1: p1, x2: t2, y2: p2 },
line2: { x1: t1, y1: p3, x2: t2, y2: p4 } }
a/b مرادفان لـ line1/line2.- لكل خطّ أن يحمل
extend خاصّاً به، وإلا ورث تمديد الشكل. - الخطّان لا يُرسمان — أضِف شكلَي
line إن أردت حدوداً مرئية. xloc: 'bar_index' يسري على الأربعة إحداثيات.- خطّ ناقص طرفاً يُسقط التعبئة كلّها (لا نصف مضلّع).
panel — لوحة إحصائيات مثبّتة (بديل table.new)
الشكل الوحيد هنا الذي لا يرتبط بوقت ولا بسعر: موضعه بالبكسل من حافة الشارت، فلا ينزاح مع التمرير أو التكبير.
{ kind: 'panel',
position: 'top_right', // top/middle/bottom × left/center/right
bgColor: 'rgba(19,23,34,0.85)',
borderColor: '#2a2e39',
borderWidth: 1,
textColor: '#d1d4dc', // لون النصّ الافتراضي لكل الخلايا
size: 11, // حجم الخطّ الافتراضي (7 – 28)
width: 25, // اختياري: ٪ من عرض الشارت لكل اللوحة
height: 12, // اختياري: ٪ من ارتفاعه
rows: [
// خليّة نصّية مباشرة، أو كائن بخصائصها
['الاتجاه', { text: 'صاعد', align: 'right', textColor: '#26a69a' }],
[{ text: 'RSI', bold: true }, { text: '62.4', align: 'right' }],
] }
خصائص الخليّة: text، textColor، bgColor، align، size، bold، font، وwidth/height كنسبة مئوية من الشارت (بديل table.cell(width=, height=)). بلا نسبة يُقاس العمود من أعرض نصّ فيه. font على اللوحة يسري على كل خلاياها، وعلى الخليّة يتقدّم عليه — و'mono' هو ما يجعل أعمدة الأرقام تصطفّ.
قواعد اللوحة:
- ٤ لوحات لكل مؤشر كحدّ أقصى، ٢٤ صفاً × ٨ أعمدة، و٦٤ حرفاً للخليّة. تُحتسب من سقف الـ ١٥٠٠ شكل بعدد خلاياها لا كشكل واحد.
- لوحات عدّة مؤشرات في نفس الموضع تتكدّس تلقائياً ولا تتراكب؛ وترتيبها ثابت لا يتبدّل بين إطار وآخر. لوحات الأسفل تتكدّس صعوداً.
- تُرسم فوق كل شيء دائماً، ولا تؤثّر في مقياس السعر إطلاقاً.
- نصّ أعرض من خليّته يُقصّ عند حدّها ولا يزحف على جارتها.
قيود الأشكال:
- تعمل على الشارت الرئيسي (
overlay: true) وفي اللوحة السفلية (overlay: false) معاً — الإحداثيات بالسعر، فمؤشر أشكال بحت في لوحة مستقلّة يضبط قيم y ضمن نطاقه الخاص. - الحدّ الأقصى ١٥٠٠ شكل. احترم بارامتراً مثل
maxDays بدل رسم كل التاريخ. - الأشكال غير تفاعلية وتُحذف تلقائياً مع المؤشر.
الرسم في لوحة فرعية مستقلّة (صفوف مكدّسة) — اقرأه قبل تحويل أي مؤشر «لوحة»
مؤشرات كثيرة (ICT Quarterly، خرائط الجلسات، مقاييس أسفل الشارت) تُرسم في لوحة منفصلة بصفوف ملوّنة لا على السعر. لتحويلها صحيحاً:
overlay: false — هذا وحده يوجّه المؤشر إلى اللوحة السفلية. overlay: true يرسم على الشموع دائماً مهما فعلت.- إحداثي
y = رقم صفّ ثابت، لا سعر. أعطِ كل دورة/صفّاً رقماً: الأول [0,1]، الثاني [1,2]… والمحور يتكيّف تلقائياً على عدد الصفوف. لا تستخدم high/low (السعر) في اللوحة الفرعية — سيمطّ المقياس (مثلاً 0..1860) ويصبح بلا معنى. box وlabel وband وbgcolor وmarkers كلها تعمل في اللوحة الفرعية. العلامات هناك تُبنى أشكالاً وتُنسَب إلى قيمة الرسم الرئيسي عند تلك الشمعة لا إلى الشمعة (لا شموع في اللوحة)، فـaboveBar تعني «فوق الخطّ». ولذلك: علامة عند وقتٍ لا قيمة للرسم الرئيسي فيه تُسقَط — إلا atPrice* التي تحمل سعرها الصريح.
قيد معماري حاسم: المؤشر الواحد = لوحة واحدة. إن كان المصدر يرسم بعض العناصر على السعر وبعضها في لوحة منفصلة معاً (مثل مفتاح Display on chart لكل دورة)، فهذا مستحيل في مؤشر واحد عندنا. اختر أحد الوضعين (غالباً اللوحة بالصفوف)، وأسقِط مفتاح الازدواج بدل تركه خياراً ميتاً، واذكر ذلك في تعليق.
// صفّ لكل دورة مُفعّلة — لا سعر
let rows = 0; const R = {};
if (inputs.showDaily) R.daily = ++rows;
if (inputs.showNinety) R.ninety = ++rows;
if (inputs.showMicro) R.micro = ++rows;
// صندوق الربع في صفّه، ملوّن حسب الربع:
shapes.push({ kind: 'box', x1: t1, x2: t2, y1: R.daily, y2: R.daily - 1,
bgColor: 'rgba(139,188,252,0.35)', borderColor: '#8bbcfc' });
shapes.push({ kind: 'label', x: t1, y: R.daily - 0.5, text: 'Q3',
textColor: '#8bbcfc', size: 9, position: 'middle' });
٣.٢ العلامات — بديل plotshape
الأسهم والمثلثات فوق الشموع وتحتها، لإشارات الدخول والخروج:
{ id: 'signals', type: 'markers', markers: [
{ time: t,
position: 'belowBar', // 'aboveBar' | 'belowBar' | 'inBar'
shape: 'arrowUp', // 'arrowUp' | 'arrowDown' | 'circle' | 'square'
color: '#26a69a',
text: 'BUY', // اختياري، ≤24 حرفاً
size: 1 }, // 0.1 – 5
]}
للتموضع عند سعر محدّد بدل ربطه بالشمعة، استخدم atPriceTop أو atPriceBottom أو atPriceMiddle — وهذه تشترط حقل price:
{ time: t, position: 'atPriceMiddle', shape: 'circle', color: '#fff', price: 65400 }
الحدّ الأقصى ٢٠٠٠ علامة، ولا حاجة لترتيبها زمنياً — يتم تلقائياً. تعمل في الشارت الرئيسي وفي اللوحة السفلية معاً. الفرق أن الشارت الرئيسي يضعها فوق/تحت الشمعة (إضافة المكتبة نفسها)، واللوحة السفلية فوق/تحت قيمة الرسم الرئيسي — انظر القاعدة ٣ في القسم السابق.
جدول تحويل أشكال PineScript:
| PineScript | هنا |
shape.triangleup / shape.arrowup | shape: 'arrowUp' |
shape.triangledown / shape.arrowdown | shape: 'arrowDown' |
shape.circle | shape: 'circle' |
shape.square / shape.diamond / shape.flag | shape: 'square' |
location.abovebar / location.belowbar | position: 'aboveBar' / 'belowBar' |
location.absolute | position: 'atPriceMiddle' + price |
يمكن الجمع بين الأشكال والسلاسل في نفس المؤشر:
return [
{ id: 'ma', type: 'line', primary: true, data: TA.pt(candles, ma, 19) },
{ id: 'zones', type: 'shapes', shapes: boxes },
];
٣.٣ الرمز والفريم — syminfo و timeframe
متغيّران عامّان داخل compute (لا يُمرّران كوسيط، تماماً كما في PineScript):
syminfo.ticker // 'BTCUSDT'
syminfo.tickerid // مثله
timeframe.period // 'M15' — بترميز المنصّة
timeframe.pine // '15' — مقابله في Pine، لنقل الكود حرفياً
timeframe.in_seconds // 900
timeframe.multiplier // 15
timeframe.isintraday // true
timeframe.isseconds / isminutes / isdaily / isweekly / ismonthly
مقابلة timeframe.pine: M15 → '15'، H4 → '240'، D1 → 'D'، W1 → 'W'، MN1 → 'M' — فالشرط timeframe.pine == '240' المنقول عن Pine يعمل كما هو.
يتغيّران تلقائياً عند تبديل الرمز أو الفريم، ويُعاد حساب المؤشر عندها. قد يعودان فارغين في مسار التحقّق قبل تحميل رمز (كما في اختبار المحرّر على الشموع الاصطناعية) — لا تفترض وجود قيمة: syminfo.ticker || '—'.
٣.٤ بيانات رمز آخر أو فريم آخر — request.security
const htf = request.security('', 'H4'); // نفس الرمز، فريم أربع ساعات
const btc = request.security('BTCUSDT', 'D1'); // رمز آخر
const h4 = request.security('H4'); // اختصار: وسيط واحد = فريم
تُرجع مصفوفة شموع [{ time, open, high, low, close, volume }] تصاعدياً — ثم احسب عليها بمكتبة TA كأي شموع، وأسقِط الناتج على الفريم الحالي بـ request.map:
function compute(candles, inputs) {
const d1 = request.security('', 'D1');
if (!d1.length) return [{ id: 'ema', type: 'line', data: TA.plot(candles, candles.map(() => NaN)) }];
const ema = TA.ema(TA.src(d1), 20); // أقصر من d1 بـ19
const full = TA.align(d1, ema, 19); // ← بطول d1
const onChart = request.map(candles, d1, full); // ← بطول الشموع الحالية
return [{ id: 'ema', type: 'line', data: TA.plot(candles, onChart) }];
}
| الدالة | الوصف |
request.security(sym, tf) | شموع الرمز/الفريم المطلوب (فارغة إن لم تصل بعد) |
request.ready(sym, tf) | هل وصلت؟ للتمييز عن «لا بيانات» |
request.map(candles, src, vals, opts) | إسقاط سلسلة المصدر على الشموع الحالية |
request.step(src) | مدّة شمعة المصدر بالثواني |
الترميز: الرمز الفارغ = الرمز الحالي، والفريم الفارغ = فريم الشارت. الفريم يُقبل بترميز المنصّة ('H4'، 'D1'، 'MN1') أو بترميز Pine ('240'، 'D'، '3M')، و'BINANCE:BTCUSDT' يُقرأ رمزاً صحيحاً.
الرموز المتاحة — وما عداها لا يُجلب أبداً (كود المؤشر مشارَك، فلا يقرّر هو ما يُطلب من الشبكة):
| السوق | الرموز |
| كريبتو سبوت | BTCUSDT ETHUSDT SOLUSDT XRPUSDT BNBUSDT ADAUSDT DOGEUSDT SHIBUSDT DOTUSDT LTCUSDT LINKUSDT |
| كريبتو عقود | نفسها بلاحقة f — BTCUSDTf … |
| مؤشرات وسلع | US500 US30 US100 WTI BRENT XAUUSD XAGUSD |
| فوركس | EURUSD GBPUSD USDJPY USDCAD USDCHF EURJPY GBPJPY EURAUD EURCAD EURNZD GBPAUD GBPCAD GBPNZD |
مقابلة العقود الأمريكية عندنا: ES → US500، YM → US30، NQ → US100 — وهي ما تُستعمل في مؤشرات SMT المنقولة عن TradingView.
مقارنة رمزين على نفس الفريم (SMT وما شابهه)
هنا تنقلب القاعدة الثانية: request.map بلا lookahead يُرجع آخر شمعة مغلقة، وعلى نفس الفريم تلك هي الشمعة السابقة — فتنزاح المقارنة شمعةً كاملة. مقارنة رمزين متزامنين تحتاج { lookahead: true }، وهذا ليس استشرافاً: الشمعتان تُغلقان في اللحظة نفسها.
const meta = {
name: 'SMT', label: 'SMT Divergence', overlay: true,
inputs: [
// نوع `symbol` يفتح قائمة رموز المنصّة — لا تكتب قائمة أزواج بيدك
{ id: 'partner', type: 'symbol', label: 'الرمز المقارَن', def: 'US30' },
{ id: 'left', type: 'int', label: 'يسار', def: 3, min: 1, max: 20 },
{ id: 'right', type: 'int', label: 'يمين', def: 3, min: 1, max: 20 },
],
};
function compute(candles, inputs) {
const other = request.security(inputs.partner, ''); // نفس الفريم
const shapes = [];
if (other.length) { // ← أول تشغيلة تعود []
const oHigh = request.map(candles, other, other.map(c => c.high), { lookahead: true });
const ph = TA.pivothigh(TA.src(candles, 'high'), inputs.left, inputs.right);
let prev = -1;
for (let i = 0; i < candles.length; i++) {
if (!isFinite(ph[i])) continue;
if (prev >= 0 && isFinite(oHigh[i]) && isFinite(oHigh[prev])) {
const mine = ph[i] > ph[prev], theirs = oHigh[i] > oHigh[prev];
// قمّة أعلى هنا وقمّة أدنى هناك (أو العكس) = دايفرجنس
if (mine !== theirs) shapes.push({
kind: 'label', x: candles[i].time, y: ph[i], text: 'SMT',
style: 'label_down', bgColor: mine ? '#ef5350' : '#26a69a',
});
}
prev = i;
}
}
return [{ id: 'smt', type: 'shapes', shapes }];
}
### القاعدتان اللتان لا تُخالَفان ١. أول تشغيلة تعود فارغة — دائماً. العزل لا يملك شبكة ولا انتظاراً: request.security تُسجّل الطلب وتعود بمصفوفة فارغة، فيجلبها المضيف في الخلفية ثم يعيد حساب المؤشر فتصل. اكتب الكود ليحتمل [] (كما في المثال أعلاه) وإلا وميضَ خطأً عند كل تفعيل وتبديل رمز. ٢. request.map بلا lookahead لا يعيد الرسم. الافتراض أن قيمة الشمعة الحالية هي قيمة آخر شمعة مغلقة من المصدر — أي ما كان معلوماً فعلاً في حينه. { lookahead: true } يُظهر الشمعة الجارية، فيبدو التاريخ أدقّ ممّا كان ممكناً: صالح للعرض، لا لإشارة دخول.
الحدود: أربع سلاسل لكل مؤشر (الخامسة تعود فارغة أبداً)، وألف شمعة لكل سلسلة، وتُحدَّث كل دقيقة. السلاسل من سوق آخر تُزاح تلقائياً إلى اصطلاح توقيت الشارت (مغذّي MT5 يسبق UTC بثلاث ساعات) فتقع على شموعها الصحيحة. وفي جلسة الباك تيست تُقصّ السلسلة عند ساعة الإعادة فلا يرى المؤشر مستقبلاً لم يكن معلوماً.
في المحرّر ينتظر زرّ التشغيل وصول البيانات مرّة واحدة ليريك نتيجة مكتملة؛ فإن لم تصل ظهر سطر في لوحة التصحيح يسمّي ما نقص. وبوّابة النشر تحكم على الكود بشموع اصطناعية قديمة التواريخ — فقد يخرج الإسقاط كلّه NaN هناك؛ أرجِع سلسلة ولو فارغة القيم كي تمرّ.
٣.٥ تلوين خلفية الشمعة — بديل bgcolor
عمودٌ ملوّن كامل الارتفاع خلف الشمعة. هو ما تحتاجه مؤشرات «الاتجاه» و«نظام السوق»: أخضر حين يكون النظام صاعداً، أحمر حين يكون هابطاً، بلا لون في الحياد.
{ id: 'regime', type: 'bgcolor', data: [
{ time: t, color: 'rgba(38,166,154,0.08)' },
// الشمعة التي لا تُلوَّن: لا تُدرجها أصلاً — لا نقطة بلا لون
] }
مثال كامل داخل compute:
const ema = TA.align(candles, TA.ema(TA.src(candles, 'close'), inputs.len), inputs.len - 1);
const bg = [];
for (let i = 0; i < candles.length; i++) {
if (!isFinite(ema[i])) continue; // فترة الإحماء بلا لون
bg.push({ time: candles[i].time,
color: candles[i].close > ema[i] ? 'rgba(38,166,154,0.10)'
: 'rgba(239,83,80,0.10)' });
}
return [
{ id: 'ema', type: 'line', primary: true, data: TA.pt(candles, ema) },
{ id: 'bg', type: 'bgcolor', data: bg },
];
قواعد الخلفية:
- تُرسم تحت كل شيء — تحت أشكال المؤشر نفسه وتحت الشموع.
- لا تؤثّر في مقياس السعر إطلاقاً: لا سعر لها أصلاً، تمتدّ من أعلى اللوحة إلى أسفلها.
- الشمعات المتجاورة ذات اللون الواحد تُدمج تلقائياً في عمود واحد، فلا تقلق من تلوين التاريخ كلّه: ٣٠٠٠ شمعة بلونين تصير عشرات الأعمدة لا ٣٠٠٠ شكل. (ولا تُحتسب من سقف الـ ١٥٠٠ شكل — هي ليست أشكالاً.)
- استعمل ألواناً شفّافة جداً (٠٫٠٥–٠٫١٥). اللون المصمت يبتلع الشموع.
- تعمل في الشارت الرئيسي وفي اللوحة السفلية معاً.
مدخل يختار به المستخدم ما يتغذّى عليه المؤشر. الفرق عن select: القيمة تصل compute مصفوفة قيم جاهزة بطول الشموع، تماماً كما في PineScript — لا نصّاً تترجمه بنفسك.
var meta = {
name: 'MYRSI', label: 'RSI بمصدر', overlay: false, color: '#5ac8fa',
inputs: [
{ id: 'len', type: 'int', label: 'الطول', def: 14, min: 2, max: 200 },
{ id: 'src', type: 'source', label: 'المصدر', def: 'close' },
],
};
function compute(candles, inputs) {
// inputs.src مصفوفة أرقام بطول candles — لا 'close' ولا 'hlc3'
const r = TA.rsi(inputs.src, inputs.len);
return [{ id: 'main', type: 'line', primary: true,
data: TA.pt(candles, TA.align(candles, r, inputs.len)) }];
}
القائمة التي يراها المستخدم:
| المجموعة | الخيارات |
| أسعار الشمعة | close open high low hl2 hlc3 ohlc4 hlcc4 |
| نواتج المؤشرات النشطة | كل رسم من كل مؤشر آخر مُفعَّل على الشارت الآن |
المجموعة الثانية هي تركيب المؤشرات: RSI على متوسّط متحرّك، متوسّط على CVD، Bollinger على ناتج مؤشرك المخصّص. لا تكتب لها شيئاً — يكفي أن تعلن مدخل source واحداً، والباقي اختيار المستخدم من لوحة الإعدادات.
قواعد المصدر:
TA.src(candles, 'hl2') يبقى موجوداً لمن يريد مصدراً ثابتاً في الكود بلا مدخل. المدخل هو ما يجعله اختيار المستخدم.- اكتب الكود ليحتمل
NaN. ناتج مؤشر آخر يبدأ بفترة إحماء، والمؤشر المُغذّي قد يُوقَف فتصل السلسلة كلّها NaN. لا ترمِ خطأً — أرجِع سلسلة ولو فارغة القيم. - تأخّر دورة رسم واحدة. المصدر هو آخر ما رُسم فعلاً للمؤشر المُغذّي، لا إعادة حساب له. أثره محصور في آخر شمعة أثناء التيك الحيّ، والدورتان تتقاربان فوراً. (هذا ما يمنع التكرار الدائري: أ يقرأ ب يقرأ أ.)
- تبديل الرمز أو الفريم آمن: المحاذاة بالوقت لا بالفهرس، فبيانات السوق القديم لا تطابق أوقات الشموع الجديدة فتخرج فجوةً لا قيمةً كاذبة.
- المؤشرات المدمجة (SMA وRSI وما إليها) تبقى على مصدرها النصّي القديم — تركيب المؤشرات ميزةُ المؤشرات المخصّصة.
٤. مكتبة TA
كل الدوال متاحة عالمياً بلا استيراد.
تحويل
| الدالة | الوصف |
TA.src(candles, 'close') | استخراج مصفوفة أسعار (open/high/low/close/volume) |
TA.pt(candles, values, offset) | مهمّة — تربط مصفوفة قيم بأوقات الشموع |
TA.hl2(c) TA.hlc3(c) TA.ohlc4(c) | متوسطات السعر |
TA.pt وoffset: الدوال ذات النافذة تُرجع مصفوفة أقصر من الشموع. offset هو فهرس الشمعة المقابلة لأول قيمة.
const closes = TA.src(candles);
const ma = TA.sma(closes, 20); // الطول = candles.length - 19
TA.pt(candles, ma, 19); // ← 19 = 20 - 1
قاعدة الإزاحة: sma/ema/stdev/highest/lowest ← n - 1 · rma/rsi ← n · tr ← 1
متوسطات ومقاييس
| الدالة | ملاحظة |
TA.sma(values, n) | الطول: len - n + 1 |
TA.ema(values, n) | الطول: len - n + 1 |
TA.rma(values, n) | متوسط Wilder — الطول: len - n + 1 |
TA.stdev(values, n) | انحراف معياري (منحاز) |
TA.highest(values, n) / TA.lowest(values, n) | القمة/القاع خلال n |
TA.change(values) | الفرق عن السابق — نفس الطول، أول عنصر 0 |
TA.tr(candles) | المدى الحقيقي — الطول: len - 1 (بديل ta.tr) |
TA.tr(candles, true) | كامل الطول، الشمعة ٠ = high - low (بديل ta.tr(true)) |
TA.rsi(values, n) | جاهز — الطول: len - n |
المكتبة الموسّعة
اصطلاحان لا ثالث لهما:
- دوال النافذة تُرجع مصفوفة أقصر بإزاحة معروفة (مثل
sma) — استخدمها مع TA.pt(candles, out, offset) أو TA.align. - مساعدات الإشارات (
crossover, rising, barssince, pivot*, valuewhen) تعمل على مصفوفات بطول الشموع نفسه — ارفع أي مصفوفة قصيرة إليها بـ TA.align.
متوسطات:
| الدالة | الإزاحة |
TA.wma(v, n) | n - 1 — متوسط مرجّح خطّياً |
TA.hma(v, n) | (n-1) + (⌊√n⌋-1) — Hull |
TA.vwma(candles, n) | n - 1 — مرجّح بالحجم |
TA.swma(v) | 3 — أوزان [1,2,2,1]/6 |
TA.linreg(v, n) | n - 1 — قيمة انحدار خطّي |
مقاييس ونطاقات:
| الدالة | الإزاحة / الملاحظة |
TA.atr(candles, n) | n - 1 — مطابِقة لـta.atr: TA.rma(TA.tr(c, true), n) |
TA.sum(v, n) | n - 1 — مجموع متحرّك |
TA.cum(v) | كامل الطول — مجموع تراكمي |
TA.roc(v, n) / TA.mom(v, n) | n — تغيّر نسبي / مطلق |
TA.dev(v, n) | n - 1 — انحراف مطلق متوسط |
TA.variance(v, n) | n - 1 |
TA.percentrank(v, n) | n |
TA.correlation(a, b, n) | n - 1 — a,b بطول واحد |
مذبذبات:
| الدالة | الإزاحة |
TA.cci(candles, n) | n - 1 |
TA.wpr(candles, n) | n - 1 — Williams %R |
TA.stoch(candles, n) | n - 1 — %K الخام (نعّمه بـ sma لـ %D) |
TA.cmo(v, n) | n — Chande Momentum |
TA.mfi(candles, n) | n — Money Flow Index |
حزم — تُرجع كائناً فيه offset مشترك لكل مصفوفاته:
const m = TA.macd(close, 12, 26, 9); // { macd, signal, hist, offset } — الإزاحة slow-1
TA.pt(candles, m.hist, m.offset); // ارسمها بالإزاحة المشتركة
const band = TA.bb(close, 20, 2); // { basis, upper, lower, offset }
TA.dc(candles, 20); // Donchian { upper, lower, basis, offset }
TA.kc(candles, 20, 2); // Keltner { basis, upper, lower, offset }
TA.vwap(candles); // كامل الطول — يُصفَّر كل يوم NY
الإزاحة المشتركة هي إزاحة أبكر مصفوفة في الحزمة، والمصفوفات التي تبدأ بعدها تحمل NaN في مقدّمتها — تماماً كما تكون na في Pine. في TA.macd إزاحة الجميع slow - 1، وتظلّ signal وhist بـNaN حتى sig - 1 شمعة بعدها. لا تقصّ المقدّمة بيدك: TA.pt وTA.plot يحوّلان NaN فجوةً.
مساعدات الإشارات (كامل الطول):
| الدالة | الوصف |
TA.align(candles, short, offset) | مصفوفة قصيرة ← بطول الشموع (NaN في الإحماء) |
TA.plot(candles, full) | مصفوفة كاملة ← نقاط رسم (فجوة عند NaN) |
TA.crossover(a, b) / TA.crossunder(a, b) / TA.cross(a, b) | تقاطعات ← مصفوفة منطقية |
TA.rising(v, n) / TA.falling(v, n) | صعود/هبوط n شمعة متتالية |
TA.barssince(cond) | عدد الشموع منذ آخر true |
TA.valuewhen(cond, src, occ) | قيمة src عند آخر تحقّق (occ= رقم الحدوث) |
TA.pivothigh(v, left, right) / TA.pivotlow(...) | قمم/قيعان محورية — قيمة عند المحور، NaN عداه |
TA.nz(x, r) / TA.na(x) | بديل na: استبدال / فحص |
النمط الموصى به مع التقاطعات: ارفع المتوسطين لكامل الطول ثم قاطِع:
const f = TA.align(candles, TA.ema(close, 12), 11);
const s = TA.align(candles, TA.ema(close, 26), 25);
const buy = TA.crossover(f, s); // مصفوفة منطقية بطول الشموع
الوقت والجلسات
Intl غير موجود في بيئة التنفيذ، فلا تحاول استخدام toLocaleString أو Intl.DateTimeFormat — استخدم هاتين بدلاً منهما. التوقيت الصيفي الأمريكي محسوب فيهما.
| الدالة | الوصف |
TA.tz(unixSeconds, zone) | تفكيك الوقت لمنطقة زمنية |
TA.inSession(unixSeconds, 'HHMM-HHMM', zone) | هل الوقت داخل جلسة؟ نفس صيغة Pine كاملةً |
نصّ الجلسة يقبل صيغة Pine بحذافيرها:
| الصيغة | المعنى |
'0930-1600' | مدى واحد — البداية شاملة والنهاية حصرية |
'2000-0400' | يعبر منتصف الليل |
'0000-0000' | اليوم كامل |
'1000-1100,1400-1500' | مدَيان أو أكثر بفواصل |
'0930-1600:23456' | لاحقة أيام — ١=الأحد … ٧=السبت (هنا الإثنين..الجمعة) |
في المدى العابر لمنتصف الليل تُنسب الجلسة إلى يوم بدايتها، كما في Pine: '2000-0400:23456' يشمل فجر السبت (امتداد الجمعة) ويستبعد فجر الإثنين (امتداد الأحد الممنوع). يحرس هذا npm run test:session.
zone إمّا معرّف IANA ('Europe/London')، أو اختصار ('NY'/'LDN'/'TOK'/'SYD')، أو رقم إزاحة بالساعات (3 = GMT+3)، أو تُترك فارغة فتؤخذ منطقة المؤشر المعلَنة في meta.timezone أو في مدخل type:'timezone' (انظر «مترجم أوقات الجلسات» أعلاه). مؤشر لم يعلن منطقته يبقى افتراضه نيويورك للتوافقية.
const t = TA.tz(candle.time); // { y, mo, d, h, mi, dow, key }
t.h // الساعة بمنطقة المؤشر
t.dow // 0 = الأحد
t.key // 20240724 — مفتاح اليوم، لتجميع الجلسات
TA.inSession(candle.time, '0930-1100') // جلسة نيويورك الصباحية
TA.inSession(candle.time, '2000-0000') // جلسة آسيا (تعبر منتصف الليل)
تنبيه: أيام تغيّر التوقيت الصيفي، بعض الساعات لا توجد أصلاً (تقفز 02:00 → 03:00 في مارس). جلسة تبدأ 02:00 ستبدأ 03:00 ذلك اليوم — هذا صحيح وليس خطأ.
Math وJSON متاحان بالكامل. أي شيء آخر (fetch, window, setTimeout, Intl, Date بمناطق زمنية محلية) غير موجود.
console.log يعمل للتصحيح. رسائله (وconsole.warn/console.error) تظهر في لوحة الاختبار داخل المحرّر بعد الضغط على «اختبار» — وحدها، بلا أي أثر على الشارت، وتظهر حتى لو رمى الكود خطأً (الرسائل قبل الخطأ). هذا بديلنا لغياب أدوات التصحيح في PineScript: اطبع أي قيمة أثناء الحساب وراجعها. الحدّ ٢٠٠ رسالة.
٥. جدول التحويل من PineScript
| PineScript | Sovereign JS |
input.int(14, "الفترة") | مدخل type:'int' ثم inputs.period |
input.bool / input.color / input.session | type: 'bool' / 'color' / 'session' |
input.string(options=[…]) | type: 'select' مع options |
group= / inline= في Pine | section: / inline: |
close / high / hlc3 | TA.src(candles) / TA.src(candles,'high') / TA.hlc3(candles) |
ta.sma(close, n) | TA.sma(TA.src(candles), n) |
ta.rma(x, n) | TA.rma(x, n) |
ta.atr(n) | TA.atr(candles, n) |
ta.highest(high, n) | TA.highest(TA.src(candles,'high'), n) |
ta.change(x) | TA.change(x) |
ta.macd / ta.bb / ta.cci / ta.mfi / ta.wpr | TA.macd / TA.bb / TA.cci / TA.mfi / TA.wpr |
ta.wma / ta.hma / ta.vwma / ta.vwap | TA.wma / TA.hma / TA.vwma / TA.vwap |
ta.crossover(a, b) | TA.crossover(a, b) (مصفوفتان بطول الشموع — TA.align أولاً) |
ta.barssince / ta.valuewhen / ta.pivothigh | TA.barssince / TA.valuewhen / TA.pivothigh |
x[1] (قيمة سابقة) | arr[i - 1] |
var float s = na + s := … | متغيّر عادي في حلقة for |
time("", "0930-1100", "America/New_York") | TA.inSession(candle.time, '0930-1100', 'America/New_York') |
input.timezone(…) | مدخل type:'timezone' (أو meta.timezone) |
input.symbol(…) | مدخل type:'symbol' — يفتح قائمة رموز المنصّة، والفارغ = رمز الشارت |
hour / dayofweek (بمنطقة زمنية) | TA.tz(candle.time).h / .dow |
syminfo.timezone | 'exchange' كقيمة للمنطقة |
plot(x) | رسم { type: 'line', data: … } |
plot(x, style=histogram) | { type: 'histogram', data: … } |
hline(70) | meta.levels: [70] |
box.new(l, t, r, b) | { kind: 'box', x1, y1, x2, y2 } |
line.new(x1, y1, x2, y2) | { kind: 'line', x1, y1, x2, y2 } |
label.new(x, y, txt) | { kind: 'label', x, y, text } |
label.style_label_right / _left / _up … | style: 'label_right' / 'label_left' / 'label_up' … |
font.family_default / font.family_monospace | font: 'sans' / 'mono' (وأيضاً 'serif') |
linefill.new(l1, l2, color) | { kind: 'linefill', line1, line2, color } |
xloc.bar_index / xloc.bar_time | xloc: 'bar_index' على الشكل / الافتراض (الوقت) |
request.security(sym, tf, expr) | request.security(sym, tf) ثم احسب بـTA وأسقِط بـrequest.map (القسم ٣.٤) |
barmerge.lookahead_on | request.map(…, { lookahead: true }) |
polyline.new(points, curved, closed, fill_color) | { kind: 'polyline', points, curved, closed, fillColor } |
chart.point.from_index(i, price) | xloc: 'bar_index' مع x: i |
chart.point.from_time(t, price) | الافتراض — x: t |
.set_right() / .set_x2() وما شابه | احسب القيمة النهائية مباشرة — لا تحديث تدريجي |
.delete() / حدود max_boxes_count / max_polylines_count / max_lines_count | لا تُنشئ الشكل أصلاً — قصّ القائمة قبل الإرجاع (السقف ١٥٠٠ شكل) |
line.all / box.all / polyline.all | لا مقابل ولا حاجة: الأشكال تُبنى من الصفر في كل حساب |
barstate.isfirst / barstate.islast | i === 0 / i === candles.length - 1 داخل حلقتك |
barstate.isconfirmed | i < candles.length - 1 (الأخيرة وحدها غير مؤكّدة) |
var / varip | متغيّر عادي قبل حلقة for |
plotshape(cond, style=shape.triangleup) | { type: 'markers', markers: [...] } (القسم ٣.٢) |
plot(x, style=area/stepline/baseline) | { type: 'area' / 'stepline' / 'baseline' } |
plotcandle(o,h,l,c) / plotbar(...) | { type: 'candle' / 'bar', data: [{time,open,high,low,close}] } |
fill(p1, p2, color) | { type: 'band', data: [{time, upper, lower}] } (يعمل في اللوحة السفلية) — أو { kind: 'fill' } في shapes |
plot(x, style=plot.style_circles) | { kind: 'circle' } أو markers بـ shape:'circle' |
table.new(pos) + table.cell(...) | { kind: 'panel', position, rows } |
table.cell(width=, height=) | width / height على الخليّة (٪ من الشارت) |
syminfo.ticker / syminfo.tickerid | syminfo.ticker |
timeframe.period | timeframe.period (ترميز المنصّة) أو timeframe.pine |
timeframe.isintraday / .isdaily / .multiplier | نفس الأسماء |
bgcolor(color) | { type: 'bgcolor', data: [{time, color}] } (القسم ٣.٥) |
input.source(close) | { id, type: 'source', def: 'close' } — تصل compute مصفوفةً (القسم ٣.٦) |
input.source من مؤشر آخر | مدعوم: يختار المستخدم ناتج أي مؤشر نشط من نفس القائمة |
alert() / alertcondition() | غير مدعوم |
فروق جوهرية
- لا تنفيذ لكل شمعة. PineScript يُنفَّذ مرّة لكل شمعة وله ذاكرة تلقائية؛ هنا تُستدعى
compute مرّة واحدة بكل الشموع. اكتب حلقة for صريحة.
- الحالة يدوية. بديل
var في Pine هو متغيّر خارج الحلقة:
// Pine: var float trend = 1.0
// trend := close > x ? 1 : -1
let trend = 1;
for (let i = 0; i < n; i++) {
trend = closes[i] > x[i] ? 1 : -1;
}
na تصبح فجوة — لا تُرجع NaN، أعِد { time } بلا value.
- الأشكال تُبنى مرّة واحدة نهائية. في Pine تُنشئ
box.new صندوقاً ثم تُحدّثه بـ set_right() على كل شمعة. هنا مرّر على الشموع أولاً لتحديد بداية الشكل ونهايته، ثم أنشئه مرّة واحدة بقيمه النهائية.
// Pine: if newSession: box.new(...) ثم box.set_right(time) على كل شمعة
// هنا: اجمع مدى الجلسة أولاً
const runs = [];
let cur = null;
for (const c of candles) {
if (TA.inSession(c.time, '0930-1100')) {
if (!cur) { cur = { t1: c.time, t2: c.time, hi: c.high, lo: c.low }; runs.push(cur); }
else { cur.t2 = c.time; cur.hi = Math.max(cur.hi, c.high); cur.lo = Math.min(cur.lo, c.low); }
} else cur = null;
}
// ثم أنشئ الصناديق دفعة واحدة
const boxes = runs.slice(-maxDays).map(r =>
({ kind: 'box', x1: r.t1, y1: r.hi, x2: r.t2, y2: r.lo, bgColor: '...' }));
- بديل
max_boxes_count و.delete() — لا تُدِر دورة حياة الأشكال. قصّ القائمة بـ .slice(-n) قبل الإرجاع، والتزم بحدّ ١٥٠٠ شكل.
٦. الحدود
| الحدّ | القيمة | عند التجاوز |
| زمن التنفيذ | ٢ ثانية | «تجاوز الكود المهلة» |
| الذاكرة | 64MB | «تجاوز حدّ الذاكرة» |
| عدد النقاط | ٢٠٠٬٠٠٠ | «تجاوز الحدّ الأقصى للنقاط» |
| عدد الأشكال | ١٬٥٠٠ | تُتجاهل الزائدة بصمت |
| عدد العلامات | ٢٬٠٠٠ | تُتجاهل الزائدة بصمت |
| نقاط التعبئة الواحدة | ٦٠٬٠٠٠ | تُقتطع |
عدد اللوحات (panel) | ٤ لكل مؤشر | تُتجاهل الزائدة |
| صفوف/أعمدة اللوحة | ٢٤ × ٨ | تُقتطع |
| نصّ خليّة اللوحة | ٦٤ حرفاً | يُقتطع |
| عدد الرسومات | ٨ | تُتجاهل الزائدة |
| حجم الكود | 120KB | يُرفض الحفظ |
خلايا اللوحة تُحتسب من سقف الـ ١٬٥٠٠ شكل بعددها لا كشكل واحد — لوحة ١٠×٣ تستهلك ٣٠ منه.
الكود يعمل داخل عزل QuickJS: لا وصول للشبكة أو الصفحة أو التخزين. القيم غير الصالحة (ألوان، سُمك، نصوص) تُنقَّى تلقائياً بدل رفض المؤشر.
٧. مثال كامل — Supertrend
const meta = {
name: 'Supertrend',
label: 'Supertrend (ATR Trailing Stop)',
color: '#26a69a',
overlay: true,
inputs: [
{ id: 'period', type: 'int', label: 'فترة ATR', def: 10, min: 1, max: 100, control: 'slider' },
{ id: 'mult', type: 'float', label: 'المضاعف', def: 3.0, min: 0.5, max: 10, step: 0.1 },
],
};
function compute(candles, inputs) {
const n = inputs.period, mult = inputs.mult;
const hl2 = TA.hl2(candles);
const atr = TA.atr(candles, n); // إزاحة n-1 — مطابِقة لـ ta.atr
const up = [], dn = [];
let prevUp = null, prevDn = null, trend = 1;
for (let i = 0; i < candles.length; i++) {
const t = candles[i].time;
const k = i - (n - 1); // فهرس ATR المقابل للشمعة i
if (k < 0 || i === 0) { up.push({ time: t }); dn.push({ time: t }); continue; } // إحماء (وi=0 لأن السطر أدناه يقرأ الشمعة السابقة)
let u = hl2[i] - mult * atr[k];
let d = hl2[i] + mult * atr[k];
// القيود الزاحفة — بديل up := close[1] > up1 ? max(up, up1) : up
const pc = candles[i - 1].close;
if (prevUp !== null && pc > prevUp) u = Math.max(u, prevUp);
if (prevDn !== null && pc < prevDn) d = Math.min(d, prevDn);
if (prevUp !== null) {
if (trend === -1 && candles[i].close > prevDn) trend = 1;
else if (trend === 1 && candles[i].close < prevUp) trend = -1;
}
// فجوة على الجانب غير النشط ← الخط ينقطع عند انعكاس الاتجاه
if (trend === 1) { up.push({ time: t, value: u }); dn.push({ time: t }); }
else { dn.push({ time: t, value: d }); up.push({ time: t }); }
prevUp = u; prevDn = d;
}
return [
{ id: 'up', type: 'line', primary: true, data: up },
{ id: 'dn', type: 'line', style: { color: '#ef5350', lineWidth: 2, title: '' }, data: dn },
];
}
نمط مُوصى به: لاحظ أن الحلقة تدور على فهرس الشموع لا على فهرس المصفوفة المحسوبة، وتُحوّل بـ k = i - offset. هذا أقلّ عرضة لأخطاء الإزاحة من الدوران على المصفوفة القصيرة، ويجعل ربط كل نقطة بشمعتها بديهياً.
٧.١ مثال بالأشكال — صناديق الجلسات
يوضّح النمط الكامل: كشف الجلسة بالتوقيت، تجميع الشموع، قصّ القائمة، ثم بناء الصناديق والخطوط والتسميات دفعة واحدة.
const meta = {
name: 'Sessions', label: 'Killzone Sessions', color: '#2196F3', overlay: true,
inputs: [
// بها تُفسَّر أوقات SESSIONS أدناه — بلا هذا المدخل يكون الافتراض نيويورك
{ id: 'zone', type: 'timezone', label: 'المنطقة الزمنية', def: 'chart' },
{ id: 'maxDays', type: 'int', label: 'حدّ الجلسات', def: 3, min: 1, max: 20, control: 'slider' },
],
};
const SESSIONS = [
{ session: '2000-0000', text: 'Asia', hi: 'AS.H', lo: 'AS.L', rgb: [41, 98, 255] },
{ session: '0200-0500', text: 'London', hi: 'LO.H', lo: 'LO.L', rgb: [255, 82, 82] },
{ session: '0930-1100', text: 'NY AM', hi: 'NYAM.H', lo: 'NYAM.L', rgb: [8, 153, 129] },
];
function compute(candles, inputs) {
if (!candles.length) return [{ id: 'kz', type: 'shapes', shapes: [] }];
const lastTime = candles[candles.length - 1].time;
const shapes = [];
for (const S of SESSIONS) {
const rgb = S.rgb.join(',');
const boxColor = `rgba(${rgb},0.3)`, lineColor = `rgba(${rgb},1)`;
// ١) تجميع الشموع المتتالية داخل الجلسة
const runs = [];
let cur = null;
for (const c of candles) {
if (TA.inSession(c.time, S.session)) {
if (!cur) { cur = { t1: c.time, t2: c.time, hi: c.high, lo: c.low }; runs.push(cur); }
else { cur.t2 = c.time; cur.hi = Math.max(cur.hi, c.high); cur.lo = Math.min(cur.lo, c.low); }
} else cur = null;
}
// ٢) آخر maxDays جلسة فقط — بديل max_boxes_count و .delete()
for (const K of runs.slice(-inputs.maxDays)) {
shapes.push({ kind: 'box', x1: K.t1, y1: K.hi, x2: K.t2, y2: K.lo,
bgColor: boxColor, borderColor: boxColor,
text: S.text, textColor: lineColor });
// ٣) خطوط القمة/القاع تمتد حتى الاختراق
let hiEnd = lastTime, loEnd = lastTime, hiHit = false, loHit = false;
for (const c of candles) {
if (c.time <= K.t2) continue;
if (!hiHit && c.high > K.hi) { hiEnd = c.time; hiHit = true; }
if (!loHit && c.low < K.lo) { loEnd = c.time; loHit = true; }
if (hiHit && loHit) break;
}
shapes.push({ kind: 'line', x1: K.t1, y1: K.hi, x2: hiEnd, y2: K.hi, color: lineColor });
shapes.push({ kind: 'line', x1: K.t1, y1: K.lo, x2: loEnd, y2: K.lo, color: lineColor });
shapes.push({ kind: 'label', x: K.t1, y: K.hi, text: S.hi, textColor: lineColor, position: 'above', size: 10 });
shapes.push({ kind: 'label', x: K.t1, y: K.lo, text: S.lo, textColor: lineColor, position: 'below', size: 10 });
}
}
return [{ id: 'kz', type: 'shapes', shapes: shapes }];
}
٨. البرومبت الجاهز — على مرحلتين
المؤشرات الجادّة تتجاوز مئة مدخل. حين يُطلب من النموذج إخراج الإعدادات والخوارزمية في ردّ واحد، تتنافسان على انتباهه وعلى طول المخرَج — فيقدّم الخوارزمية ويختصر الإعدادات. الفصل يُنهي هذا التنافس، ويتيح لك مراجعة قائمة الإعدادات قبل أن يُكتب الحساب أصلاً.
المرحلة ١ — استخراج الإعدادات
ارفع هذا الملف + كود PineScript، وانسخ ما بين السطرين.
اقرأ مؤشر PineScript المرفق واستخرج إعداداته فقط. لا تكتب أي كود حساب الآن.
المطلوب شيئان:
أولاً — جدول أمانة يسرد كل `input` في المصدر:
| المدخل في Pine | النوع | المقابل عندنا | ملاحظة |
القاعدة: اعرض كل مدخل **يغيّر ما يُرسم فعلاً** — حتى لو كان معطّلاً افتراضياً
(input.bool(false))، فاعرضه كـ bool بقيمة def: false.
لا تعرض المدخلات المرتبطة بميزات غير مدعومة (التنبيهات وحدها عملياً)، بل اكتب في
عمود الملاحظة سبب استحالتها. أمّا مدخلات `bgcolor` و`input.source` فاعرضها:
كلاهما مدعوم (القسمان ٣.٥ و٣.٦)، و`input.source` يصير `type:'source'`.
أمّا مدخلات الجدول (`table`) — موضعه وألوانه وحجم خطّه — فاعرضها كلّها: الجدول
مدعوم عبر `kind:'panel'`. وكذلك مدخلات عرض الرمز/الفريم: كلاهما متاح. ومدخل
الفريم الأعلى (`input.timeframe` أو نصّ فريم) اعرضه كـ `select` أو `string`
ومرّره إلى `request.security` — جلب فريم/رمز آخر صار مدعوماً (القسم ٣.٤).
ثانياً — انتهِ بسطر عددي صريح:
«حُوِّل N من M مدخلاً. غير محوَّل: X لميزات غير مدعومة، Y لسبب آخر (اذكره).»
ثم أخرج `const meta = {...}` كاملاً وحده — بلا `compute`.
استخدم الأنواع التسعة من القسم ٢ (int / float / bool / color / string /
select / session / time / symbol — والأخير لكل مدخل يختار رمزاً)، ونظّم المدخلات بـ `section` للأقسام و`inline` لجمع
ما يخصّ عنصراً واحداً في صفّ (المفتاح والاسم والوقت واللون معاً).
الحدّ الأقصى ٨٠ مدخلاً؛ إن تجاوز المصدر ذلك، أسقِط الأقل أثراً واذكر ما أسقطت.
المرحلة ٢ — كتابة الحساب
بعد مراجعة meta وتعديل ما تريد، أرسل هذا في نفس المحادثة.
مُعتمَد. الآن اكتب `function compute(candles, inputs)` لهذا الـ meta.
اتبع المرجع المرفق (CUSTOM-INDICATORS.md) بدقّة، والتزم بالتالي:
0. استخدم كل مدخل عرّفناه في meta. أي مدخل لا يؤثّر في المخرَج = خيار ميت
في الواجهة؛ إن تعذّر تنفيذ أحدها فاذكره صراحةً بدل تركه معلّقاً.
1. أخرج ملفاً واحداً فيه `const meta = {...}` و `function compute(candles, inputs)` فقط.
2. استخدم دوال TA المتاحة في المرجع فقط. لا تخترع دوالاً غير موجودة.
إن احتجت دالة غير موجودة (مثل ATR) فاشتقّها من الموجود.
3. انتبه لطول المصفوفات والإزاحة (offset) — هذا أكثر مصدر للأخطاء.
تحقّق أن كل نقطة تُربط بالشمعة الصحيحة عبر TA.pt أو بفهرس صريح.
4. حوّل منطق `var` و `:=` في Pine إلى متغيّرات خارج حلقة for صريحة،
لأن compute تُستدعى مرّة واحدة بكل الشموع لا مرّة لكل شمعة.
5. بدل NaN/na استخدم نقطة بلا value: { time } — تُرسم كفجوة.
6. اضبط overlay: true إن كان المؤشر يُرسم على السعر، و false إن كان له مقياس مستقل.
7. حوّل hline إلى meta.levels، ولا تحدّد ألواناً صريحة للمستويات.
8. للصناديق والخطوط والتسميات والدوائر والتعبئة واللوحات استخدم "shapes"
(القسم ٣.١)، ولإشارات plotshape استخدم "markers" (القسم ٣.٢).
الإحداثيات بالوقت والسعر لا بفهرس الشمعة. لا تحاكِ box.new بالتحديث التدريجي:
احسب بداية الشكل ونهايته أولاً ثم أنشئه مرّة واحدة. كل الأنواع — الأشكال
والتعبئة والعلامات وخلفية bgcolor — تعمل في اللوحة السفلية أيضاً
(overlay: false). ولـ bgcolor() استخدم `{ type:'bgcolor' }` (القسم ٣.٥)،
ولـ input.source استخدم `{ type:'source' }` (القسم ٣.٦).
9. للجلسات والأوقات استخدم TA.inSession و TA.tz — لا تستخدم Intl أو
toLocaleString، فهي غير موجودة. وأضِف مدخل `type:'timezone'` لأي مؤشر
جلسات، وإلا فُسِّرت أوقاته بتوقيت نيويورك مهما كان محور المستخدم.
10. احترم بارامتراً يحدّ عدد الأشكال (مثل maxDays) وقصّ القائمة بـ slice(-n).
الحدّ الأقصى ١٥٠٠ شكل، وخلايا اللوحة تُحتسب منه.
10.١ حوّل table.new/table.cell إلى `{ kind:'panel', position, rows }` داخل
"shapes" (القسم ٣.١) — لوحة مثبّتة بالبكسل لا تنزاح مع التمرير، وwidth/height
في الخليّة نسبة مئوية من الشارت كما في Pine. لا تستبدلها بـ label: التسمية
مرساة بوقت وسعر فتنزاح عن مكانها وتخرج عن الشاشة.
10.٢ `syminfo.ticker` و`timeframe.period` متاحان داخل compute (القسم ٣.٣)،
و`timeframe.pine` يعطي ترميز Pine نفسه ('15'/'240'/'D'). لا تحذف السطور
التي تستعملهما ولا تستبدلها بنصّ ثابت.
11. لا تستخدم fetch أو window أو setTimeout — غير موجودة في العزل. (`console.log`
يعمل للتصحيح: يظهر في لوحة اختبار المحرّر فقط، بلا أثر على الشارت.)
11.١ `request.security` مدعومة (القسم ٣.٤): حوّلها إلى
`request.security(sym, tf)` + حساب بـ`TA` + `request.map`، واجعل الكود
يحتمل مصفوفة فارغة في أول تشغيلة. و`barmerge.lookahead_on` يقابله
`{ lookahead: true }` — بدونه لا يعيد المؤشر الرسم، وهو الافتراض الصحيح.
12. إن كان في المصدر ميزات غير مدعومة (التنبيهات عملياً) فاذكرها صراحةً في
تعليق أعلى الملف بدل تجاهلها بصمت، وحوّل ما تبقّى. ولا تعلن شيئاً غير مدعوم
قبل أن تجده في جدول التحويل (القسم ٥) كذلك — الجداول والرمز والفريم وجلب
فريم/رمز آخر وخلفية bgcolor ومصدر السعر والعلامات في اللوحة السفلية كلّها
مدعومة.
13. الخيارات المعطّلة افتراضياً في المصدر نفّذها فعلياً في الحساب — قيمتها
الافتراضية false تكفي لإخفائها، ولا داعي لحذف الكود.
14. **إن كان المصدر يُرسم في لوحة منفصلة بصفوف** (ICT Quarterly، خرائط الجلسات،
مقاييس أسفل الشارت): اجعل `overlay: false`، واستخدم إحداثيات صفوف ثابتة
(`y = 0,1,2…`) **لا أسعار** (high/low). راجع قسم «الرسم في لوحة فرعية
مستقلّة». إن كان المصدر يجمع بين الرسم على السعر والرسم في لوحة معاً (مفتاح
display-on-chart لكل دورة)، فذلك مستحيل في مؤشر واحد: اختر اللوحة بالصفوف
وأسقِط مفتاح الازدواج، واذكر ذلك في تعليق أعلى الملف.
أخرج الكود فقط، جاهزاً للصق في المحرّر.
إن أردت التحويل في ردّ واحد
للمؤشرات الصغيرة (أقل من ١٠ مدخلات) الفصل ليس ضرورياً — أرسل البرومبتين معاً مسبوقين بـ «نفّذ المرحلتين في ردّ واحد». للمؤشرات الكبيرة لا تفعل؛ هذا بالضبط ما ينتج لوحة إعدادات فقيرة.
إن لم يعمل الكود من أول مرّة: انسخ رسالة الخطأ من لوحة المحرّر وأعدها للذكاء الاصطناعي — أغلب المشاكل تُحلّ في محاولة أو اثنتين، وأكثرها شيوعاً خطأ في الإزاحة.
إن خرجت الإعدادات فقيرة رغم ذلك: اطلب جدول الأمانة صراحةً — «كم مدخلاً في المصدر وكم حوّلت؟». الرقم يكشف الحذف الصامت فوراً.
Custom Indicators Reference — Sovereign Chart
For use with AI: upload this file together with your PineScript code to Claude or ChatGPT and ask for the conversion. The ready-made prompt is at the end of the file.
1. Structure
An indicator is a single JavaScript file containing only two things:
const meta = { /* indicator description */ };
function compute(candles, inputs) { /* the calculation */ }
No tabs, no this._context, no numeric indices for the inputs. That is all.
const meta = {
name: 'RSI', // ≤16 chars — shown in the chart legend
label: 'Relative Strength Index',
color: '#4CAF50', // main plot colour
overlay: false, // true = over the candles | false = lower pane
levels: [70, 30], // horizontal reference lines (optional)
levelAlpha: 0.15, // their opacity (optional)
showLastValue: false, // hide the value label on the axis (optional)
styleDefaults: { lineWidth: 2, lineStyle: 0 }, // 0=solid 1=dotted 2=dashed
inputs: [
{ id: 'period', type: 'int', label: 'Period', def: 14, min: 2, max: 200, control: 'slider' },
{ id: 'mult', type: 'float', label: 'Multiplier', def: 3.0, min: 0.1, max: 10, step: 0.1 },
{ id: 'fast', type: 'int', label: 'Fast', def: 12, min: 1, max: 200, group: 'Fast / Slow' },
],
};
inputs rules:
id must be a valid identifier ([a-zA-Z_][a-zA-Z0-9_]*) — it is used as a key in inputscontrol: 'slider' shows a slider; without it, a number fieldsection starts a titled group · inline puts several parameters on one rowtooltip explanatory text shown on hover- The ceiling is 80 parameters
The nine types:
type | value in inputs | UI | PineScript conversion |
int / float | number | field or slider | input.int / input.float |
bool | true/false | switch | input.bool |
color | '#2962FF' | colour picker | input.color |
string | text | text field | input.string |
select | one of options | dropdown | input.string(options=[...]) |
session | '0930-1100' | two time pickers | input.session |
time | '0930' | time picker | — |
timezone | 'America/New_York' | timezone list | input.timezone |
symbol | 'US30' or '' | the platform's symbol list | input.symbol |
source | 'close' … or another indicator's output | source list | input.source |
source — compute receives an array of values, not a string, and its list includes the outputs of other active indicators, so indicators can be stacked on one another. Details in section 3.6.
{ id: 'src', type: 'source', label: 'Source', def: 'close' }
symbol — do not hand-write a list of pairs in a select. This type opens the platform's own symbol list (with its search, categories and favourites) exactly as input.symbol does in TradingView, so the list stays correct whenever a pair is added. Its value is a symbol string passed straight to request.security, and an empty '' means "follow the chart's symbol" — an option shown at the top of the list, so the user is never stuck on a fixed symbol.
{ id: 'partner', type: 'symbol', label: 'Compared symbol', def: 'US30' }
{ id: 'useAsia', type: 'bool', label: 'Asia', def: true, inline: 'asia', section: 'Sessions' },
{ id: 'asiaName', type: 'string', label: 'Name', def: 'Asia', inline: 'asia' },
{ id: 'asiaSess', type: 'session', label: 'Time', def: '2000-0000', inline: 'asia' },
{ id: 'asiaColor', type: 'color', label: 'Colour',def: '#2962FF', inline: 'asia' },
The timezone — the session-time translator
Three separate time layers, exactly as in TradingView:
| Layer | Who sets it |
| The candle's stamp | The feed (Binance in UTC, MT5 in its server's time +GMT3 — corrected automatically) |
| The indicator's timezone | You: it is what the session string is interpreted in |
| The axis's timezone | The user, from the axis's gear — and it alone is what appears under the chart |
The difference between the last two is the offset the user sees: a session written 1300-2200 in New York time is drawn at 20:00 on an axis set to Baghdad. That is intended, not a bug.
The indicator's timezone is declared in one of two ways:
var meta = {
timezone: 'chart', // a fixed declaration, or…
inputs: [
{ id: 'zone', type: 'timezone', label: 'Timezone', def: 'America/New_York' }
]
};
The input takes precedence over the fixed declaration — the user has the last word. The values: an IANA identifier ('Europe/London'), or 'UTC', or 'chart' (follows the axis's timezone, so what the user types is what they see) or 'exchange' (New York for forex, UTC for crypto).
Compatibility warning: an indicator that does not declare its timezone keeps the old behaviour — New York by default, and no correction of the MT5 feed's time. Adding timezone will shift its sessions away from what its users are accustomed to, so mention it when updating a published indicator.
Inside compute everything follows that timezone automatically:
TA.inSession(c[i].time, S.ny) // in the indicator's timezone
TA.inSession(c[i].time, S.ny, 'Asia/Tokyo') // an explicit timezone overrides it
TA.tz(c[i].time).h // the hour in the indicator's timezone
TA.tzName() // the name of the timezone in force — for a chart label
An explicit argument is final — just like the third argument of Pine's time(). An indicator that declares no meta.timezone follows the chart's axis timezone, but that is its default for a bare call only; it never overrides a timezone written into the call. Were it to override, every Pine-converted indicator's sessions would shift by the gap between the user's axis and the timezone written in the original.
Always-available zones: UTC, New York, Chicago, London, Berlin, Dubai, India, Shanghai, Tokyo, Sydney — plus the indicator's timezone and the axis's timezone. Anything else must be declared in timezone. The shorthands 'NY'/'LDN'/'TOK'/'SYD' are supported.
levels rules: either numbers [70, 30], or objects [{ value: 0, alpha: 0.1 }].
Do not set an explicit color for levels unless you must — the default colour adapts to the light and dark themes automatically, an explicit one does not.
3. The compute function
function compute(candles, inputs) {
// candles: [{ time, open, high, low, close, volume }, ...] in ascending order
// inputs : { period: 14, mult: 3.0, ... } the user's current values
return [ /* plots */ ];
}
The shape of a single plot
{
id: 'main',
type: 'line', // see "series types" below
primary: true, // the main plot — it takes the user's colour and width
data: [{ time: 1700000000, value: 42.5 }, ...]
}
Series types
One value per candle — data: [{ time, value }] (a gap = a point with no value):
type | Form | Pine counterpart |
line | a continuous line | plot() |
histogram | bars (colour per bar, inside the point) | plot(style=histogram/columns) |
area | a line with a gradient fill toward the bottom | plot(style=area) |
baseline | two colours around a reference (baseValue) — green above / red below | plot(style=baseline) |
stepline | a line that jumps with no diagonal — for fixed levels | plot(style=stepline) |
baseline needs a reference: { type: 'baseline', baseValue: 0, data: [...] }.
OHLC candles — data: [{ time, open, high, low, close }] (optional per-candle colour via color/wickColor/borderColor; a gap = a point with time only):
type | Form | Pine counterpart |
candle | custom Japanese candles (Heikin Ashi, higher timeframe…) | plotcandle() |
bar | OHLC bars | plotbar() |
Fill between two bounds (band) — a cloud between two lines, the cleaner replacement for fill(). It works on the main chart and in the lower pane alike:
{ id: 'cloud', type: 'band', color: 'rgba(38,166,154,0.15)',
data: [{ time: t, upper: 105, lower: 95 }, { time: t2 }, ...] } // a point with no upper/lower = a gap
A Bollinger cloud example:
const b = TA.bb(TA.src(candles), 20, 2); // { basis, upper, lower, offset }
return [
{ id: 'basis', type: 'line', primary: true, data: TA.pt(candles, b.basis, b.offset) },
{ id: 'band', type: 'band', color: 'rgba(90,200,250,0.12)',
data: candles.map((c, i) => { const k = i - b.offset;
return k >= 0 ? { time: c.time, upper: b.upper[k], lower: b.lower[k] } : { time: c.time }; }) },
];
The candle's background (bgcolor) — a full-height coloured column behind the candle, the replacement for Pine's bgcolor(). Details and rules in section 3.5:
{ id: 'regime', type: 'bgcolor',
data: [{ time: t, color: 'rgba(38,166,154,0.08)' }, ...] } // a candle with no colour: leave it out
A secondary plot with a fixed style (unaffected by the user's settings):
{
id: 'signal', type: 'line',
style: { color: '#FF9800', lineWidth: 1, lineStyle: 0, title: '' },
data: [...]
}
Colouring each bar individually (for a histogram) — the colour goes inside the data point:
{ id: 'hist', type: 'histogram',
data: [{ time: t, value: v, color: v >= 0 ? '#26a69a' : '#ef5350' }, ...] }
Gaps and diagonal lines: a point with no value = a gap. The line connects points that have values, skipping over gaps — this is how zigzag patterns and highs/lows are drawn.
data.push({ time: t }); // a gap
data.push({ time: t, value: 100 }); // a point
3.1 Shapes — boxes, lines and text
The "one value per candle" model cannot draw a rectangle spanning candle 50 to candle 90, nor write text at a given price. Hence a third output type:
{ id: 'zones', type: 'shapes', shapes: [ /* … */ ] }
Coordinates are (unix time in seconds, price) — the same time that is in the candles.
xloc: 'bar_index' — bar-number coordinates (the counterpart of Pine's xloc.bar_index)
Add xloc: 'bar_index' to the shape and every horizontal coordinate in it becomes a bar number rather than a time:
{ kind: 'line', xloc: 'bar_index', x1: i, x2: i + 20, y1: p1, y2: p2 }
It applies to box, line, label, circle, fill (the t field) and linefill. Numbers outside the range are projected by the timeframe's step (n + 5 = five bars past the last, negatives before the first) — but future times remain outside the time scale as explained in the projection note below. Without xloc, x stays a literal time, so no existing indicator changes.
box — a rectangle
{ kind: 'box',
x1: startTime, y1: topPrice, x2: endTime, y2: bottomPrice,
bgColor: 'rgba(41,98,255,0.3)', // the fill
borderColor: '#2962FF',
borderWidth: 1,
style: 'solid', // 'solid' | 'dotted' | 'dashed'
text: 'Asia', // text in the middle of the box (optional)
textColor: '#2962FF',
font: 'mono' } // 'sans' (default) | 'mono' | 'serif'
line — a line between two points
{ kind: 'line',
x1: t1, y1: p1, x2: t2, y2: p2,
color: '#26a69a',
width: 1,
style: 'dotted',
extend: 'right' } // 'none' | 'left' | 'right' | 'both'
A diagonal line is drawn by making y1 ≠ y2 — this is how trend lines and high-to-low connections are drawn.
Future projection: extend: 'right' extends the line to the pane's edge preserving its slope (for trend lines and forecasts). Define the line by two real points inside the data and then extend it — do not put an endpoint at a future time (past the last candle), because future times are outside the time scale and get clipped to the edge with no slope. To project to a specific future point, a series (candles / a forecast line) must occupy those times so that they enter the axis.
label — text at a point
{ kind: 'label',
x: time, y: price,
text: 'NYAM.H',
textColor: '#089981',
bgColor: 'rgba(0,0,0,0.6)', // a background for the text (optional)
size: 11, // 7 – 28
position: 'above', // 'above' | 'below' | 'middle'
align: 'left', // 'left' | 'center' | 'right'
font: 'mono', // 'sans' (default) | 'mono' | 'serif'
bold: true }
Label styles — style (the counterpart of label.style_*)
Without a style (or with style: 'none') the behaviour above holds: free text at the point. With a label_* style, a card with a triangular pointer aimed at the point is drawn — the familiar Pine label:
{ kind: 'label',
x: time, y: price,
text: 'CHoCH',
style: 'label_right', // the card sits left of the point, the pointer aims right
bgColor: '#2962FF', // the card's colour (blue by default)
textColor: '#ffffff',
borderColor: '#ffffff' } // an optional border
style | Card position | Pine counterpart |
label_right | left of the point | label.style_label_right |
label_left | right of it | label.style_label_left |
label_up | below it | label.style_label_up |
label_down | above it | label.style_label_down |
label_center | exactly on it (no pointer) | label.style_label_center |
label_upper_left / label_upper_right | diagonally above | the same names |
label_lower_left / label_lower_right | diagonally below | the same names |
Inside the card align centres the text by default, and position (above/below) has no effect — the style is what decides the position. An unknown style falls back to 'none' rather than the shape being rejected.
polyline — a free path and polygon (replacement for polyline.new)
box is axis-aligned and fill needs two bounds at the same time — so neither can draw a triangle nor a slanted quadrilateral. This shape is a single path across N points, filled or stroked or both, and it is what "3D" profiles, triangles and solid zigzag paths are built on.
{ kind: 'polyline',
points: [ { x: t1, y: p1 }, { x: t2, y: p2 }, { x: t3, y: p3 } ],
color: '#2962FF', // the border colour ('width: 0' = no border)
width: 1,
style: 'solid', // 'solid' | 'dotted' | 'dashed'
fillColor: 'rgba(41,98,255,0.25)', // optional — without it, a path with no fill
closed: true, // closes the last edge (required for a polygon)
curved: false } // quadratic smoothing (the counterpart of curved=true)
- The limit is 2,000 points per polyline; and a path is a single draw call however long it is, so a 500-point polygon is cheaper than 500 boxes.
- A point whose coordinate is outside the data's range is skipped and does not break the path — breaking it would have opened the polygon and let its fill leak.
xloc: 'bar_index' applies to x in every point.- A polygon with a single valid point is dropped (there is no path from one point).
circle — a round point
{ kind: 'circle',
x: time, y: price,
radius: 4, // 1 – 30 pixels
color: '#26a69a', // the fill
borderColor: '#ffffff', // optional
borderWidth: 1 }
fill — a fill between two bounds (a cloud)
The replacement for PineScript's fill(). One shape carries a series of points, each of which defines the upper and lower bound at a given time.
{ kind: 'fill',
color: 'rgba(90,200,250,0.12)',
points: [
{ t: time1, top: 105, bottom: 95 },
{ t: time2 }, // a point with no top/bottom = a gap that breaks the fill
{ t: time3, top: 108, bottom: 97 },
] }
The gap ends the current segment and starts another — use it during the warm-up period, otherwise the fill stretches across the void. The maximum is 60,000 points per fill.
linefill — a fill between two sloped lines (replacement for linefill.new)
fill needs a series of points aligned with the candles, which suits clouds and bands. For a fill between two arbitrary lines — a channel, a fan, a triangle — use this:
{ kind: 'linefill',
color: 'rgba(41,98,255,0.15)',
extend: 'right', // applies to both lines
line1: { x1: t1, y1: p1, x2: t2, y2: p2 },
line2: { x1: t1, y1: p3, x2: t2, y2: p4 } }
a/b are synonyms for line1/line2.- Each line may carry its own
extend; otherwise it inherits the shape's. - The two lines are not drawn — add two
line shapes if you want visible borders. xloc: 'bar_index' applies to all four coordinates.- A line missing an endpoint drops the whole fill (there is no half polygon).
panel — a pinned stats panel (replacement for table.new)
The only shape here that is tied to neither time nor price: it is positioned in pixels from the chart's edge, so it does not move with scrolling or zooming.
{ kind: 'panel',
position: 'top_right', // top/middle/bottom × left/center/right
bgColor: 'rgba(19,23,34,0.85)',
borderColor: '#2a2e39',
borderWidth: 1,
textColor: '#d1d4dc', // the default text colour for every cell
size: 11, // the default font size (7 – 28)
width: 25, // optional: % of the chart's width for the whole panel
height: 12, // optional: % of its height
rows: [
// a plain text cell, or an object with its properties
['Trend', { text: 'Up', align: 'right', textColor: '#26a69a' }],
[{ text: 'RSI', bold: true }, { text: '62.4', align: 'right' }],
] }
Cell properties: text, textColor, bgColor, align, size, bold, font, and width/height as a percentage of the chart (the counterpart of table.cell(width=, height=)). Without a percentage the column is measured from its widest text. font on the panel applies to all of its cells, and on a cell it takes precedence — and 'mono' is what makes columns of numbers line up.
Panel rules:
- 4 panels per indicator at most, 24 rows × 8 columns, and 64 characters per cell. They count against the 1,500-shape ceiling by their cell count, not as one shape.
- Panels from several indicators in the same position stack automatically and never overlap; their order is stable and does not change from frame to frame. Bottom panels stack upwards.
- They are always drawn above everything, and never affect the price scale.
- Text wider than its cell is clipped at the cell's edge and never creeps onto its neighbour.
Shape constraints:
- They work on the main chart (
overlay: true) and in the lower pane (overlay: false) alike — the coordinates are prices, so a pure-shapes indicator in its own pane sets its y values within its own range. - The maximum is 1,500 shapes. Honour a parameter such as
maxDays rather than drawing the whole history. - Shapes are not interactive and are removed automatically with the indicator.
Drawing in a separate sub-pane (stacked rows) — read this before converting any "pane" indicator
Many indicators (ICT Quarterly, session maps, gauges under the chart) draw in a separate pane as coloured rows rather than on the price. To convert them correctly:
overlay: false — this alone routes the indicator to the lower pane. overlay: true always draws on the candles, whatever else you do.- The
y coordinate = a fixed row number, not a price. Give each cycle/row a number: the first [0,1], the second [1,2]… and the axis adapts automatically to the number of rows. Do not use high/low (price) in a sub-pane — it will stretch the scale (to 0..1860, say) and render it meaningless. box, label, band, bgcolor and markers all work in the sub-pane. Markers there are built as shapes and attached to the primary plot's value at that candle rather than to the candle (there are no candles in the pane), so aboveBar means "above the line". Therefore: a marker at a time where the primary plot has no value is dropped — except atPrice*, which carries its own explicit price.
A decisive architectural constraint: one indicator = one pane. If the source draws some elements on the price and others in a separate pane at the same time (such as a Display on chart switch per cycle), that is impossible in a single indicator here. Pick one of the two modes (usually the row-based pane), drop the dual switch rather than leaving it as a dead option, and mention it in a comment.
// a row per enabled cycle — no price
let rows = 0; const R = {};
if (inputs.showDaily) R.daily = ++rows;
if (inputs.showNinety) R.ninety = ++rows;
if (inputs.showMicro) R.micro = ++rows;
// the quarter's box in its row, coloured by quarter:
shapes.push({ kind: 'box', x1: t1, x2: t2, y1: R.daily, y2: R.daily - 1,
bgColor: 'rgba(139,188,252,0.35)', borderColor: '#8bbcfc' });
shapes.push({ kind: 'label', x: t1, y: R.daily - 0.5, text: 'Q3',
textColor: '#8bbcfc', size: 9, position: 'middle' });
3.2 Markers — the replacement for plotshape
Arrows and triangles above and below the candles, for entry and exit signals:
{ id: 'signals', type: 'markers', markers: [
{ time: t,
position: 'belowBar', // 'aboveBar' | 'belowBar' | 'inBar'
shape: 'arrowUp', // 'arrowUp' | 'arrowDown' | 'circle' | 'square'
color: '#26a69a',
text: 'BUY', // optional, ≤24 chars
size: 1 }, // 0.1 – 5
]}
To place one at a specific price rather than tying it to the candle, use atPriceTop, atPriceBottom or atPriceMiddle — these require a price field:
{ time: t, position: 'atPriceMiddle', shape: 'circle', color: '#fff', price: 65400 }
The maximum is 2,000 markers, and there is no need to sort them by time — that happens automatically. They work on the main chart and in the lower pane alike. The difference is that the main chart places them above/below the candle (the library's own feature), while the lower pane places them above/below the primary plot's value — see rule 3 in the previous section.
The PineScript shape conversion table:
| PineScript | here |
shape.triangleup / shape.arrowup | shape: 'arrowUp' |
shape.triangledown / shape.arrowdown | shape: 'arrowDown' |
shape.circle | shape: 'circle' |
shape.square / shape.diamond / shape.flag | shape: 'square' |
location.abovebar / location.belowbar | position: 'aboveBar' / 'belowBar' |
location.absolute | position: 'atPriceMiddle' + price |
Shapes and series can be combined in the same indicator:
return [
{ id: 'ma', type: 'line', primary: true, data: TA.pt(candles, ma, 19) },
{ id: 'zones', type: 'shapes', shapes: boxes },
];
3.3 The symbol and the timeframe — syminfo and timeframe
Two globals inside compute (not passed as arguments, exactly as in PineScript):
syminfo.ticker // 'BTCUSDT'
syminfo.tickerid // the same
timeframe.period // 'M15' — in the platform's notation
timeframe.pine // '15' — its Pine counterpart, so code ports literally
timeframe.in_seconds // 900
timeframe.multiplier // 15
timeframe.isintraday // true
timeframe.isseconds / isminutes / isdaily / isweekly / ismonthly
The timeframe.pine mapping: M15 → '15', H4 → '240', D1 → 'D', W1 → 'W', MN1 → 'M' — so a timeframe.pine == '240' condition ported from Pine works as is.
They update automatically when the symbol or the timeframe changes, and the indicator is recalculated then. They may come back empty on the validation path before a symbol has loaded (as in the editor's test on synthetic candles) — do not assume a value: syminfo.ticker || '—'.
3.4 Data from another symbol or timeframe — request.security
const htf = request.security('', 'H4'); // same symbol, four-hour timeframe
const btc = request.security('BTCUSDT', 'D1'); // another symbol
const h4 = request.security('H4'); // shorthand: a single argument = the timeframe
It returns an array of candles [{ time, open, high, low, close, volume }] in ascending order — then calculate on it with the TA library like any candles, and project the result onto the current timeframe with request.map:
function compute(candles, inputs) {
const d1 = request.security('', 'D1');
if (!d1.length) return [{ id: 'ema', type: 'line', data: TA.plot(candles, candles.map(() => NaN)) }];
const ema = TA.ema(TA.src(d1), 20); // 19 shorter than d1
const full = TA.align(d1, ema, 19); // ← the length of d1
const onChart = request.map(candles, d1, full); // ← the length of the current candles
return [{ id: 'ema', type: 'line', data: TA.plot(candles, onChart) }];
}
| Function | Description |
request.security(sym, tf) | the requested symbol's/timeframe's candles (empty if they have not arrived yet) |
request.ready(sym, tf) | have they arrived? to tell that apart from "no data" |
request.map(candles, src, vals, opts) | projects the source's series onto the current candles |
request.step(src) | the source candle's duration in seconds |
Notation: an empty symbol = the current symbol, and an empty timeframe = the chart's timeframe. The timeframe is accepted in the platform's notation ('H4', 'D1', 'MN1') or in Pine's ('240', 'D', '3M'), and 'BINANCE:BTCUSDT' is read as a valid symbol.
Available symbols — nothing else is ever fetched (indicator code is shared, so it does not get to decide what is requested from the network):
| Market | Symbols |
| Crypto spot | BTCUSDT ETHUSDT SOLUSDT XRPUSDT BNBUSDT ADAUSDT DOGEUSDT SHIBUSDT DOTUSDT LTCUSDT LINKUSDT |
| Crypto futures | the same with an f suffix — BTCUSDTf … |
| Indices and commodities | US500 US30 US100 WTI BRENT XAUUSD XAGUSD |
| Forex | EURUSD GBPUSD USDJPY USDCAD USDCHF EURJPY GBPJPY EURAUD EURCAD EURNZD GBPAUD GBPCAD GBPNZD |
Our mapping for the US futures: ES → US500, YM → US30, NQ → US100 — these are what SMT indicators ported from TradingView use.
Comparing two symbols on the same timeframe (SMT and the like)
Here the second rule flips: request.map without lookahead returns the last closed candle, and on the same timeframe that is the previous candle — so the comparison shifts by a whole candle. Comparing two synchronous symbols needs { lookahead: true }, and that is not lookahead bias: both candles close at the same instant.
const meta = {
name: 'SMT', label: 'SMT Divergence', overlay: true,
inputs: [
// the `symbol` type opens the platform's symbol list — do not hand-write a list of pairs
{ id: 'partner', type: 'symbol', label: 'Compared symbol', def: 'US30' },
{ id: 'left', type: 'int', label: 'Left', def: 3, min: 1, max: 20 },
{ id: 'right', type: 'int', label: 'Right', def: 3, min: 1, max: 20 },
],
};
function compute(candles, inputs) {
const other = request.security(inputs.partner, ''); // the same timeframe
const shapes = [];
if (other.length) { // ← the first run returns []
const oHigh = request.map(candles, other, other.map(c => c.high), { lookahead: true });
const ph = TA.pivothigh(TA.src(candles, 'high'), inputs.left, inputs.right);
let prev = -1;
for (let i = 0; i < candles.length; i++) {
if (!isFinite(ph[i])) continue;
if (prev >= 0 && isFinite(oHigh[i]) && isFinite(oHigh[prev])) {
const mine = ph[i] > ph[prev], theirs = oHigh[i] > oHigh[prev];
// a higher high here and a lower high there (or the reverse) = divergence
if (mine !== theirs) shapes.push({
kind: 'label', x: candles[i].time, y: ph[i], text: 'SMT',
style: 'label_down', bgColor: mine ? '#ef5350' : '#26a69a',
});
}
prev = i;
}
}
return [{ id: 'smt', type: 'shapes', shapes }];
}
### The two rules that are never broken 1. The first run returns empty — always. The sandbox has no network and no waiting: request.security registers the request and returns an empty array, the host fetches it in the background and then recalculates the indicator, at which point it arrives. Write code that tolerates [] (as in the example above), otherwise it flashes an error on every activation and symbol switch. 2. request.map without lookahead does not repaint. The default is that the current candle's value is the value of the source's last closed candle — that is, what was actually known at the time. { lookahead: true } reveals the in-progress candle, making history look more accurate than was possible: valid for display, not for an entry signal.
Limits: four series per indicator (the fifth always returns empty), a thousand candles per series, refreshed every minute. Series from another market are shifted automatically to the chart's time convention (the MT5 feed leads UTC by three hours) so they land on their correct candles. And in a backtest session the series is truncated at the replay's clock, so the indicator never sees a future that was not known.
In the editor the run button waits once for the data to arrive so it can show you a complete result; if it does not arrive, a line in the debug panel names what was missing. And the publishing gate judges the code on synthetic candles with old dates — so the projection may come out entirely NaN there; return a series even if it has no values so that it passes.
3.5 Colouring the candle's background — the replacement for bgcolor
A full-height coloured column behind the candle. It is what "trend" and "market regime" indicators need: green when the regime is bullish, red when it is bearish, no colour when neutral.
{ id: 'regime', type: 'bgcolor', data: [
{ time: t, color: 'rgba(38,166,154,0.08)' },
// a candle that is not coloured: do not include it at all — there is no colourless point
] }
A complete example inside compute:
const ema = TA.align(candles, TA.ema(TA.src(candles, 'close'), inputs.len), inputs.len - 1);
const bg = [];
for (let i = 0; i < candles.length; i++) {
if (!isFinite(ema[i])) continue; // no colour during the warm-up
bg.push({ time: candles[i].time,
color: candles[i].close > ema[i] ? 'rgba(38,166,154,0.10)'
: 'rgba(239,83,80,0.10)' });
}
return [
{ id: 'ema', type: 'line', primary: true, data: TA.pt(candles, ema) },
{ id: 'bg', type: 'bgcolor', data: bg },
];
Background rules:
- It is drawn beneath everything — beneath the indicator's own shapes and beneath the candles.
- It never affects the price scale: it has no price at all, it spans from the top of the pane to the bottom.
- Adjacent candles of the same colour are merged automatically into one column, so do not worry about colouring the whole history: 3,000 candles in two colours become dozens of columns, not 3,000 shapes. (And they do not count against the 1,500-shape ceiling — they are not shapes.)
- Use very transparent colours (0.05–0.15). A solid colour swallows the candles.
- It works on the main chart and in the lower pane alike.
An input with which the user chooses what the indicator feeds on. The difference from select: the value reaches compute as a ready array of values the length of the candles, exactly as in PineScript — not a string you translate yourself.
var meta = {
name: 'MYRSI', label: 'RSI with a source', overlay: false, color: '#5ac8fa',
inputs: [
{ id: 'len', type: 'int', label: 'Length', def: 14, min: 2, max: 200 },
{ id: 'src', type: 'source', label: 'Source', def: 'close' },
],
};
function compute(candles, inputs) {
// inputs.src is an array of numbers the length of candles — not 'close' nor 'hlc3'
const r = TA.rsi(inputs.src, inputs.len);
return [{ id: 'main', type: 'line', primary: true,
data: TA.pt(candles, TA.align(candles, r, inputs.len)) }];
}
The list the user sees:
| Group | Options |
| The candle's prices | close open high low hl2 hlc3 ohlc4 hlcc4 |
| The active indicators' outputs | every plot of every other indicator currently enabled on the chart |
The second group is indicator stacking: an RSI on a moving average, a moving average on CVD, Bollinger on your own custom indicator's output. Write nothing for it — declaring one source input is enough, and the rest is the user's choice from the settings panel.
Source rules:
TA.src(candles, 'hl2') still exists for anyone who wants a fixed source in the code with no input. The input is what makes it the user's choice.- Write code that tolerates
NaN. Another indicator's output starts with a warm-up period, and the feeding indicator may be switched off so the whole series arrives as NaN. Do not throw — return a series even if it has no values. - One draw cycle of lag. The source is the last thing actually drawn for the feeding indicator, not a recalculation of it. Its effect is confined to the last candle during a live tick, and the two cycles converge immediately. (This is what prevents circular recursion: A reads B reads A.)
- Switching the symbol or the timeframe is safe: alignment is by time, not by index, so the old market's data does not match the new candles' times and comes out as a gap rather than a false value.
- The built-in indicators (SMA, RSI and so on) keep their old string source — indicator stacking is a custom-indicator feature.
4. The TA library
Every function is available globally with no import.
Conversion
| Function | Description |
TA.src(candles, 'close') | extracts an array of prices (open/high/low/close/volume) |
TA.pt(candles, values, offset) | important — binds an array of values to the candles' times |
TA.hl2(c) TA.hlc3(c) TA.ohlc4(c) | price averages |
TA.pt and offset: windowed functions return an array shorter than the candles. offset is the index of the candle matching the first value.
const closes = TA.src(candles);
const ma = TA.sma(closes, 20); // length = candles.length - 19
TA.pt(candles, ma, 19); // ← 19 = 20 - 1
The offset rule: sma/ema/stdev/highest/lowest ← n - 1 · rma/rsi ← n · tr ← 1
Averages and measures
| Function | Note |
TA.sma(values, n) | length: len - n + 1 |
TA.ema(values, n) | length: len - n + 1 |
TA.rma(values, n) | Wilder's average — length: len - n + 1 |
TA.stdev(values, n) | standard deviation (biased) |
TA.highest(values, n) / TA.lowest(values, n) | the high/low over n |
TA.change(values) | the difference from the previous — same length, first element 0 |
TA.tr(candles) | true range — length: len - 1 (the ta.tr equivalent) |
TA.tr(candles, true) | full length, bar 0 = high - low (the ta.tr(true) equivalent) |
TA.rsi(values, n) | ready-made — length: len - n |
The extended library
Two conventions, no third:
- Windowed functions return a shorter array with a known offset (like
sma) — use them with TA.pt(candles, out, offset) or TA.align. - Signal helpers (
crossover, rising, barssince, pivot*, valuewhen) work on arrays the same length as the candles — lift any short array to that with TA.align.
Averages:
| Function | Offset |
TA.wma(v, n) | n - 1 — linearly weighted average |
TA.hma(v, n) | (n-1) + (⌊√n⌋-1) — Hull |
TA.vwma(candles, n) | n - 1 — volume weighted |
TA.swma(v) | 3 — weights [1,2,2,1]/6 |
TA.linreg(v, n) | n - 1 — linear regression value |
Measures and ranges:
| Function | Offset / note |
TA.atr(candles, n) | n - 1 — matches ta.atr: TA.rma(TA.tr(c, true), n) |
TA.sum(v, n) | n - 1 — rolling sum |
TA.cum(v) | full length — cumulative sum |
TA.roc(v, n) / TA.mom(v, n) | n — relative / absolute change |
TA.dev(v, n) | n - 1 — mean absolute deviation |
TA.variance(v, n) | n - 1 |
TA.percentrank(v, n) | n |
TA.correlation(a, b, n) | n - 1 — a,b of one length |
Oscillators:
| Function | Offset |
TA.cci(candles, n) | n - 1 |
TA.wpr(candles, n) | n - 1 — Williams %R |
TA.stoch(candles, n) | n - 1 — raw %K (smooth it with sma for %D) |
TA.cmo(v, n) | n — Chande Momentum |
TA.mfi(candles, n) | n — Money Flow Index |
Bundles — they return an object with one offset shared by all of its arrays:
const m = TA.macd(close, 12, 26, 9); // { macd, signal, hist, offset } — offset is slow-1
TA.pt(candles, m.hist, m.offset); // plot it with the shared offset
const band = TA.bb(close, 20, 2); // { basis, upper, lower, offset }
TA.dc(candles, 20); // Donchian { upper, lower, basis, offset }
TA.kc(candles, 20, 2); // Keltner { basis, upper, lower, offset }
TA.vwap(candles); // full length — resets every NY day
The shared offset is that of the earliest array in the bundle; arrays that start later carry NaN at their front — exactly where Pine has na. In TA.macd every array has offset slow - 1, and signal/hist stay NaN for a further sig - 1 bars. Do not trim that front yourself: TA.pt and TA.plot turn NaN into a gap.
Signal helpers (full length):
| Function | Description |
TA.align(candles, short, offset) | a short array ← the length of the candles (NaN during warm-up) |
TA.plot(candles, full) | a full array ← plot points (a gap at NaN) |
TA.crossover(a, b) / TA.crossunder(a, b) / TA.cross(a, b) | crossings ← a boolean array |
TA.rising(v, n) / TA.falling(v, n) | rising/falling for n consecutive candles |
TA.barssince(cond) | the number of candles since the last true |
TA.valuewhen(cond, src, occ) | src's value at the last occurrence (occ = the occurrence number) |
TA.pivothigh(v, left, right) / TA.pivotlow(...) | pivot highs/lows — a value at the pivot, NaN elsewhere |
TA.nz(x, r) / TA.na(x) | the replacement for na: substitute / test |
The recommended pattern with crossings: lift both averages to full length, then cross them:
const f = TA.align(candles, TA.ema(close, 12), 11);
const s = TA.align(candles, TA.ema(close, 26), 25);
const buy = TA.crossover(f, s); // a boolean array the length of the candles
Time and sessions
Intl does not exist in the execution environment, so do not try to use toLocaleString or Intl.DateTimeFormat — use these two instead. US daylight saving is accounted for in both.
| Function | Description |
TA.tz(unixSeconds, zone) | decomposes a time for a timezone |
TA.inSession(unixSeconds, 'HHMM-HHMM', zone) | is the time inside a session? the full Pine syntax |
The session string accepts Pine's syntax verbatim:
| Form | Meaning |
'0930-1600' | one range — the start is inclusive, the end is exclusive |
'2000-0400' | crosses midnight |
'0000-0000' | the whole day |
'1000-1100,1400-1500' | two or more comma-separated ranges |
'0930-1600:23456' | day suffix — 1 = Sunday … 7 = Saturday (here Mon–Fri) |
A range crossing midnight belongs to the day it starts on, as in Pine: '2000-0400:23456' includes Saturday's small hours (Friday's tail) and excludes Monday's (Sunday's tail, which is disallowed). npm run test:session guards this.
zone is either an IANA identifier ('Europe/London'), a shorthand ('NY'/'LDN'/'TOK'/'SYD'), an offset in hours (3 = GMT+3), or left empty so that the indicator's timezone is used — the one declared in meta.timezone or in a type:'timezone' input (see "the session-time translator" above). An indicator that has not declared its timezone keeps New York as its default, for compatibility.
const t = TA.tz(candle.time); // { y, mo, d, h, mi, dow, key }
t.h // the hour in the indicator's timezone
t.dow // 0 = Sunday
t.key // 20240724 — the day key, for grouping sessions
TA.inSession(candle.time, '0930-1100') // the New York morning session
TA.inSession(candle.time, '2000-0000') // the Asian session (crosses midnight)
Note: on daylight-saving transition days some hours do not exist at all (02:00 jumps to 03:00 in March). A session starting at 02:00 will start at 03:00 that day — that is correct, not a bug.
Math and JSON are fully available. Anything else (fetch, window, setTimeout, Intl, Date with local timezones) does not exist.
console.log works for debugging. Its messages (and console.warn / console.error) appear in the test panel inside the editor after pressing "Test" — there alone, with no effect whatsoever on the chart, and they appear even if the code throws (the messages before the error). This is our answer to PineScript's lack of debugging tools: print any value during the calculation and inspect it. The limit is 200 messages.
5. The PineScript conversion table
| PineScript | Sovereign JS |
input.int(14, "Period") | a type:'int' input, then inputs.period |
input.bool / input.color / input.session | type: 'bool' / 'color' / 'session' |
input.string(options=[…]) | type: 'select' with options |
group= / inline= in Pine | section: / inline: |
close / high / hlc3 | TA.src(candles) / TA.src(candles,'high') / TA.hlc3(candles) |
ta.sma(close, n) | TA.sma(TA.src(candles), n) |
ta.rma(x, n) | TA.rma(x, n) |
ta.atr(n) | TA.atr(candles, n) |
ta.highest(high, n) | TA.highest(TA.src(candles,'high'), n) |
ta.change(x) | TA.change(x) |
ta.macd / ta.bb / ta.cci / ta.mfi / ta.wpr | TA.macd / TA.bb / TA.cci / TA.mfi / TA.wpr |
ta.wma / ta.hma / ta.vwma / ta.vwap | TA.wma / TA.hma / TA.vwma / TA.vwap |
ta.crossover(a, b) | TA.crossover(a, b) (two arrays the length of the candles — TA.align first) |
ta.barssince / ta.valuewhen / ta.pivothigh | TA.barssince / TA.valuewhen / TA.pivothigh |
x[1] (a previous value) | arr[i - 1] |
var float s = na + s := … | an ordinary variable in a for loop |
time("", "0930-1100", "America/New_York") | TA.inSession(candle.time, '0930-1100', 'America/New_York') |
input.timezone(…) | a type:'timezone' input (or meta.timezone) |
input.symbol(…) | a type:'symbol' input — it opens the platform's symbol list, and empty = the chart's symbol |
hour / dayofweek (in a timezone) | TA.tz(candle.time).h / .dow |
syminfo.timezone | 'exchange' as the timezone value |
plot(x) | a { type: 'line', data: … } plot |
plot(x, style=histogram) | { type: 'histogram', data: … } |
hline(70) | meta.levels: [70] |
box.new(l, t, r, b) | { kind: 'box', x1, y1, x2, y2 } |
line.new(x1, y1, x2, y2) | { kind: 'line', x1, y1, x2, y2 } |
label.new(x, y, txt) | { kind: 'label', x, y, text } |
label.style_label_right / _left / _up … | style: 'label_right' / 'label_left' / 'label_up' … |
font.family_default / font.family_monospace | font: 'sans' / 'mono' (and also 'serif') |
linefill.new(l1, l2, color) | { kind: 'linefill', line1, line2, color } |
xloc.bar_index / xloc.bar_time | xloc: 'bar_index' on the shape / the default (time) |
request.security(sym, tf, expr) | request.security(sym, tf) then calculate with TA and project with request.map (section 3.4) |
barmerge.lookahead_on | request.map(…, { lookahead: true }) |
polyline.new(points, curved, closed, fill_color) | { kind: 'polyline', points, curved, closed, fillColor } |
chart.point.from_index(i, price) | xloc: 'bar_index' with x: i |
chart.point.from_time(t, price) | the default — x: t |
.set_right() / .set_x2() and the like | compute the final value directly — there is no incremental updating |
.delete() / the max_boxes_count / max_polylines_count / max_lines_count limits | do not create the shape at all — trim the list before returning (the ceiling is 1,500 shapes) |
line.all / box.all / polyline.all | no counterpart and none needed: shapes are built from scratch on every calculation |
barstate.isfirst / barstate.islast | i === 0 / i === candles.length - 1 inside your loop |
barstate.isconfirmed | i < candles.length - 1 (only the last one is unconfirmed) |
var / varip | an ordinary variable before the for loop |
plotshape(cond, style=shape.triangleup) | { type: 'markers', markers: [...] } (section 3.2) |
plot(x, style=area/stepline/baseline) | { type: 'area' / 'stepline' / 'baseline' } |
plotcandle(o,h,l,c) / plotbar(...) | { type: 'candle' / 'bar', data: [{time,open,high,low,close}] } |
fill(p1, p2, color) | { type: 'band', data: [{time, upper, lower}] } (works in the lower pane) — or { kind: 'fill' } in shapes |
plot(x, style=plot.style_circles) | { kind: 'circle' } or markers with shape:'circle' |
table.new(pos) + table.cell(...) | { kind: 'panel', position, rows } |
table.cell(width=, height=) | width / height on the cell (% of the chart) |
syminfo.ticker / syminfo.tickerid | syminfo.ticker |
timeframe.period | timeframe.period (the platform's notation) or timeframe.pine |
timeframe.isintraday / .isdaily / .multiplier | the same names |
bgcolor(color) | { type: 'bgcolor', data: [{time, color}] } (section 3.5) |
input.source(close) | { id, type: 'source', def: 'close' } — it reaches compute as an array (section 3.6) |
input.source from another indicator | supported: the user picks any active indicator's output from the same list |
alert() / alertcondition() | not supported |
Fundamental differences
- There is no per-candle execution. PineScript runs once per candle and has automatic memory; here
compute is called once with all the candles. Write an explicit for loop.
- State is manual. The replacement for Pine's
var is a variable outside the loop:
// Pine: var float trend = 1.0
// trend := close > x ? 1 : -1
let trend = 1;
for (let i = 0; i < n; i++) {
trend = closes[i] > x[i] ? 1 : -1;
}
na becomes a gap — do not return NaN, return { time } with no value.
- Shapes are built once, final. In Pine you create a box with
box.new and then update it with set_right() on every candle. Here, pass over the candles first to determine the shape's start and end, then create it once with its final values.
// Pine: if newSession: box.new(...) then box.set_right(time) on every candle
// here: collect the session's range first
const runs = [];
let cur = null;
for (const c of candles) {
if (TA.inSession(c.time, '0930-1100')) {
if (!cur) { cur = { t1: c.time, t2: c.time, hi: c.high, lo: c.low }; runs.push(cur); }
else { cur.t2 = c.time; cur.hi = Math.max(cur.hi, c.high); cur.lo = Math.min(cur.lo, c.low); }
} else cur = null;
}
// then create the boxes in one go
const boxes = runs.slice(-maxDays).map(r =>
({ kind: 'box', x1: r.t1, y1: r.hi, x2: r.t2, y2: r.lo, bgColor: '...' }));
- The replacement for
max_boxes_count and .delete() — do not manage the shapes' lifecycle. Trim the list with .slice(-n) before returning, and stay within the 1,500-shape limit.
6. Limits
| Limit | Value | On exceeding |
| Execution time | 2 seconds | "The code timed out" |
| Memory | 64MB | "Memory limit exceeded" |
| Number of points | 200,000 | "Maximum number of points exceeded" |
| Number of shapes | 1,500 | the excess is ignored silently |
| Number of markers | 2,000 | the excess is ignored silently |
| Points in one fill | 60,000 | truncated |
Number of panels (panel) | 4 per indicator | the excess is ignored |
| Panel rows/columns | 24 × 8 | truncated |
| Panel cell text | 64 characters | truncated |
| Number of plots | 8 | the excess is ignored |
| Code size | 120KB | saving is refused |
Panel cells count against the 1,500-shape ceiling individually, not as one shape — a 10×3 panel consumes 30 of it.
The code runs inside a QuickJS sandbox: no access to the network, the page or storage. Invalid values (colours, widths, texts) are sanitised automatically rather than the indicator being rejected.
7. Complete example — Supertrend
const meta = {
name: 'Supertrend',
label: 'Supertrend (ATR Trailing Stop)',
color: '#26a69a',
overlay: true,
inputs: [
{ id: 'period', type: 'int', label: 'ATR period', def: 10, min: 1, max: 100, control: 'slider' },
{ id: 'mult', type: 'float', label: 'Multiplier', def: 3.0, min: 0.5, max: 10, step: 0.1 },
],
};
function compute(candles, inputs) {
const n = inputs.period, mult = inputs.mult;
const hl2 = TA.hl2(candles);
const atr = TA.atr(candles, n); // offset n-1 — matches ta.atr
const up = [], dn = [];
let prevUp = null, prevDn = null, trend = 1;
for (let i = 0; i < candles.length; i++) {
const t = candles[i].time;
const k = i - (n - 1); // the ATR index matching candle i
if (k < 0 || i === 0) { up.push({ time: t }); dn.push({ time: t }); continue; } // warm-up (and i=0, since the line below reads the previous candle)
let u = hl2[i] - mult * atr[k];
let d = hl2[i] + mult * atr[k];
// the trailing constraints — the replacement for up := close[1] > up1 ? max(up, up1) : up
const pc = candles[i - 1].close;
if (prevUp !== null && pc > prevUp) u = Math.max(u, prevUp);
if (prevDn !== null && pc < prevDn) d = Math.min(d, prevDn);
if (prevUp !== null) {
if (trend === -1 && candles[i].close > prevDn) trend = 1;
else if (trend === 1 && candles[i].close < prevUp) trend = -1;
}
// a gap on the inactive side ← the line breaks when the trend reverses
if (trend === 1) { up.push({ time: t, value: u }); dn.push({ time: t }); }
else { dn.push({ time: t, value: d }); up.push({ time: t }); }
prevUp = u; prevDn = d;
}
return [
{ id: 'up', type: 'line', primary: true, data: up },
{ id: 'dn', type: 'line', style: { color: '#ef5350', lineWidth: 2, title: '' }, data: dn },
];
}
A recommended pattern: note that the loop runs over the candle index rather than the computed array's index, and converts with k = i - offset. This is less prone to offset errors than looping over the short array, and it makes binding each point to its candle obvious.
7.1 A shapes example — session boxes
It demonstrates the full pattern: detecting the session by time, grouping the candles, trimming the list, then building the boxes, lines and labels in one go.
const meta = {
name: 'Sessions', label: 'Killzone Sessions', color: '#2196F3', overlay: true,
inputs: [
// the SESSIONS times below are interpreted in it — without this input the default is New York
{ id: 'zone', type: 'timezone', label: 'Timezone', def: 'chart' },
{ id: 'maxDays', type: 'int', label: 'Session limit', def: 3, min: 1, max: 20, control: 'slider' },
],
};
const SESSIONS = [
{ session: '2000-0000', text: 'Asia', hi: 'AS.H', lo: 'AS.L', rgb: [41, 98, 255] },
{ session: '0200-0500', text: 'London', hi: 'LO.H', lo: 'LO.L', rgb: [255, 82, 82] },
{ session: '0930-1100', text: 'NY AM', hi: 'NYAM.H', lo: 'NYAM.L', rgb: [8, 153, 129] },
];
function compute(candles, inputs) {
if (!candles.length) return [{ id: 'kz', type: 'shapes', shapes: [] }];
const lastTime = candles[candles.length - 1].time;
const shapes = [];
for (const S of SESSIONS) {
const rgb = S.rgb.join(',');
const boxColor = `rgba(${rgb},0.3)`, lineColor = `rgba(${rgb},1)`;
// 1) group the consecutive candles inside the session
const runs = [];
let cur = null;
for (const c of candles) {
if (TA.inSession(c.time, S.session)) {
if (!cur) { cur = { t1: c.time, t2: c.time, hi: c.high, lo: c.low }; runs.push(cur); }
else { cur.t2 = c.time; cur.hi = Math.max(cur.hi, c.high); cur.lo = Math.min(cur.lo, c.low); }
} else cur = null;
}
// 2) only the last maxDays sessions — the replacement for max_boxes_count and .delete()
for (const K of runs.slice(-inputs.maxDays)) {
shapes.push({ kind: 'box', x1: K.t1, y1: K.hi, x2: K.t2, y2: K.lo,
bgColor: boxColor, borderColor: boxColor,
text: S.text, textColor: lineColor });
// 3) the high/low lines run until the break
let hiEnd = lastTime, loEnd = lastTime, hiHit = false, loHit = false;
for (const c of candles) {
if (c.time <= K.t2) continue;
if (!hiHit && c.high > K.hi) { hiEnd = c.time; hiHit = true; }
if (!loHit && c.low < K.lo) { loEnd = c.time; loHit = true; }
if (hiHit && loHit) break;
}
shapes.push({ kind: 'line', x1: K.t1, y1: K.hi, x2: hiEnd, y2: K.hi, color: lineColor });
shapes.push({ kind: 'line', x1: K.t1, y1: K.lo, x2: loEnd, y2: K.lo, color: lineColor });
shapes.push({ kind: 'label', x: K.t1, y: K.hi, text: S.hi, textColor: lineColor, position: 'above', size: 10 });
shapes.push({ kind: 'label', x: K.t1, y: K.lo, text: S.lo, textColor: lineColor, position: 'below', size: 10 });
}
}
return [{ id: 'kz', type: 'shapes', shapes: shapes }];
}
8. The ready-made prompt — in two stages
Serious indicators exceed a hundred inputs. When the model is asked to produce the settings and the algorithm in a single reply, the two compete for its attention and for the output's length — so it favours the algorithm and cuts the settings short. Separating them ends that competition, and lets you review the settings list before the calculation is written at all.
Upload this file + the PineScript code, and copy what is between the two rules.
Read the attached PineScript indicator and extract only its settings. Do not write any
calculation code yet.
Two things are required:
First — an honesty table listing every `input` in the source:
| The Pine input | Type | Our counterpart | Note |
The rule: show every input that **changes what is actually drawn** — even one disabled
by default (input.bool(false)); show it as a bool with def: false.
Do not show inputs tied to unsupported features (in practice only alerts); instead write
the reason it is impossible in the note column. As for `bgcolor` and `input.source`
inputs, do show them: both are supported (sections 3.5 and 3.6), and `input.source`
becomes `type:'source'`. As for table inputs (`table`) — its position, colours and font
size — show them all: the table is supported via `kind:'panel'`. Likewise the
symbol/timeframe display inputs: both are available. And a higher-timeframe input
(`input.timeframe` or a timeframe string) — show it as a `select` or `string` and pass it
to `request.security`; fetching another timeframe/symbol is now supported (section 3.4).
Second — end with an explicit numeric line:
"Converted N of M inputs. Not converted: X for unsupported features, Y for another reason
(state it)."
Then output the complete `const meta = {...}` alone — with no `compute`.
Use the nine types from section 2 (int / float / bool / color / string /
select / session / time / symbol — the last for every input that picks a symbol), and
organise the inputs with `section` for the sections and `inline` to gather what belongs to
one element on a single row (the toggle, the name, the time and the colour together).
The ceiling is 80 inputs; if the source exceeds it, drop the least consequential ones and
say what you dropped.
Stage 2 — writing the calculation
After reviewing meta and adjusting what you want, send this in the same conversation.
Approved. Now write `function compute(candles, inputs)` for this meta.
Follow the attached reference (CUSTOM-INDICATORS.en.md) closely, and abide by the
following:
0. Use every input we declared in meta. Any input that does not affect the output is a
dead option in the UI; if one of them cannot be implemented, say so explicitly rather
than leaving it dangling.
1. Output a single file containing only `const meta = {...}` and
`function compute(candles, inputs)`.
2. Use only the TA functions available in the reference. Do not invent functions that do
not exist. If you need a function that is missing (such as ATR), derive it from what is
there.
3. Mind the arrays' lengths and the offset — this is the biggest source of errors.
Verify that every point is bound to the correct candle via TA.pt or an explicit index.
4. Convert Pine's `var` and `:=` logic into variables outside an explicit for loop,
because compute is called once with all the candles rather than once per candle.
5. Instead of NaN/na use a point with no value: { time } — it is drawn as a gap.
6. Set overlay: true if the indicator draws on the price, and false if it has an
independent scale.
7. Convert hline into meta.levels, and do not set explicit colours for levels.
8. For boxes, lines, labels, circles, fills and panels use "shapes"
(section 3.1), and for plotshape signals use "markers" (section 3.2).
Coordinates are time and price, not the candle's index. Do not emulate box.new with
incremental updating: compute the shape's start and end first, then create it once.
Every type — shapes, fills, markers and the bgcolor background — works in the lower
pane too (overlay: false). For bgcolor() use `{ type:'bgcolor' }` (section 3.5),
and for input.source use `{ type:'source' }` (section 3.6).
9. For sessions and times use TA.inSession and TA.tz — do not use Intl or
toLocaleString, they do not exist. And add a `type:'timezone'` input to any session
indicator, otherwise its times are interpreted in New York time whatever the user's
axis is.
10. Honour a parameter that caps the number of shapes (such as maxDays) and trim the list
with slice(-n). The maximum is 1,500 shapes, and panel cells count against it.
10.1 Convert table.new/table.cell into `{ kind:'panel', position, rows }` inside
"shapes" (section 3.1) — a pixel-pinned panel that does not drift with scrolling, and
width/height on the cell are a percentage of the chart as in Pine. Do not substitute a
label for it: a label is anchored to a time and a price, so it drifts out of place and
off the screen.
10.2 `syminfo.ticker` and `timeframe.period` are available inside compute (section 3.3),
and `timeframe.pine` gives Pine's own notation ('15'/'240'/'D'). Do not delete the
lines that use them and do not replace them with fixed text.
11. Do not use fetch or window or setTimeout — they do not exist in the sandbox.
(`console.log` works for debugging: it appears in the editor's test panel only, with
no effect on the chart.)
11.1 `request.security` is supported (section 3.4): convert it into
`request.security(sym, tf)` + a calculation with `TA` + `request.map`, and make the
code tolerate an empty array on the first run. And `barmerge.lookahead_on`
corresponds to `{ lookahead: true }` — without it the indicator does not repaint,
which is the correct default.
12. If the source has unsupported features (in practice, alerts), state them explicitly in
a comment at the top of the file rather than ignoring them silently, and convert the
rest. And do not declare anything unsupported before finding it as such in the
conversion table (section 5) — tables, the symbol and the timeframe, fetching another
timeframe/symbol, the bgcolor background, the price source and markers in the lower
pane are all supported.
13. Options disabled by default in the source should actually be implemented in the
calculation — their default value of false is enough to hide them, there is no need to
delete the code.
14. **If the source draws in a separate pane as rows** (ICT Quarterly, session maps,
gauges under the chart): set `overlay: false`, and use fixed row coordinates
(`y = 0,1,2…`) **not prices** (high/low). Review the "drawing in a separate sub-pane"
section. If the source combines drawing on the price and drawing in a pane at once (a
display-on-chart switch per cycle), that is impossible in a single indicator: pick the
row-based pane and drop the dual switch, and mention it in a comment at the top of the
file.
Output the code only, ready to paste into the editor.
If you want the conversion in a single reply
For small indicators (fewer than 10 inputs) the split is not necessary — send both prompts together, preceded by "do both stages in one reply". For large indicators do not; that is exactly what produces a poor settings panel.
If the code does not work on the first try: copy the error message from the editor's panel and hand it back to the AI — most problems are solved in an attempt or two, and the most common of them is an offset error.
If the settings come out poor anyway: ask for the honesty table explicitly — "how many inputs are in the source and how many did you convert?". The number exposes silent omission immediately.