Selcoin/ docs
SelScriptÖrnek kütüphanesi

Finansal Sağlık skoru

Gelir, EPS, marj ve nakit akışıyla tam tablo örneği.

Gereksinimler

Desteklenen bir hisse grafiği gerekir. Gelir için son dönem ile 1, 2, 3, 4 ve 8 dönem öncesi; brüt kâr, seyreltilmiş EPS ve serbest nakit akışı için son dönem ile dört dönem öncesi kullanılır. Bu nedenle yalnız birkaç çeyreklik verisi olan şirkette VERİ YETERSİZ görünmesi beklenir.

Puanlama

BileşenAzami puan
Yıllık gelir büyümesi20
Gelir büyüme ivmesi10
Gelir oynaklığı, CV10
EPS büyümesi20
EPS büyümesinin geliri geçmesi10
Brüt marj15
Brüt marj trendi5
Serbest nakit akışı marjı7
Serbest nakit akışı büyümesi3

Hedefe ulaşma oranı 0–1 aralığına sınırlanır ve math.pow(ratio, 1.8) ile puanlanır; doğrusal puanlama değildir. Gelir CV’si beş gözlemin popülasyon standart sapmasının mutlak ortalamaya oranıdır. Eksik gözlemler CV hesabına katılmaz; ancak tam skorun veri hazır kontrolü bütün gerekli dönemleri ister. CV %100 üzerindeyse 20 puan ceza vardır.

Boolean koşulu kapatmak o kriterin puanını devre dışı bırakmaz; şartı sağlanmış kabul eder. Ağırlık toplamı 100 olsa da nihai sonuç 0–99’a sınırlanıp yuvarlanır. Bu nedenle panel /100 yazsa da 100 puan üretmez. Bunlar örneğin bilinçli formül kurallarıdır.

Etiketler ve sınırlar

85 ve üzeri POZİTİF+, 70–84 POZİTİF, 51–69 NÖTR, altı NEGATİF. Veri hazır değilse sayısal puan yerine —, görünümde VERİ YETERSİZ yazılır. Son raporlanmış bilanço snapshot’ıdır; geçmiş performans veya işlem sinyali değildir.

Tablo konumu ayarlardan değiştirilebilir. Tam kod iki dilde aynı tutulur; içindeki kullanıcı başlıkları Türkçedir. Pine kullanıcı fonksiyonları ve dizileri açık formüllere dönüştürülmüştür.

Tam kod

//@selscript=1
indicator("Sağlık Skoru", overlay=true)

// Kullanıcı girişleri
i_revGrowthMin = input.float("Min. Yıllık Gelir Büyümesi (%)", 25.0, min=0, step=5)
i_revenueAccel = input.bool("Gelir ivmesi artıyor mu? (4 çeyrek)", true)
i_grossMarginMin = input.float("Min. Brüt Marj (%)", 30.0, min=0, step=5)
i_grossMarginTrend = input.bool("Brüt marj trendi yükselen mi?", true)
i_revenueCvMax = input.float("Max Gelir Oynaklığı — CV% eşiği", 30.0, min=0, step=5)
i_epsGrowthMin = input.float("Min. EPS Büyümesi YoY (%)", 25.0, min=0, step=5)
i_epsBeatRev = input.bool("EPS büyümesi geliri geçmeli mi?", true)
i_fcfMarginMin = input.float("Min. FCF Marjı (%)", 15.0, min=0, step=5)

// Puan ağırlıkları
W_REV_GROWTH = 20.0
W_REV_ACCEL = 10.0
W_REV_CV = 10.0
W_EPS_GROWTH = 20.0
W_EPS_BEAT = 10.0
W_GROSS_MGN = 15.0
W_GM_TREND = 5.0
W_FCF_MARGIN = 7.0
W_FCF_GROWTH = 3.0
HIGH_CV_PENALTY = 20.0

