Paket kode trader — Python dan Pine Script

Enam cuplikan kode untuk disalin: empat dalam Python (memuat jurnal CSV, metrik, kurva ekuitas beserta drawdown, dan penghitung ukuran posisi ATR) serta dua dalam Pine Script (rentang harian terhadap ATR dengan peringatan, dan jendela sesi rencana trading pada grafik). Semuanya merupakan alat ANALISIS — menghitung, menggambar dan mengingatkan; tidak ada cuplikan yang menghasilkan sinyal beli/jual atau mengambil keputusan, karena itu tugas playbook. Pengenal kode dan nama API tetap dalam bahasa Inggris agar konsisten dengan dokumentasi pandas dan Pine Script; komentar dan label keluaran yang ditujukan kepada pembaca dilokalkan ke bahasa Indonesia. Konteks penggunaan kode dibahas pula dalam lokakarya AI untuk prompt yang melibatkan kode; penjelasan di sekitar cuplikan menggunakan bahasa kursus dan memandu Anda langkah demi langkah. Lingkungan: instalasi Python dan percobaan pertama dibahas dalam 11.8; judul kolom jurnal ada dalam lampiran F.

Meja kerja trader dan analis: editor kode dengan skrip analisis jurnal Python dan grafik dengan indikator Pine Script pada monitor
Pokok utama dalam 60 detik
  • Alat, bukan strategi: kode menghitung metrik dari data ANDA dan memantau rencana ANDA — batasannya sama dengan bagian “apa yang tidak dilakukan AI” dalam lampiran H, dengan alasan yang sama
  • Format data bersama: cuplikan Python membaca judul kolom lembar jurnal (lampiran F) — tingkat kemenangan memasukkan transaksi impas dalam penyebut dan ekspektasi menggunakan hasil bersih; definisinya sama dengan prompt P5
  • Pine Script tanpa lookahead: S5 mengambil data harian menggunakan lookahead=barmerge.lookahead_off; S6 sama sekali tidak mengakses kerangka waktu yang lebih tinggi (hanya fungsi time ). Ketiadaan lookahead tidak membuktikan ketiadaan repainting — uraian S5 menjelaskan tampilan pada bar historis dan secara waktu nyata. Inilah jebakan yang dicari dalam peninjauan kode dengan prompt P3
  • Penghitung membulatkan volume KE BAWAH: saat mengubah risiko menjadi lot, pembulatan ke atas secara sistematis meningkatkan risiko melampaui rencana — satu baris kode itulah perbedaan antara 1% dan “sekitar 1%”

Sebelum menjalankan kode: tiga aturan

  • Baca sebelum menjalankan. Setiap cuplikan sengaja dibuat singkat — Anda seharusnya memahami setiap baris. Ubah kode yang belum Anda pahami dengan bantuan peninjauan P3, bukan menebak; aturan ini berlaku lebih ketat lagi untuk cuplikan dari forum dan percakapan.
  • Data masuk dari berkas; keputusan tidak keluar dari kode. Skrip membaca ekspor jurnal (format dari lampiran F) dan parameter yang Anda berikan secara eksplisit. Keluarannya berupa angka dan grafik untuk tinjauan mingguan — Anda yang menarik kesimpulan, mengikuti proses audit dalam 15.10 serta memperhitungkan ukuran sampel, ketergantungan antartransaksi dan ketidakpastian hasil.
  • Lingkungan dari 11.8: Python 3.11 atau lebih baru dengan pandas dan matplotlib (instalasi dan skrip pertama dibahas di sana); cuplikan S1–S4 dijalankan ulang pada 17 September 2026 dengan Python 3.12.10, pandas 3.0.5 dan matplotlib 3.11.1. Versi lain belum diuji. Tempel Pine Script ke editor indikator platform grafik Anda; kedua indikator menggunakan versi 6.

Langkah minimum untuk menjalankan kode: pada lingkungan Python 3.11+ Anda, periksa versi dengan python --version, lalu instal paket dengan python -m pip install pandas==3.0.5 matplotlib==3.11.1, simpan S1+S2+S3 sebagai analiza_dziennika.py dan CSV uji sebagai dziennik.csv, lalu jalankan python analiza_dziennika.py. S4 hanya memerlukan pustaka standar. Tempel S5 dan S6 ke Pine Editor secara terpisah. Kedua cuplikan Pine diperiksa berdasarkan dokumentasi v6; keduanya belum dikompilasi dan peringatannya belum diuji di TradingView.

Alur analisis: ekspor jurnal CSV masuk ke skrip Python, metrik dan grafik kurva ekuitas keluar, lalu kesimpulannya masuk ke tinjauan mingguan dan playbook Jurnal CSVformat dari lampiran F Python: S1–S3metrik · kurva · drawdown Tinjauan akhir pekansampel, ketergantungan, ketidakpastian Playbookberi versi pada aturan dan pengujian tidak ada panah “kode → order” dalam alur kerja ini — dan itu bukan kelalaian

S1 · Memuat jurnal CSV

Python 3 · pandas

Titik awal S2 dan S3: membaca ekspor lembar kerja dari lampiran F, memeriksa judul kolom dan jumlah kolom, lalu membersihkan kolom r_net . Nama kolom duplikat, judul kosong, serta kolom terlalu banyak atau terlalu sedikit menghentikan impor; skrip tidak memilih di antara dua kolom hasil atau memindahkan data ke indeks. Tunjukkan kolom kosong dengan pemisah, misalnya ;A+;;0; baris terpotong merupakan kasus berbeda. Atur pemisah dan karakter desimal untuk varian CSV (PL: titik koma dan koma; EN: koma dan titik), beserta pengodeannya: berkas dari generator lampiran F menggunakan UTF-8, dan varian “CSV UTF-8” yang disimpan di Excel juga dapat dibaca sebagai UTF-8; ekspor lama dengan halaman kode Polandia mungkin memerlukan cp1250. Periksa format penyimpanan, karena bahasa antarmuka saja tidak menentukan pengodean. Kolom r_net kosong dan kolom close_time_utc kosong berarti transaksi belum ditutup (dan dilewati); waktu penutupan tanpa hasil berarti catatan belum lengkap dan menghentikan perhitungan. Nilai terisi yang tidak dapat dibaca sebagai angka (termasuk “NA” atau tanda hubung) menghentikan skrip dan melaporkan nomor catatan — demikian pula transaksi ditutup tanpa waktu penutupan dalam format lampiran F (2026-07-08 13:15), karena jika tidak, urutan kurva harus ditebak.

import pandas as pd

CSV_PATH = "dziennik.csv"   # ekspor jurnal (struktur lampiran F)
CSV_SEP  = ";"              # ";" untuk CSV dengan lokal PL, "," untuk EN
DECIMAL  = ","              # "," untuk CSV dengan lokal PL, "." untuk EN
ENCODING = "utf-8-sig"      # generator lampiran F menulis UTF-8 (BOM ditangani); CSV lama dengan halaman kode Polandia mungkin memerlukan "cp1250"

REQUIRED = ["r_net", "setup", "setup_class", "error_code", "close_time_utc"]

# Baca berkas lokal dan validasi strukturnya SEBELUM membuat DataFrame.
# pandas.read_csv dapat mengganti nama judul kolom duplikat atau menyimpulkan indeks dari kolom tambahan.
import csv

try:
    with open(CSV_PATH, encoding=ENCODING, newline="") as source:
        reader = csv.reader(source, delimiter=CSV_SEP, strict=True)
        header = next(reader, None)
        if not header:
            raise SystemExit("CSV tidak dapat dibaca: baris judul kosong.")
        if any(not name or name != name.strip() for name in header):
            raise SystemExit("Nama kolom kosong atau memiliki spasi di tepi — perbaiki judul kolom CSV.")
        duplicates = sorted({name for name in header if header.count(name) > 1})
        if duplicates:
            raise SystemExit(f"Kolom duplikat: {duplicates} — perbaiki di jurnal.")
        rows, record_indexes = [], []
        for index, row in enumerate(reader):
            if not row:                              # satu catatan CSV yang sepenuhnya kosong
                continue
            if len(row) != len(header):
                raise SystemExit(f"Catatan {index + 1}: diharapkan {len(header)} kolom, ditemukan {len(row)} "
                                 f"— periksa pemisah dan tanda kutip; tidak ada kolom yang dibuang.")
            rows.append(row)
            record_indexes.append(index)
    df = pd.DataFrame(rows, columns=header, index=record_indexes, dtype="string").replace("", pd.NA)
except UnicodeDecodeError:
    raise SystemExit(f"Tidak dapat mendekode {CSV_PATH} sebagai {ENCODING} — periksa UTF-8 atau cp1250 lama.") from None
except (OSError, csv.Error) as exc:
    raise SystemExit(f"Tidak dapat membaca {CSV_PATH}: {exc}") from None

missing = [c for c in REQUIRED if c not in df.columns]
if missing:
    raise SystemExit(f"Kolom yang belum ada: {missing} — periksa judul kolom terhadap lampiran F dan pengaturan CSV_SEP.")

raw = df["r_net"]                                   # hasil kosong hanya diperbolehkan jika waktu penutupan juga kosong
txt = raw.astype("string").str.strip().str.replace(DECIMAL, ".", regex=False)   # parsing kolom dilakukan sendiri: satu sel yang salah tidak boleh menyembunyikan sel yang dapat dibaca
r   = pd.to_numeric(txt, errors="coerce").astype("Float64")  # hindari luapan bilangan bulat dalam penjumlahan selanjutnya
r   = r.where(r.abs() != float("inf"))              # inf juga bukan kelipatan R yang valid
invalid = raw.notna() & r.isna()                    # terisi, tetapi tidak dapat dibaca sebagai angka
if invalid.any():
    raise SystemExit(f"r_net tidak valid pada catatan {(df.index[invalid] + 1).tolist()} "
                     f"(catatan 1 = baris pertama di bawah judul) — periksa nilai dan DECIMAL; "
                     f"NA, tanda hubung dan tak hingga bukan angka.")

blank = df.isna().all(axis=1)                       # seluruh baris kosong = baris lembar yang tidak terpakai (ekspor seluruh lembar XLSX), bukan transaksi
df, r = df[~blank], r[~blank]
has_close = df["close_time_utc"].fillna("").str.strip().ne("")
incomplete = r.isna() & has_close
if incomplete.any():
    raise SystemExit(f"r_net belum ada untuk catatan yang ditutup {(df.index[incomplete] + 1).tolist()} "
                     f"— lengkapi hasil bersih sebelum menghitung metrik.")
open_trades = int(r.isna().sum())
df = df.assign(r_net=r).dropna(subset=["r_net"])   # indeks dipertahankan = nomor catatan untuk pesan di bawah