// Son raporlanmış bilanço snapshot’ı. offset, grafik barı değil mali dönem sayısıdır.
rev_q0 = request.financial(market.symbol, "TOTAL_REVENUE", "FQ", offset=0)
rev_q1 = request.financial(market.symbol, "TOTAL_REVENUE", "FQ", offset=1)
rev_q2 = request.financial(market.symbol, "TOTAL_REVENUE", "FQ", offset=2)
rev_q3 = request.financial(market.symbol, "TOTAL_REVENUE", "FQ", offset=3)
rev_q4 = request.financial(market.symbol, "TOTAL_REVENUE", "FQ", offset=4)
rev_q8 = request.financial(market.symbol, "TOTAL_REVENUE", "FQ", offset=8)
gp_q0 = request.financial(market.symbol, "GROSS_PROFIT", "FQ", offset=0)
gp_q4 = request.financial(market.symbol, "GROSS_PROFIT", "FQ", offset=4)
eps_q0 = request.financial(market.symbol, "EARNINGS_PER_SHARE_DILUTED", "FQ", offset=0)
eps_q4 = request.financial(market.symbol, "EARNINGS_PER_SHARE_DILUTED", "FQ", offset=4)
fcf_q0 = request.financial(market.symbol, "FREE_CASH_FLOW", "FQ", offset=0)
fcf_q4 = request.financial(market.symbol, "FREE_CASH_FLOW", "FQ", offset=4)

// Büyüme ve ivme
revGrowthNow = not is_na(rev_q0) and not is_na(rev_q4) and rev_q4 != 0 ? (rev_q0 - rev_q4) / math.abs(rev_q4) * 100 : na
revGrowthPrev = not is_na(rev_q4) and not is_na(rev_q8) and rev_q8 != 0 ? (rev_q4 - rev_q8) / math.abs(rev_q8) * 100 : na
accelOk = not i_revenueAccel or (not is_na(revGrowthNow) and not is_na(revGrowthPrev) and revGrowthNow > revGrowthPrev)

// Beş gelir gözlemi: array yerine açık toplam ve popülasyon varyansı.
// Eksik gözlemler ortalamaya/standart sapmaya katılmaz; hepsi eksikse na.
revValid0 = not is_na(rev_q0)
revValid1 = not is_na(rev_q1)
revValid2 = not is_na(rev_q2)
revValid3 = not is_na(rev_q3)
revValid4 = not is_na(rev_q4)
revCount = (revValid0 ? 1 : 0) + (revValid1 ? 1 : 0) + (revValid2 ? 1 : 0) + (revValid3 ? 1 : 0) + (revValid4 ? 1 : 0)
revSum = (revValid0 ? rev_q0 : 0) + (revValid1 ? rev_q1 : 0) + (revValid2 ? rev_q2 : 0) + (revValid3 ? rev_q3 : 0) + (revValid4 ? rev_q4 : 0)
revMean = revCount > 0 ? revSum / revCount : na
revDiff0 = revValid0 ? rev_q0 - revMean : 0
revDiff1 = revValid1 ? rev_q1 - revMean : 0
revDiff2 = revValid2 ? rev_q2 - revMean : 0
revDiff3 = revValid3 ? rev_q3 - revMean : 0
revDiff4 = revValid4 ? rev_q4 - revMean : 0
revSquaredSum = revDiff0 * revDiff0 + revDiff1 * revDiff1 + revDiff2 * revDiff2 + revDiff3 * revDiff3 + revDiff4 * revDiff4
revStdev = revCount > 0 ? math.sqrt(revSquaredSum / revCount) : na
revCV = not is_na(revMean) and revMean != 0 ? revStdev / math.abs(revMean) * 100 : na

epsGrowth = not is_na(eps_q0) and not is_na(eps_q4) and eps_q4 != 0 ? (eps_q0 - eps_q4) / math.abs(eps_q4) * 100 : na
epsBeatOk = not i_epsBeatRev or (not is_na(epsGrowth) and not is_na(revGrowthNow) and epsGrowth > revGrowthNow)

gmNow = not is_na(gp_q0) and not is_na(rev_q0) and rev_q0 != 0 ? gp_q0 / rev_q0 * 100 : na
gmPrev = not is_na(gp_q4) and not is_na(rev_q4) and rev_q4 != 0 ? gp_q4 / rev_q4 * 100 : na
gmTrendOk = not i_grossMarginTrend or (not is_na(gmNow) and not is_na(gmPrev) and gmNow > gmPrev)

fcfMargin = not is_na(fcf_q0) and not is_na(rev_q0) and rev_q0 != 0 ? fcf_q0 / rev_q0 * 100 : na
fcfGrowth = not is_na(fcf_q0) and not is_na(fcf_q4) and fcf_q4 != 0 ? (fcf_q0 - fcf_q4) / math.abs(fcf_q4) * 100 : na
fcfGrowthOk = not is_na(fcfGrowth) and fcfGrowth > 0