# kurva ekuitas dan drawdown memerlukan urutan kronologis (lampiran F: menurut waktu penutupan) — transaksi ditutup tanpa waktu penutupan yang terbaca menghentikan skrip
close_text = df["close_time_utc"].fillna("").str.strip()
has_time = close_text.str.fullmatch(r"\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})?")
closed = pd.to_datetime(close_text.where(has_time), errors="coerce", format="ISO8601", utc=True)   # lampiran F menulis 2026-07-08 13:15; format tetap menjaga pemeriksaan tidak bergantung pada urutan baris, utc=True mencegah akhiran "Z"/"+00:00" yang terselip membuatnya gagal
if closed.isna().any():
    raise SystemExit(f"close_time_utc tidak terbaca pada catatan {(df.index[closed.isna()] + 1).tolist()} "
                     f"— setiap transaksi yang ditutup memerlukan waktu penutupan dalam format lampiran F (2026-07-08 13:15); perbaiki jurnal, bukan skrip.")
df = df.assign(_t=closed).sort_values("_t", kind="stable").drop(columns="_t").reset_index(drop=True)

print(f"Dimuat {len(df)} transaksi ditutup ({open_trades} baris dilewati: tanpa waktu penutupan dan tanpa r_net).")

Anda memperoleh: DataFrame, df, yang siap untuk cuplikan berikutnya, diurutkan menurut waktu penutupan (kurva dan drawdown dihitung dalam urutan itu), serta jumlah transaksi terbuka yang dilewati (baris kosong berarti catatan sepenuhnya kosong, seperti pada ekspor seluruh lembar XLSX — skrip tidak menghitung baris ini; baris berisi data tetapi tanpa hasil maupun waktu penutupan dilewati sebagai belum ditutup; entri ditutup tanpa hasil menghentikan skrip). Jika skrip berhenti dengan daftar catatan, perbaiki baris terkait dalam jurnal, bukan skrip; jika seluruh kolom terdampak, periksa juga konstanta DECIMAL . Penomoran catatan dimulai setelah judul dan mencakup catatan kosong; kolom dalam tanda kutip dapat memuat pemisah atau baris baru dan tetap menjadi bagian catatan yang sama. Setiap transaksi harus muncul sekali: periksa pengenal trade_id sebelum menggabungkan ekspor — S1 tidak menghapus duplikasi riwayat. Tanggal tanpa jam tidak cukup untuk merekonstruksi urutan. Waktu tanpa penanda zona waktu dianggap UTC; offset ISO 8601 eksplisit dikonversi ke UTC. Untuk waktu identik, skrip mempertahankan urutan catatan — tetapkan urutan itu di jurnal karena memengaruhi lintasan drawdown.

S2 · Metrik jurnal

Python 3 · pandas

Definisi sama dengan prompt P5 dan dasbor lembar kerja: tingkat kemenangan memasukkan transaksi impas dalam penyebut; profit factor mengembalikan pesan alih-alih membagi dengan nol; ekspektasi adalah rata-rata R bersih (r_net); dan drawdown diukur dari kurva kumulatif yang dimulai pada 0 R (kerugian pertama sudah merupakan drawdown — seperti rumus dalam lembar F). Jalankan setelah S1. Definisi metriknya sama, tetapi kebijakan kesalahan berbeda dari P5: S1 menghentikan analisis jika transaksi ditutup memiliki hasil tidak valid, sedangkan P5 memerintahkan untuk melewati baris itu dan menghitung jumlahnya. Bandingkan hasil hanya setelah menetapkan transaksi yang masuk ke kedua sampel.

import math

def metrics(r):
    "Metrik inti jurnal pada deret kelipatan R bersih.\n    Definisi (sama dengan dasbor lampiran F):\n    - tingkat kemenangan: transaksi dengan r > 0 dibagi SEMUA transaksi valid (impas tetap dalam penyebut)\n    - profit factor: jumlah r positif dibagi abs(jumlah r negatif)\n    - ekspektasi: rata-rata aritmetika r (setelah biaya)\n    - drawdown maksimum: penurunan terbesar kurva R kumulatif dari puncak berjalannya,\n      kurva dimulai pada 0 R (jadi kerugian pertama -1 R sudah merupakan drawdown 1 R)\n"
    n = len(r)
    if n == 0:
        raise SystemExit("Tidak ada transaksi valid — tidak ada yang dapat diukur.")
    try:
        r = r.astype(float)
    except (TypeError, ValueError):
        raise SystemExit("Metrik memerlukan nilai R bersih numerik — jalankan S1 terlebih dahulu.") from None
    if not r.map(math.isfinite).all():
        raise SystemExit("Metrik memerlukan nilai R bersih berhingga — jalankan S1 terlebih dahulu.")
    wins   = r[r > 0]
    losses = r[r < 0]
    equity   = r.cumsum()
    peak     = equity.cummax().clip(lower=0)   # lampiran F: MAX(0; puncak berjalan)
    drawdown = peak - equity
    try:
        total = math.fsum(r)
        pf_value = None if losses.empty else math.fsum(wins) / abs(math.fsum(losses))
    except (OverflowError, ValueError, ZeroDivisionError):
        raise SystemExit("Rentang numerik terlampaui — periksa nilai R; tidak ada metrik yang dikembalikan.") from None
    if (not equity.map(math.isfinite).all() or not drawdown.map(math.isfinite).all()
            or (pf_value is not None and not math.isfinite(pf_value))):
        raise SystemExit("Rentang numerik terlampaui — periksa nilai R; tidak ada metrik yang dikembalikan.")
    pf = "tidak ada kerugian dalam sampel" if pf_value is None else round(pf_value, 2)
    return {
        "trades":         n,
        "win_rate":       round(len(wins) / n, 3),
        "profit_factor":  pf,
        "expectancy_R":   round(total / n, 3),
        "sum_R":          round(total, 2),
        "max_drawdown_R": round(drawdown.max(), 2),
    }

for key, value in metrics(df["r_net"]).items():
    print(f"{key:>16}: {value}")

Anda memperoleh: enam metrik untuk tinjauan mingguan; jika tidak ada kerugian, PF berupa pesan, bukan angka. Jika nilai melampaui rentang perhitungan, skrip menghentikan analisis alih-alih mengembalikan inf, NaN atau drawdown yang tampak nol. WR = 0.4 berarti 40%. PF dihitung dalam R, bukan PLN; jika risiko uang bervariasi, hasilnya dapat berbeda. Biaya sudah masuk ke dalam r_net — S2 tidak mengurangkannya lagi. Jangan mengharapkan penilaian keunggulan dari rata-rata saja atau sekadar setelah melewati 20 atau 50 transaksi. Definisi dasbor terdapat dalam lampiran F, sedangkan rencana pemilihan sampel, biaya dan validasi terdapat dalam lampiran N.

S3 · Kurva ekuitas dan drawdown

Python 3 · pandas + matplotlib

Menggambar kurva R kumulatif dengan drawdown dari puncak di bawahnya dan menyimpan PNG di samping berkas CSV yang ditentukan oleh CSV_PATH (S1), terlepas dari direktori tempat skrip dijalankan. Kurva memperlihatkan urutan hasil dan kelompok kerugian yang tidak terlihat dalam angka agregat. Ini penjumlahan R, bukan saldo akun dalam PLN atau persentase ekuitas: jika risiko uang bervariasi, kurva tidak mereproduksi perubahan saldo. Grafik ini tidak mencakup setoran, penarikan atau hasil posisi terbuka. Jalankan setelah S1.

import math
import matplotlib.pyplot as plt
from pathlib import Path

if len(df) == 0:
    raise SystemExit("Tidak ada transaksi ditutup — tidak ada yang dapat digambar.")

equity   = df["r_net"].astype(float).cumsum()
drawdown = equity.cummax().clip(lower=0) - equity   # definisi sama dengan S2 dan lampiran F (kurva dimulai pada 0 R)

if not equity.map(math.isfinite).all() or not drawdown.map(math.isfinite).all():
    raise SystemExit("Rentang numerik terlampaui — periksa nilai R; grafik tidak disimpan.")

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6), sharex=True,
                               gridspec_kw={"height_ratios": [3, 1]})
x = [0, *(equity.index + 1)]                        # transaksi 0 = titik awal 0 R yang menjadi acuan pengukuran drawdown
ax1.plot(x, [0.0, *equity.values])
ax1.set_ylabel("ekuitas (R)")
ax1.set_title("Kurva ekuitas — R bersih kumulatif")
ax1.grid(True, alpha=0.3)

ax2.fill_between(x, [0.0, *(-drawdown.values)], 0, alpha=0.5)
ax2.set_ylabel("drawdown (R)")
ax2.set_xlabel("transaksi #")
ax2.grid(True, alpha=0.3)

fig.tight_layout()
PNG_PATH = Path(CSV_PATH).with_name("equity_curve.png")   # di samping berkas jurnal, terlepas dari direktori kerja
fig.savefig(PNG_PATH, dpi=150)
plt.close(fig)
print(f"Disimpan: {PNG_PATH}")

Anda memperoleh: berkas equity_curve.png di samping jurnal; kurva dimulai pada 0 R (transaksi 0), yang menjadi acuan pengukuran drawdown. Panel bawah menunjukkan drawdown R kumulatif dari puncaknya (dimulai pada 0 R, seperti S2 dan lembar F) sepanjang seluruh riwayat — ini bukan kerugian mingguan. Periksa batas mingguan dari rencana (lampiran E) secara terpisah, minggu demi minggu, menggunakan satuan dan aturan reset dalam rencana.

Berkas uji dan keluaran yang diharapkan (S1 + S2 + S3) CSV · format PL

Simpan sebagai dziennik.csv (UTF-8) di samping skrip gabungan S1+S2+S3 lalu jalankan. Berkas hanya memuat kolom yang diperlukan S1 — ekspor lengkap dari lampiran F (26 kolom) dimuat dengan cara sama. Baris terakhir adalah transaksi terbuka (r_net kosong); dua transaksi ditutup pertama merugi, sehingga drawdown diukur dari 0 R.

trade_id;close_time_utc;setup;setup_class;r_net;error_code
2026-041;2026-07-08 13:15;wybicie-azja-londyn;A+;-1,00;5
2026-042;2026-07-09 16:40;powrot-do-sredniej;B;-1,08;3
2026-043;2026-07-10 09:05;wybicie-azja-londyn;A+;1,83;0
2026-044;2026-07-13 12:20;wybicie-azja-londyn;B;0,00;0
2026-045;2026-07-14 14:30;powrot-do-sredniej;C;2,40;0
2026-046;;wybicie-azja-londyn;B;;

Keluaran yang diharapkan (Python 3.12.10, pandas 3.0.5, matplotlib 3.11.1; dijalankan pada 17 September 2026):