// Kullanıcı fonksiyonları yerine açılmış puan formülleri.
// math.pow(ratio, 1.8) korunur; doğrusal yaklaşımla değiştirilmez.
ratioRevGrowth = i_revGrowthMin > 0 and not is_na(revGrowthNow) ? math.max(0.0, math.min(1.0, revGrowthNow / i_revGrowthMin)) : na
scoreRevGrowth = not is_na(ratioRevGrowth) ? math.pow(ratioRevGrowth, 1.8) * W_REV_GROWTH : (not is_na(revGrowthNow) and i_revGrowthMin <= 0 and revGrowthNow >= 0 ? W_REV_GROWTH : 0.0)
ratioEpsGrowth = i_epsGrowthMin > 0 and not is_na(epsGrowth) ? math.max(0.0, math.min(1.0, epsGrowth / i_epsGrowthMin)) : na
scoreEpsGrowth = not is_na(ratioEpsGrowth) ? math.pow(ratioEpsGrowth, 1.8) * W_EPS_GROWTH : (not is_na(epsGrowth) and i_epsGrowthMin <= 0 and epsGrowth >= 0 ? W_EPS_GROWTH : 0.0)
ratioGrossMgn = i_grossMarginMin > 0 and not is_na(gmNow) ? math.max(0.0, math.min(1.0, gmNow / i_grossMarginMin)) : na
scoreGrossMgn = not is_na(ratioGrossMgn) ? math.pow(ratioGrossMgn, 1.8) * W_GROSS_MGN : (not is_na(gmNow) and i_grossMarginMin <= 0 and gmNow >= 0 ? W_GROSS_MGN : 0.0)
ratioFcfMargin = i_fcfMarginMin > 0 and not is_na(fcfMargin) ? math.max(0.0, math.min(1.0, fcfMargin / i_fcfMarginMin)) : na
scoreFcfMargin = not is_na(ratioFcfMargin) ? math.pow(ratioFcfMargin, 1.8) * W_FCF_MARGIN : (not is_na(fcfMargin) and i_fcfMarginMin <= 0 and fcfMargin >= 0 ? W_FCF_MARGIN : 0.0)

ratioRevCV = i_revenueCvMax > 0 and not is_na(revCV) ? math.max(0.0, math.min(1.0, (i_revenueCvMax * 2.0 - revCV) / i_revenueCvMax)) : na
scoreRevCV = not is_na(ratioRevCV) ? math.pow(ratioRevCV, 1.8) * W_REV_CV : (not is_na(revCV) and i_revenueCvMax <= 0 and revCV <= 0 ? W_REV_CV : 0.0)
scoreRevAccel = accelOk ? W_REV_ACCEL : 0.0
scoreEpsBeat = epsBeatOk ? W_EPS_BEAT : 0.0
scoreGmTrend = gmTrendOk ? W_GM_TREND : 0.0
scoreFcfGrowth = fcfGrowthOk ? W_FCF_GROWTH : 0.0
highCvPenalty = not is_na(revCV) and revCV > 100.0 ? HIGH_CV_PENALTY : 0.0

totalScoreRaw = (
    scoreRevGrowth + scoreRevAccel + scoreRevCV + scoreEpsGrowth +
    scoreEpsBeat + scoreGrossMgn + scoreGmTrend + scoreFcfMargin +
    scoreFcfGrowth - highCvPenalty
)
totalScore = math.round(math.max(0.0, math.min(99.0, totalScoreRaw)))
dataReady = not is_na(rev_q0) and not is_na(rev_q1) and not is_na(rev_q2) and not is_na(rev_q3) and not is_na(rev_q4) and not is_na(rev_q8) and not is_na(gmNow) and not is_na(gmPrev) and not is_na(epsGrowth) and not is_na(revGrowthNow) and not is_na(revGrowthPrev) and not is_na(fcfMargin) and not is_na(fcfGrowth)
scoreLabel = not dataReady ? "VERİ YETERSİZ" : totalScore >= 85 ? "POZİTİF+" : totalScore >= 70 ? "POZİTİF" : totalScore >= 51 ? "NÖTR" : "NEGATİF"
scoreColor = not dataReady ? "#9ba3aa" : totalScore >= 85 ? "#69f08b" : totalScore >= 70 ? "#4ade80" : totalScore >= 51 ? "#fbbf24" : "#f87171"
trendIcon = not dataReady ? "—" : totalScore >= 70 ? "↗" : totalScore >= 51 ? "→" : "↘"
statusIcon = not dataReady ? "—" : totalScore >= 70 ? "✓" : totalScore >= 51 ? "—" : "✕"