Dimuat 5 transaksi ditutup (1 baris dilewati: tanpa waktu penutupan dan tanpa r_net).
          trades: 5
        win_rate: 0.4
   profit_factor: 2.03
    expectancy_R: 0.43
           sum_R: 2.15
  max_drawdown_R: 2.08
Disimpan: equity_curve.png

Perhitungan untuk diperiksa manual: keuntungan 1.83 + 2.40 = 4.23 R, kerugian 1.00 + 1.08 = 2.08 R → PF 2.03; total 2.15 R / 5 = 0.43 R; kurva −1.00 → −2.08 → −0.25 → −0.25 → 2.15, dengan puncak awal 0 R → drawdown 2.08 R (tanpa titik 0 R, hasilnya 1.08 R — inilah perbedaan di balik definisi lampiran F). Keluaran mengacu pada berkas persis ini dan versi yang dinyatakan; jika berbeda, periksa data (pemisah, karakter desimal, pengodean, urutan baris), konstanta S1, kode, lalu versi paket, sesuai urutan tersebut.

S4 · Penghitung ukuran posisi ATR

Python 3 · pustaka standar

Volume dalam lot untuk jarak stop sebesar k × ATR: jumlah risiko berasal dari modal dan persentase, jarak dari ATR, dan volume dibulatkan KE BAWAH ke langkah lot akun Anda (parameter lot_step dan min_lot , keduanya bernilai awal 0.01) — agar risiko TERENCANA tidak melebihi anggaran. Kerugian aktual dapat lebih besar: perhitungan ini tidak mencakup slippage, gap melewati stop, spread, komisi dan swap. Modal dan nilai pip harus dalam mata uang akun yang sama; periksa secara terpisah volume maksimum dan persyaratan margin dalam spesifikasi. Masukkan ATR dalam pip (EURUSD 0.0080 → 80; USDJPY 0.80 → 80; ukuran pip dibahas dalam profil pasangan, lampiran B); nilai pip untuk 1 lot ada dalam spesifikasi instrumen (atau dapat dihitung menggunakan kalkulator lampiran C).

import math
from decimal import Decimal
from fractions import Fraction

def atr_position_size(capital, risk_pct, atr_pips, atr_mult, pip_value_per_lot,
                      lot_step=0.01, min_lot=0.01):
    "Volume dalam lot untuk stop sebesar atr_mult × ATR.\n    atr_pips: ATR dalam PIP, bukan satuan harga (EURUSD ATR 0.0080 -> 80; USDJPY ATR 0.80 -> 80 — ukuran pip pasangan).\n    pip_value_per_lot: nilai 1 pip untuk 1.00 lot, dalam mata uang akun\n    (mis. ~10 USD untuk EURUSD pada akun USD — periksa spesifikasi instrumen).\n    lot_step / min_lot: langkah volume dan minimum akun ANDA (0.01 umum, tetapi tidak universal).\n    Membulatkan KE BAWAH ke kelipatan lot_step, dihitung dari nol. Aritmetika Fraction\n    mempertahankan masukan desimal tanpa bergantung pada presisi Decimal global.\n    Risiko terencana bukan kerugian maksimum: slippage, gap, spread dan swap tidak tercakup dalam model ini.\n"
    inputs = (capital, risk_pct, atr_pips, atr_mult, pip_value_per_lot, lot_step, min_lot)
    if any(isinstance(x, bool) or not isinstance(x, (int, float, Decimal)) for x in inputs):
        raise ValueError("Gunakan nilai numerik, bukan teks atau boolean (desimal Python menggunakan titik).")
    decimals = tuple(Decimal(str(x)) for x in inputs)
    if not all(x.is_finite() and x > 0 for x in decimals):
        raise ValueError("Semua masukan harus berupa angka positif berhingga.")
    c, p, a, k, v, step, minimum = map(Fraction, decimals)
    if p > 100:
        raise ValueError("risk_pct adalah persentase modal (1.0 = 1%).")
    risk_amount = c * p / 100
    sl_pips     = a * k
    lots        = math.floor(risk_amount / (sl_pips * v * step)) * step
    if lots < minimum:
        return {"warning": "Tidak ada volume yang memenuhi lot_step dan min_lot ini dalam anggaran risiko."}
    exact = {"risk_amount": risk_amount, "sl_pips": sl_pips,
             "lots": lots, "planned_risk": lots * sl_pips * v}
    try:
        result = {key: float(value) for key, value in exact.items()}
    except OverflowError:
        raise ValueError("Masukan melebihi rentang numerik keluaran.") from None
    if not all(math.isfinite(value) and value > 0 for value in result.values()):
        raise ValueError("Masukan melebihi rentang numerik keluaran.")
    return result  # float untuk tampilan; tanpa pembulatan tambahan pada stop atau nilai uang


print(atr_position_size(capital=10_000, risk_pct=1.0,
                        atr_pips=80, atr_mult=1.5, pip_value_per_lot=10.0))
# {'risk_amount': 100.0, 'sl_pips': 120.0, 'lots': 0.08, 'planned_risk': 96.0}

Anda memperoleh: lot yang dibulatkan ke bawah sesuai langkah dan jumlah risiko TERENCANA yang dihasilkan (dalam model tanpa biaya, ≤ anggaran; kerugian aktual dapat lebih besar). Langkah diukur dari nol; periksa apakah akun menggunakan kisi volume ini. Stop dan jumlah uang tidak dibulatkan lagi: misalnya ATR 0.26 pip × 1.25 menghasilkan 0.325 pip, bukan 0.3. Keluaran dikembalikan sebagai nilai float untuk dibaca, sementara jumlah langkah lot dihitung menggunakan pecahan eksak; perubahan harga minimum instrumen tetap harus diperiksa secara terpisah. Jika Anda menyesuaikan stop ke perubahan minimum itu, hitung kembali volumenya. Jangan mengharapkan: keputusan untuk masuk posisi — penghitung menjawab “berapa banyak”, tidak pernah “apakah”.

S5 · Rentang harian vs ATR dengan peringatan

Pine Script v6 · indikator

Alat kesadaran volatilitas (indikator intrahari): pada bar waktu nyata, menampilkan persentase ATR harian yang sudah “terpakai” oleh pasar dan memberi peringatan saat ambang dilintasi — saat untuk membaca rencana, bukan mengejar harga. Data harian diambil tanpa lookahead (tanpa nilai masa depan); pada bar historis, request.security mempertahankan nilai harian terkonfirmasi terakhir sampai akhir periode D berikutnya, dengan hasil baru periode itu muncul pada bar intrahari terakhirnya. Secara waktu nyata, indikator mengambil rentang dan ATR candle D saat ini yang sedang berkembang, sehingga pembacaan sebelumnya dapat berubah setelah pemuatan ulang (repainting). Satu hari berarti candle D penyedia data, bukan hari kalender dalam Europe/Warsaw. ATR dalam penyebut juga berubah sepanjang hari; 100% bukan batas pergerakan harga. Metode ini tidak dapat merekonstruksi rangkaian lintasan ambang hari ini pada bar historis.

//@version=6
indicator("Rentang harian vs ATR (alat kesadaran)", overlay=false)

if not timeframe.isintraday
    runtime.error("Gunakan grafik intrahari berbasis waktu di bawah 1D.")

atrLen  = input.int(14, "Periode ATR", minval=2)
warnPct = input.int(80, "Peringatkan saat rentang harian mencapai % ATR", minval=10, maxval=200)

// Historis: nilai D terkonfirmasi terakhir, diperbarui pada AKHIR setiap periode D.
//Waktu nyata: nilai D yang sedang berkembang; pembacaan waktu nyata yang telah berlalu dapat berubah setelah pemuatan ulang.
dayHigh = request.security(syminfo.tickerid, "D", high,            lookahead=barmerge.lookahead_off)
dayLow  = request.security(syminfo.tickerid, "D", low,             lookahead=barmerge.lookahead_off)
dayAtr  = request.security(syminfo.tickerid, "D", ta.atr(atrLen),  lookahead=barmerge.lookahead_off)

rangePct = dayAtr > 0 ? 100.0 * (dayHigh - dayLow) / dayAtr : na

plot(rangePct, "Rentang harian sebagai % ATR", linewidth=2)
hline(100, "100% ATR")
hline(warnPct, "Tingkat peringatan", linestyle=hline.style_dotted)

alertcondition(ta.crossover(rangePct, warnPct),
     title="Rentang mencapai % ATR",
     message="Rentang harian mencapai % ATR pilihan Anda — baca kembali rencana sebelum masuk posisi baru.")

Anda memperoleh: panel di bawah grafik dan kondisi peringatan — buat sendiri peringatannya di platform setelah menambahkan indikator, dengan memilih “Rentang mencapai % ATR”. Gunakan candle intrahari standar berbasis waktu; data Heikin Ashi/Renko dapat memiliki karakteristik berbeda. Pembacaan yang tidak tersedia (na) berarti ATR atau data yang diperlukan tidak tersedia, bukan 0%. Kondisi mensyaratkan pergerakan dari nilai ≤ ambang ke nilai > ambang: sekadar tetap berada di atasnya tidak memicu peringatan lain. “Once Per Bar Close” membatasi peringatan pada penutupan candle intrahari, tetapi tidak mengonfirmasi seluruh candle D. Buat ulang peringatan setelah mengubah parameter. Jangan mengharapkan: sinyal arah — pembacaan tinggi mengatakan “pergerakan sudah terjadi”; playbook memberi tahu tindakan Anda.

S6 · Jendela sesi rencana trading pada grafik

Pine Script v6 · indikator

Bagian 3 rencana (lampiran E), digambar pada grafik: menyoroti jam ketika Anda BOLEH membuka posisi dan memberi peringatan saat jendela ditutup. Masukkan waktu dalam zona Europe/Warsaw — pengenal IANA memperhitungkan CET/CEST. Secara bawaan, jendela berlaku Senin hingga Jumat; ada pilihan untuk memperluasnya ke semua hari. Sorotan hanya mengonfirmasi waktu dalam rencana, bukan syarat masuk lainnya. Gunakan candle intrahari standar berbasis waktu.

//@version=6
indicator("Jendela sesi rencana trading", overlay=true)

if not timeframe.isintraday
    runtime.error("Gunakan grafik intrahari berbasis waktu di bawah 1D.")

tz   = input.string("Europe/Warsaw", "Zona waktu jendela")
win1 = input.session("0900-1200", "Jendela 1")
win2 = input.session("1400-1700", "Jendela 2")

weekdaysOnly = input.bool(true, "Hanya Senin sampai Jumat")
days = weekdaysOnly ? ":23456" : ":1234567"

inWin = not na(time(timeframe.period, win1 + days, tz)) or not na(time(timeframe.period, win2 + days, tz))

bgcolor(inWin ? color.new(color.teal, 88) : na, title="Di dalam jendela rencana")