// Sabit tablo slotu; yalnız son barın paneli güncellenir.
// table.new her barda yeni nesne oluşturmaz.
// Birleşimler hazırlıkta doğrulanır.
panelBg = "#031016f2"
lineColor = totalScore >= 85 ? "#69f08b4d" : totalScore >= 70 ? "#4ade804d" : totalScore >= 51 ? "#fbbf244d" : "#f871714d"
iconBg = totalScore >= 85 ? "#69f08b1f" : totalScore >= 70 ? "#4ade801f" : totalScore >= 51 ? "#fbbf241f" : "#f871711f"
mutedText = "#d7dfe6"
noteColor = "#9ba3aa"
W_COL_ICON = 3.0
W_COL_LABEL = 8.0
W_COL_VALUE = 8.0
W_COL_STATUS = 3.0
W_MERGED = W_COL_ICON * 4

t = table.new("top_left", 4, 6, bgcolor=panelBg, border_width=0, frame_width=2, frame_color=scoreColor)
table.merge_cells(t, 0, 0, 3, 0)
table.merge_cells(t, 0, 5, 3, 5)

if barstate.islast:
    table.cell(t, 0, 0, "  FİNANSAL SAĞLIK", text_color="#ffffff", bgcolor=panelBg, text_size="large", text_halign="left", text_valign="center", width=W_MERGED)
    table.cell(t, 0, 1, "", bgcolor=lineColor, height=1, width=W_COL_ICON)
    table.cell(t, 1, 1, "", bgcolor=lineColor, height=1, width=W_COL_LABEL)
    table.cell(t, 2, 1, "", bgcolor=lineColor, height=1, width=W_COL_VALUE)
    table.cell(t, 3, 1, "", bgcolor=lineColor, height=1, width=W_COL_STATUS)
    table.cell(t, 0, 3, "", bgcolor=lineColor, height=1, width=W_COL_ICON)
    table.cell(t, 1, 3, "", bgcolor=lineColor, height=1, width=W_COL_LABEL)
    table.cell(t, 2, 3, "", bgcolor=lineColor, height=1, width=W_COL_VALUE)
    table.cell(t, 3, 3, "", bgcolor=lineColor, height=1, width=W_COL_STATUS)
    table.cell(t, 0, 2, trendIcon, text_color=scoreColor, bgcolor=iconBg, text_size="normal", text_halign="center", text_valign="center", width=W_COL_ICON)
    table.cell(t, 1, 2, "Görünüm:", text_color=mutedText, bgcolor=panelBg, text_size="normal", text_halign="left", text_valign="center", width=W_COL_LABEL)
    table.cell(t, 2, 2, scoreLabel, text_color=scoreColor, bgcolor=panelBg, text_size="normal", text_halign="center", text_valign="center", width=W_COL_VALUE)
    table.cell(t, 3, 2, statusIcon, text_color=scoreColor, bgcolor=iconBg, text_size="normal", text_halign="center", text_valign="center", width=W_COL_STATUS)
    table.cell(t, 0, 4, "🛡", text_color=scoreColor, bgcolor=iconBg, text_size="normal", text_halign="center", text_valign="center", width=W_COL_ICON)
    table.cell(t, 1, 4, "Sağlık Skoru:", text_color=mutedText, bgcolor=panelBg, text_size="normal", text_halign="left", text_valign="center", width=W_COL_LABEL)
    table.cell(t, 2, 4, (dataReady ? str.tostring(totalScore, 0) + "/100" : "—"), text_color=scoreColor, bgcolor=panelBg, text_size="normal", text_halign="center", text_valign="center", width=W_COL_VALUE)
    table.cell(t, 3, 4, statusIcon, text_color=scoreColor, bgcolor=iconBg, text_size="normal", text_halign="center", text_valign="center", width=W_COL_STATUS)
    table.cell(t, 0, 5, (dataReady ? "Son raporlanmış bilanço verileri · Tarihsel skor değildir." : "Puan için gerekli mali dönem veya metrik eksik."), text_color=noteColor, bgcolor=panelBg, text_size="tiny", text_halign="left", text_valign="center", width=W_MERGED)

Tüm örnekler

Bu sayfada