windowJustClosed = inWin[1] and not inWin
alertcondition(windowJustClosed,
     title="Jendela rencana ditutup",
     message="Jendela trading Anda baru saja ditutup — kelola hanya posisi terbuka, tanpa entri baru (rencana, bagian 3).")

Anda memperoleh: arsiran latar selama jam trading dan kondisi peringatan saat jendela ditutup (“Jendela rencana ditutup” — buat peringatan di platform setelah menambahkan indikator). Peringatan mendeteksi candle pertama di luar jendela setelah candle di dalamnya, sehingga tidak menjamin pemberitahuan tepat pada waktu penutupan: tanpa candle baru dan pembaruan data, tidak ada pemeriksaan. Gunakan kerangka waktu yang pembukaan dan penutupannya sejajar dengan batas jendela. Misalnya pada M15, dengan jendela 09:00–12:00 dan candle 12:00 tersedia, arsiran berakhir pada 12:00 dan kondisi aktif pada candle pertama di luar jendela itu. “Once Per Bar” dapat memberi tahu pada pembaruan pertamanya; “Once Per Bar Close” hanya saat penutupannya. Jendela yang bertumpang tindih membentuk gabungan: berakhirnya jendela pertama tidak menutup yang kedua. Buat ulang peringatan setelah mengubah waktu. Jangan mengharapkan: pemblokiran order — indikator mengingatkan rencana; daftar periksa pratransaksi Anda yang menegakkannya.

Indikator Pine Script v6 pada grafik EUR/USD TradingView: sorotan jendela sesi rencana (S6) dan panel bawah rentang harian vs ATR dengan tingkat peringatan 80% (S5)
Indikator Pine Script v6 pada grafik EUR/USD TradingView: sorotan jendela sesi rencana (S6) dan panel bawah rentang harian vs ATR dengan tingkat peringatan 80% (S5)
Seluruh paket dalam satu berkas dibuat secara lokal di peramban Anda

Enam cuplikan beserta judul dalam satu berkas teks, petunjuk penyiapan dan versi pustaka, diikuti CSV uji serta keluaran yang diharapkan — untuk diarsipkan bersama jurnal atau disimpan dalam repositori Anda. TXT menggabungkan bahasa berbeda: jangan jalankan seluruh berkas sebagai Python. Pilih S1+S2+S3, S4 mandiri, atau satu indikator Pine.

Keluaran S3 aktual untuk lima transaksi uji: jendela editor kode dengan skrip Python dan metrik terminal di samping kurva ekuitas serta grafik drawdown yang dihasilkan
Keluaran S3 aktual untuk lima transaksi uji: jendela editor kode dengan skrip Python dan metrik terminal di samping kurva ekuitas serta grafik drawdown yang dihasilkan

Tanya jawab

Mengapa pengenal kode tetap berbahasa Inggris, sedangkan komentar dari sumber kursus Polandia dilokalkan?

Pada sumber kursus Polandia, komentar berbahasa Inggris mengikuti nama API, dokumentasi pandas dan Pine Script serta pesan kesalahan; lokakarya AI menggunakan pendekatan itu untuk prompt yang melibatkan kode (lampiran H, P3). Dalam paket versi Indonesia ini, pengenal dan API tetap berbahasa Inggris, sedangkan komentar serta label keluaran untuk pembaca diterjemahkan. Penjelasan di sekitar cuplikan menerangkan APA yang dilakukan kode dan kapan digunakan; komentar di dalamnya menjelaskan BAGAIMANA. Pengecualian dalam versi Polandia prompt P5 (komentar Polandia pada kode yang dibuat khusus untuk Anda) berkaitan dengan kode sekali pakai, bukan aturan asli pustaka.

Bagaimana menjalankan cuplikan Python?

11.8 memandu Anda langkah demi langkah untuk menginstal Python, lingkungan dan paket — pandas dan matplotlib sudah cukup untuk paket ini. Gabungkan cuplikan S1–S3 dalam satu berkas, analiza_dziennika.py (S1 di paling atas), lalu jalankan python analiza_dziennika.py di direktori berisi ekspor jurnal; S4 berjalan mandiri. Jalur relatif CSV_PATH mengacu pada direktori kerja terminal. Mulai dengan CSV uji dan analisis salinan jurnal sungguhan Anda; equity_curve.png ditimpa pada proses berikutnya. Jika terminal menampilkan kesalahan yang belum Anda pahami, tempel kode dan pesan lengkapnya ke peninjauan P3 dari lampiran H, alih-alih menebak.

Bolehkah saya mengubah dan memperluas cuplikan ini?

Memang itulah tujuannya — ini paket AWAL. Perluasan yang wajar antara lain metrik per setup (kelompokkan menurut kolom setup sebelum memanggil fungsi S2), penyaringan menurut error_code untuk menghitung ekspektasi pelaksanaan tanpa kesalahan, atau kurva terpisah untuk kelas A+/B. Dua aturan perubahan: tinjau setiap perubahan melalui P3 (terutama yang melibatkan request.security dalam Pine), serta beri versi pada definisi metrik dan aturan strategi. Perbaiki kesalahan perhitungan segera dan hitung ulang periode pembanding dengan metode sama; dokumentasikan perubahan aturan strategi dalam protokol pengujian (lampiran N) dan periksa menggunakan data baru. Berakhirnya triwulan sendiri tidak membenarkan perubahan, dan berada di tengah triwulan tidak membenarkan mempertahankan kesalahan.

Mengapa paket tidak memuat strategi atau EA?

Dengan alasan sama mengapa lokakarya AI tidak menghasilkan sinyal: strategi siap pakai dari internet memiliki keunggulan tidak diketahui, asumsi tidak diketahui dan tidak terkait rencana Anda — sedangkan EA yang menjalankan logika orang lain pada akun Anda berarti mendelegasikan keputusan yang seluruh biayanya Anda tanggung. Kode dalam kursus memperkuat proses (pengukuran, visualisasi dan kepatuhan pada rencana), bukan menggantikannya. Jika ingin mengotomatiskan strategi Anda sendiri yang telah diuji, jalurnya melalui backtest dalam 15.4, protokol pengujian lampiran N, dan peninjauan kode P3, bukan menyalin skrip “menguntungkan”.

Skenario ilustratif: membulatkan “ke angka yang lebih rapi”. Penghitung menghasilkan 0.07 lot; trader mengeklik 0.1 — “beda kecil saja, dan angkanya bulat”. Penyesuaian kecil itu dalam angka: risiko +43% pada setiap transaksi, secara sistematis sepanjang triwulan; batas harian −2R secara efektif menjadi −2.9R dan drawdown kurva ekuitas melampaui rencana, meski trader menganggap tiap penyesuaian hanya kosmetis. Karena itu digunakan math.floor dalam S4, bukan round: mesin membulatkan ke bawah tanpa berdebat; manusia membulatkan ke atas dengan pembenaran.
Skenario ilustratif: kelompok kerugian yang hanya terlihat pada kurva. Metrik triwulan tampak masuk akal: ekspektasi positif, drawdown dalam rentang perkiraan. Hanya grafik S3 yang memperlihatkan bahwa seluruh drawdown berasal dari satu minggu — dan filter waktu masuk yang ditambahkan ke S2 memperjelasnya: setiap kerugian antara 21:00 dan 23:00 UTC (jurnal lampiran F mencatat waktu dalam UTC), saat spread lebar, “karena saya akan bertahan sedikit lebih lama”. Apakah jam itu bertepatan dengan rollover bergantung pada broker dan zona waktu — periksa spesifikasinya alih-alih berasumsi. Angka agregat meratakan keadaan; kurva memperlihatkan KAPAN sistem merugi. Satu baris dalam rencana (tanpa entri baru setelah 21:00 UTC, atau 23:00 CEST / 22:00 CET — catat dalam waktu Europe/Warsaw pada rencana dan jendela S6) dalam skenario ini merupakan kesimpulan untuk diuji selama minggu berikutnya, bukan perbaikan hasil siap pakai — dan kesimpulan semacam inilah yang hendak didukung paket ini.
Pernyataan batasan: halaman ini bersifat edukatif dan bukan nasihat investasi. Cuplikan kode adalah alat analisis yang Anda jalankan atas tanggung jawab sendiri: baca kode sebelum menggunakannya, verifikasi hasil (misalnya dengan kalkulator lampiran C), dan ambil keputusan trading sesuai rencana Anda sendiri. Cuplikan Python (S1–S4) tidak terhubung ke internet atau akun Anda — membaca berkas lokal dan menggambar grafik; indikator Pine (S5–S6) berjalan pada platform grafik menggunakan datanya. Tidak ada cuplikan yang menghasilkan sinyal beli/jual atau memasang order.

Sumber

  1. Dokumentasi: pandas 3.0.5 dan matplotlib 3.11.1 (fungsi dalam S1–S3); Python: pembacaan CSV dan aritmetika Fraction (validasi format dan perhitungan eksak jumlah langkah lot) — cuplikan S1–S4 dijalankan pada data sintetis tanggal 17 September 2026 menggunakan Python 3.12.10; TradingView Pine Script v6: kerangka waktu dan data lain, sesi, waktu dan zona waktu, peringatan (request.security dengan parameter lookahead dan repainting, hline, input.session, alertcondition) — kode S5–S6 ditinjau berdasarkan dokumentasi, tanpa kompilasi.
  2. Internal: 11.8 Python dalam trading (lingkungan), 15.9 (definisi metrik dan format jurnal), 15.10 (prinsip penarikan kesimpulan) — paket ini merupakan penerapan praktis ringkas dari materi tersebut.
  3. Van K. Tharp, Trade Your Way to Financial Freedom, edisi ke-2, McGraw-Hill 2007 — kelipatan R dan ekspektasi, bahasa bersama S2, lembar F dan prompt P5.

Jarosław Wasiński

Pemimpin redaksi, ForexMechanics.com • Analis makro dan FX

Analis independen dan profesional dengan lebih dari 20 tahun pengalaman di pasar keuangan. Aktif di Forex sejak 2004, berfokus pada analisis fundamental, struktur pasar OTC, dan manajemen risiko modal yang disiplin.

  • Pendiri dan pemimpin redaksi MyBank.pl, menyediakan edukasi keuangan tepercaya sejak 2004.
  • Penulis ratusan komentar pasar mendalam, analisis struktural, dan materi edukasi untuk trader dan investor.
  • Pendukung transparansi pasar, mempromosikan edukasi berbasis data konkret dan laporan institusional (BIS, ESMA, CFTC).

Seluruh konten bersifat edukasi dan informasi semata – bukan merupakan saran investasi atau rekomendasi apa pun. Ingat: trading dengan leverage (Forex/CFD) memiliki risiko tinggi kehilangan modal dengan cepat.