Python dalam perdagangan – analisis data, otomatisasi, dan riset

Python membantu menata data pasar, menganalisis jurnal perdagangan, dan mengulang perhitungan tanpa menyalin rumus secara manual. Sangat berguna ketika lembar kerja menjadi terlalu kecil atau sulit dirawat. Artikel ini memandu Anda dari penyiapan lingkungan hingga skrip yang memuat data, memeriksa mutu, dan menyimpan hasil. Kami juga menjelaskan perbedaan riset dalam notebook dengan perangkat lunak yang terhubung ke akun perdagangan serta pengaman yang diperlukan integrasi itu.

Python mendukung analisis data berulang dan pemeriksaan hasil; kode tidak menggantikan mutu data atau metodologi pengujian yang benar.
Inti pembahasan dalam 60 detik
  • Python untuk riset dan Python untuk eksekusi memiliki tingkat tanggung jawab berbeda – notebook penghitung statistik boleh gagal tanpa konsekuensi. Kode yang menyentuh akun wajib memiliki batas, log, timeout, mode uji, dan penghentian terkendali
  • Lingkungan harus dapat direproduksi – versi Python, lingkungan virtual terisolasi, versi pustaka dibekukan, sumber data, zona waktu, dan rentang tanggal. Tanpanya, “berfungsi di komputer saya” bukan hasil, melainkan anekdot
  • pandas dan numpy menggantikan lembar kerja dalam analisis OHLCV – resampling M1 ke H4, rata-rata bergerak, drawdown, korelasi antarpasangan. Lembar kerja berhenti pada 1.048.576 baris; data tick dimulai jauh di atasnya
  • Kunci dan kata sandi tidak pernah masuk kode – hanya variabel lingkungan atau berkas konfigurasi di luar repositori. Kunci yang diketik dalam skrip masuk riwayat Git dan tetap ada setelah setiap “penghapusan”
  • API bukan akses tanpa batas – ada autentikasi, batas laju permintaan, dan kode kesalahan; lingkungan uji kadang — tetapi tidak selalu — dipisahkan ke host sendiri. Kode tanpa penanganan HTTP 429 dan timeout cepat atau lambat akan terputus pada saat terburuk
  • Look-ahead hanyalah satu pergeseran dalam kode – posisi yang diputuskan pada penutupan candle baru berlaku untuk imbal hasil candle berikutnya. Mengalikan sinyal dengan imbal hasil pada baris yang sama menghasilkan hasil yang tidak pernah diberikan pasar
  • Kode yang belum dijalankan adalah kode yang belum diperiksa – dapat dikompilasi dan “tampak benar” tidak sama dengan dieksekusi pada data. Enam blok yang dapat dijalankan telah diperiksa secara offline dengan data serta mock yang diungkapkan. Blok keluaran menunjukkan hasil aktual pengujian tersebut; perintah instalasi menjelaskan penyiapan lingkungan, sementara pengiriman order tetap berupa sketsa struktural yang secara eksplisit tidak dapat dijalankan

1. Di mana Python benar-benar membantu, dan di mana hanya tampak sebagai solusi

Lembar kerja mampu menangani jurnal transaksi selama masih dalam batas kesabaran Anda — batas itu bergantung pada komputer, jumlah rumus, dan susunan lembar sehingga tidak dapat digambarkan dengan angka bulat jumlah transaksi. Namun satu batas tegas tidak bergantung pada apa pun karena tertulis dalam spesifikasi format berkas: satu lembar Excel memuat 1.048.576 baris dan 16.384 kolom [16]. Riwayat tick satu tahun untuk satu pasangan mata uang berlipat kali angka itu — tidak ada trik yang bisa mengatasinya.

Python bukan keajaiban. Ia menyelesaikan empat persoalan konkret yang tidak dapat ditangani lembar kerja atau membutuhkan berjam-jam:

  • Skala data – dibatasi memori komputer dan format penyimpanan, bukan jumlah baris lembar kerja
  • Keterulangan – skrip yang sama dijalankan besok pada data sama memberikan hasil sama. Lembar kerja dengan rumus yang ditarik manual tidak menjamin hal demikian
  • Mengubah interval tanpa pekerjaan manual – agregasi M1 menjadi H4 dengan OHLCV tetap terjaga hanya memerlukan satu pemanggilan resample, bukan kolom rumus pembantu
  • Mengotomatiskan pengambilan dan penataan data – skrip terjadwal mengunduh, memeriksa, dan menyimpan data tanpa kehadiran Anda

Itu saja. Python tidak memberikan keunggulan pasar, tidak memperbaiki mutu strategi, dan tidak mengubah kondisi eksekusi broker. Ia memperpendek jarak antara pertanyaan dan jawaban — justru karena itu, Anda lebih cepat tahu suatu gagasan tidak bekerja.

Tabel perbandingan lembar kerja dan Python menurut lima kriteria kerja
Pilih sesuai skala dan keterulangan tugas; nilai mutu strategi terpisah.

Apa yang tidak dilakukan Python. Python tidak cocok untuk perdagangan berlatensi sangat rendah — mikrodetik menentukan dan bahasa terinterpretasi dengan garbage collector merupakan pilihan buruk. Tetapi “Python terlalu lambat untuk perdagangan” sama kelirunya: bagi strategi yang masuk pada pembukaan candle H1 berikutnya, waktu komputasi Python dapat diabaikan dibandingkan waktu perjalanan order melalui jaringan dan server broker.

Percepatan vektorisasi berasal dari mekanisme, bukan keajaiban: operasi satu kolom penuh berjalan dalam kode numpy terkompilasi, bukan loop interpreter. Orde besar selisihnya bergantung pada data, jenis operasi, dan perangkat keras — jika ingin mengutip angka tertentu, ukur di komputer sendiri dengan timeit dari pustaka standar [4] dan catat bersama versi pustaka. Mengutip tolok ukur orang lain tanpa lingkungan bukan pengukuran.

Perbandingan perangkat analitis trader:

FiturExcelPythonPine ScriptMQL4/MQL5
Batas data1.048.576 baris per lembarDibatasi memori komputerBergantung paket TradingViewRiwayat yang disediakan broker
Lokasi eksekusi kodeLokalLokal, di server, atau di cloudDi dalam lingkungan TradingViewDi dalam terminal MetaTrader
Akses akunTidak adaHanya melalui API broker atau paket perantaraTidak ada – alert dapat memicu rangkaian lanjutanBawaan di dalam terminal
Sumber dataApa pun yang Anda muatApa pun, tetapi harus disebutkanFeed TradingViewFeed broker
ReproduksibilitasRendah (rumus manual)Tinggi jika versi dibekukanSedang (versi Pine dan data platform)Sedang (build terminal dan data broker)
Kurva belajarRendahSedangRendah hingga sedangSedang hingga tinggi

Perbedaan pada baris terakhir tabel lebih penting daripada kelihatannya. Pine Script dieksekusi di lingkungan TradingView, MQL di terminal MetaTrader, dan Python di mana pun Anda menjalankannya. Kebebasan itu berarti Anda bertanggung jawab atas lingkungan: versi pustaka, zona waktu sistem, ketersediaan, dan apakah skrip memang berhak menyentuh akun.

Batas artikel ini. Ini bukan kursus Python atau backtesting. Metodologi pengujian — pembagian sampel pelatihan dan validasi, forward testing, model biaya — dibahas dalam artikel Akun demo, backtesting, dan forward testing. Kode di dalam terminal dan cara menjaganya tetap berjalan di server dibahas dalam Otomatisasi perdagangan: EA, skrip, dan VPS. Model bahasa dan keterbatasannya — AI dan LLM dalam perdagangan. Di sini kami hanya membahas Python sebagai lapisan data, komputasi, dan integrasi.
Anda tidak harus meninggalkan lembar kerja. Ekspor hasil Python ke CSV lalu buka pada lembar kerja, atau sebaliknya: simpan jurnal perdagangan di lembar kerja dan muat ke pandas untuk analisis. Pustaka openpyxl memungkinkan baca-tulis berkas .xlsx langsung dari Python; rilis terbarunya bertanggal Juni 2024 (3.1.5) [13] dan pada kasus khusus ini bukan pertanda peringatan — format .xlsx sudah mapan dan pustaka tidak bergantung pada NumPy maupun pandas, jadi aturan bagian 2 tidak menyingkirkannya, hanya mengharuskan Anda mengetahui keadaan itu [STATUS: 2026-08-22]. Python memperluas lembar kerja di tempat batasnya berakhir.

2. Lingkungan yang dapat direproduksi: versi, venv, requirements

Alasan sangat umum skrip orang lain “tidak bekerja” bukan pada kode, melainkan pembuat menjalankannya dengan versi pustaka berbeda tanpa mencatatnya. Dalam analisis data pasar, ini bukan ketidaknyamanan — hasilnya tidak sah karena tidak dapat direproduksi.

Lingkungan dapat direproduksi jika Anda mampu menyebut enam hal: versi Python, metode isolasi, versi dependensi beku, sumber data beserta rentang tanggal, zona waktu, dan random seed di setiap tempat yang hasilnya bergantung pada keacakan. Unsur yang hilang mengubah hasil menjadi cerita, bukan pengukuran.

Lingkungan virtual bukan pilihan tambahan. Instalasi pustaka “global” menyebabkan pembaruan satu proyek merusak proyek lain. Isolasi gratis, hanya memerlukan belasan detik, dan menggunakan modul venv dari pustaka standar [4]:

# Windows, PowerShell
py -3.12 -m venv .venv
.venv\Scripts\Activate.ps1

# macOS / Linux
python3.12 -m venv .venv
source .venv/bin/activate

# berlaku untuk kedua sistem
python -m pip install --upgrade pip
python -m pip install pandas numpy matplotlib requests python-dotenv
python -m pip freeze > requirements.txt

Baris terakhir paling sering dilewatkan. pip freeze mencatat versi tepat semua yang benar-benar terpasang — termasuk dependensi yang tidak Anda ketahui. Simpan berkas itu dalam repositori bersama kode. Pembuatan ulang lingkungan di komputer lain kemudian menjadi python -m pip install -r requirements.txt, bukan tebak-tebakan.

Lingkungan yang digunakan untuk verifikasi offline saat ini pada contoh komputasi bagian 4 dan 9: Windows 11, Python 3.12.10, pandas 3.0.5, dan NumPy 2.5.3, diperiksa pada 8 Oktober 2026. Daftar berikut mencatat paket yang terkait perhitungan tersebut; ini inventaris terbatas, bukan ekspor seluruh paket yang terpasang pada komputer[1][3].

# Versi dependensi untuk verifikasi komputasi lokal
numpy==2.5.3
pandas==3.0.5
python-dateutil==2.9.0.post0
six==1.17.0
tzdata==2026.4

# interpreter
Python 3.12.10

Perhatikan bahwa pandas juga menggunakan paket lain: deskripsi lingkungan tidak berhenti pada satu versi pustaka. Dependensi dapat menyebabkan perbedaan antarkomputer. Perintah instalasi di atas mengilustrasikan prosedur penyiapan proyek, bukan catatan instalasi selama audit ini. Buat berkas lengkap requirements.txt di lingkungan sendiri menggunakan python -m pip freeze dan simpan bersama kode.

Berkas ini juga bukan deskripsi lengkap lingkungan seluruh artikel, melainkan hanya lingkungan bagian komputasi, dan harus dibaca demikian. Fragmen pengujian penanganan kesalahan HTTP pada bagian 7 dijalankan dalam lingkungan dengan tambahan requests 2.34.2, sedangkan pengujian gerbang keselamatan bagian 8 tidak memerlukan paket di luar pustaka standar. Versi matplotlib dan python-dotenv tidak diberikan karena tidak digunakan dalam contoh yang benar-benar dijalankan — mencantumkannya “agar rapi” berarti menulis angka yang tidak diperiksa siapa pun. Jika membangun lingkungan untuk keseluruhan, perlakukan perintah instalasi di atas sebagai titik awal dan bekukan versi sendiri melalui pip freeze; hanya berkas dependensi itulah yang bermakna bagi hasil Anda.

Notebook riset konseptual berisi data UTC, pemeriksaan nilai hilang, dan ekspor hasil tanpa pengiriman order.
Notebook mendukung analisis data yang dapat direproduksi. Keluaran harus divalidasi sebelum digunakan dalam sistem eksekusi terpisah.

Notebook atau berkas .py? Keduanya, tetapi untuk tujuan berbeda. Jupyter baik ketika Anda belum tahu apa yang dicari: jalankan sel, lihat hasil, ubah parameter. Namun satu sifat membuatnya tidak layak sebagai tempat kode eksekusi — keadaan variabel bergantung pada urutan sel dijalankan, bukan urutan tertulis dalam berkas. Notebook yang selnya dieksekusi dengan urutan 1–3–2 tampak sama dengan yang berurutan. Kode yang menyentuh akun harus berupa berkas biasa .py yang dijalankan dari atas ke bawah, dengan pengujian dan log.

Anaconda dan Google Colab merupakan jalan pintas dengan biaya tersendiri. Anaconda memasang banyak paket sekaligus dan memiliki pengelola dependensi sendiri; memulai lebih mudah, tetapi mendeskripsikan lingkungan tepat menjadi sulit bila mencampur conda dengan pip. Colab berjalan di browser tanpa instalasi dan baik untuk belajar, tetapi lingkungan dibuat ulang pada setiap sesi dan versi pustaka berubah tanpa keterlibatan Anda. Untuk materi yang harus dapat diulang setahun lagi, keduanya memerlukan disiplin tambahan.

Pustaka untuk memulai – empat butir, bukan empat puluh (ada lima paket: butir pertama sepasang):

  • pandas dan numpy – struktur data dan komputasi
  • matplotlib – grafik
  • requests – komunikasi HTTP dengan API
  • python-dotenv – memuat konfigurasi dari berkas di luar repositori

Tambahkan indikator teknikal saat benar-benar diperlukan. Satu koreksi untuk panduan lama: TA-Lib menerbitkan wheel biner resmi dengan pustaka C di PyPI; untuk kombinasi OS, arsitektur, dan Python yang didukung, perintah sederhana pip install TA-Lib tidak memerlukan unduhan tidak resmi ataupun kompilasi sendiri[9]. Periksa wheel tertentu sebelum instalasi; ini bukan janji bekerja pada setiap komputer. Metadata yang diperiksa pada 8 Oktober 2026 mencantumkan TA-Lib 0.8.1 dan pandas-ta masih 0.4.71b0, dirilis September 2025[13]. Paket terakhir mengunci numba==0.61.2, jadi memeriksa syarat pandas saja tidak cukup. Uji instalasi historis pada Python 3.14.3, tertanggal 22 Agustus 2026, gagal pada dependensi tersebut dengan pesan only versions >=3.10,<3.14 are supported. Pengamatan itu tidak otomatis berlaku bagi contoh saat ini yang berjalan pada Python 3.12.10. Audit sekarang membaca metadata PyPI; tidak memasang atau menjalankan TA-Lib maupun pandas-ta. Jadi aturan pemeriksaan di samping terlalu sempit: rilis baru dan syarat pandas yang kompatibel tidak membuktikan seluruh dependensi dapat dipasang dan berjalan benar. Periksa pula pustaka terkompilasi yang dikunci, ketersediaan wheel untuk interpreter, dan pengujian data kecil. Metadata dua rilis terbaru tidak menggambarkan seluruh riwayat proyek; nilai jeda sebelumnya di repositorinya.

Sebelum menambahkan pustaka, periksa tanggal rilis terakhirnya. Proyek yang lama tidak dikembangkan cepat atau lambat menjauh dari versi NumPy atau pandas baru — lalu Anda harus membekukan versi lama dalam lingkungan terpisah atau menulis ulang kode. Tanggal rilis terakhir terlihat pada halaman paket PyPI [13] dan lebih bermakna daripada jumlah bintang GitHub. Ini terutama berlaku untuk framework backtesting lama.

Random seed. Di setiap tempat hasil bergantung pada keacakan — pembagian sampel, bootstrap, simulasi, inisialisasi model — tetapkan dan catat seed. Dalam numpy: rng = np.random.default_rng(20260822) [3], lalu gunakan objek itu saja. Tanpanya, dua kali menjalankan skrip yang sama menghasilkan dua hasil berbeda dan tidak jelas hasil mana yang Anda jelaskan.

3. pandas dan numpy: data OHLCV, indeks waktu, zona waktu

numpy menangani komputasi pada larik angka, pandas menangani struktur: DataFrame adalah larik dengan kolom bernama dan indeks, biasanya indeks waktu. Dalam data pasar, indeks waktu lebih penting daripada perhitungannya karena menentukan apakah Anda benar-benar menghitung hal yang dimaksud.

Memuat – pd.read_csv() mampu menangani berkas berjuta baris, mengenali pemisah, dan mengurai tanggal jika Anda menunjuk kolomnya. Namun secara default tidak mengetahui zona waktu; itulah hal pertama yang harus diputuskan.

Mengubah interval – agregasi M1 ke H4 dengan OHLCV tetap terjaga memerlukan satu pemanggilan: df.resample('4h').agg({'open':'first', 'high':'max', 'low':'min', 'close':'last', 'tick_volume':'sum'}). Perhatikan dua argumen yang sering tidak diatur tetapi menentukan interpretasi: label menentukan apakah label jendela memakai awal atau akhir, dan closed – sisi interval mana yang termasuk jendela. Perilaku default untuk sebagian besar frekuensi menempatkan label pada sisi kiri [1], berarti stempel waktu candle adalah saat dibuka; dalam pandas 3.0.5, resample('4h') pada delapan candle per jam sejak tengah malam menghasilkan label 00:00 dan 04:00. Jika konvensi sinyal mengasumsikan penutupan tetapi data dilabeli pembukaan, seluruh hasil bergeser satu interval tanpa Anda sadari.

Statistik bergerak – df['close'].rolling(20).mean() menghasilkan rata-rata bergerak, .rolling(20).std() simpangan yang digunakan untuk Bollinger Bands, .ewm(span=20).mean() rata-rata eksponensial [1]. Semuanya dihitung hanya dari data masa lalu relatif terhadap baris saat ini — sifat itulah yang mendasari ketepatan seluruh bagian lain.

Penyaringan – kondisi majemuk ditulis sekali: df[(df['sma20'] > df['sma50']) & (df['rsi'] < 30)]. Tanda kurung pada setiap kondisi wajib, sebab & mengikat lebih kuat daripada perbandingan dalam Python — tanpa kurung terjadi kesalahan, bukan jawaban keliru diam-diam; untuk satu hal ini, itu kabar baik.

Tabel konsisten empat candle OHLCV sintetis dan pemeriksaan data
Format DataFrame tidak membuktikan mutu data; periksa nilai, waktu, dan definisi volume.

Zona waktu adalah keputusan, bukan detail. Data broker biasanya memakai stempel waktu server broker, data penyedia eksternal umumnya UTC, sedangkan sistem operasi dapat memakai waktu lokal dengan perubahan jam menurut zona pilihan. Jika ketiga jam bercampur, analisis “sesi London” justru mempelajari hal lain.

Aturan praktis: tetapkan zona sumber terlebih dahulu dan lakukan perhitungan dalam UTC. Untuk stempel waktu dengan offset eksplisit atau Z, gunakan pd.to_datetime(column, utc=True). Untuk stempel waktu server tanpa offset, uraikan terlebih dahulu, tetapkan zona terkonfirmasi dengan tz_localize() sambil menangani jam ambigu dan tidak ada, lalu terapkan tz_convert("UTC"). Hanya menggunakan utc=True memperlakukan nilai tanpa zona sebagai UTC; tidak menyimpulkan zona broker. Konversi ke Europe/London hanya untuk penyajian[1]. Indeks berzona eksplisit dan sumber data tercatat memungkinkan penggabungan rangkaian secara konsisten.

Waktu server broker adalah nilai tersendiri. Banyak broker menjalankan server pada zona dengan offset UTC, yang kadang berubah bersamaan dengan pergantian jam AS atau Eropa. Jangan mengasumsikan offset dari ingatan: periksa spesifikasi broker dan catat dalam konfigurasi proyek sebagai parameter, bukan angka yang diketik dalam kode. [FITUR BERGANTUNG PADA BROKER/AKUN]

Data yang hilang. Riwayat OHLCV memiliki celah: akhir pekan, hari libur, jeda teknis, instrumen tidak dikuotasikan. pandas menawarkan df.ffill(), yang mengisi celah dengan nilai terakhir — biasanya gagasan buruk pada data harga karena menciptakan candle bervolatilitas nol dan menekan simpangan baku. Lebih baik hapus periode yang hilang dan hitung jumlahnya.

Koreksi untuk materi lama. Bentuk df.fillna(method='ffill'), lazim pada panduan beberapa tahun lalu, telah ditarik: parameter method pada fillna() ditandai usang dalam pandas 2.1 [2] dan dihapus pada pandas 3.0. Ini perbedaan praktis: bentuk lama tidak lagi memberi peringatan, tetapi menghentikan program dengan pesan TypeError: NDFrame.fillna() got an unexpected keyword argument 'method'. Bentuk yang benar adalah df.ffill() dan df.bfill(). Signature dalam dokumentasi pandas 3.0.5 tidak lagi memuat parameter itu — DataFrame.fillna(value, *, axis=None, inplace=False, limit=None) [1]. Signature dan teks pesan diperiksa dengan menjalankannya pada pandas 3.0.5, bukan dengan membaca dokumentasi. [STATUS: 2026-08-22]
Tick volume bukan volume pasar. Di pasar mata uang terdesentralisasi, klien ritel tidak memperoleh volume perputaran terkonsolidasi — yang diperoleh adalah jumlah perubahan kuotasi feed broker tertentu. Dapat digunakan hati-hati sebagai proksi aktivitas, tetapi kolom bernama “volume” dalam berkas bukan volume pasar dan tidak dapat dibandingkan antarbroker.

Beberapa instrumen sekaligus. Untuk analisis portofolio mata uang, pandas menawarkan indeks hierarkis (MultiIndex): satu DataFrame memuat banyak pasangan dengan indeks pasangan dan waktu. Ini memungkinkan korelasi antarinstrumen pada sumbu waktu bersama — asalkan semua rangkaian terlebih dahulu disamakan zona dan kisi stempel waktunya. Tanpanya, yang dihitung adalah korelasi data yang bergeser satu sama lain.

Menyimpan hasil. df.to_csv() mudah dibaca manusia dan universal. df.to_parquet() menghasilkan berkas lebih kecil dan pembacaan lebih cepat, sekaligus mempertahankan tipe kolom serta zona waktu indeks [1] – CSV menulis semuanya sebagai teks sehingga perlu diurai ulang saat dimuat. Untuk data kerja bervolume besar, Parquet lebih praktis tetapi memerlukan mesin penulisan tambahan; tambahkan ke requirements.txt.

Konteks makro dalam Python. Laporan positioning dan data kalender dapat diambil dengan skrip seperti harga — dengan disiplin zona waktu serta waktu publikasi yang sama. Namun interpretasinya merupakan bagian lain kursus: Laporan COT dan positioning.

4. Contoh minimal: CSV → UTC → imbal hasil → pemeriksaan celah → simpan

Skrip pertama sebaiknya tidak mengirim order, bahkan tidak terhubung ke broker. Lakukan hal berguna, dapat diperiksa, dan sepenuhnya aman: muat berkas, benahi waktu, hitung imbal hasil, nyatakan masalah data dengan jelas, lalu simpan hasil.

Asumsi. Berkas CSV dengan kolom time,open,high,low,close,tick_volume. Kolom time berformat ISO 8601 dengan penanda zona (Z berarti UTC). Interval dinyatakan per jam. Berkas pengujian sengaja memuat tiga cacat yang terjadi pada ekspor nyata: satu stempel waktu duplikat, satu jam hilang, dan satu sel volume kosong. Keenam baris ditempel langsung dalam kode agar Anda dapat menjalankan contoh dan memperoleh hasil persis sama tanpa mengunduh berkas — hasil yang tidak dapat direproduksi bukan hasil. Lingkungan: Python 3.12.10, pandas 3.0.5, numpy 2.5.3, Windows 11, [STATUS: 2026-10-08].

"""Contoh minimal: CSV -> UTC -> imbal hasil log -> pemeriksaan celah -> simpan."""
import io
from pathlib import Path

import numpy as np
import pandas as pd

# Data dimuat langsung dalam kode agar hasil dapat direproduksi tanpa mengunduh berkas.
# Enam baris dengan tiga cacat disengaja: stempel waktu 01:00 duplikat,
# jam 03:00 hilang dan sel volume kosong pada 02:00.
CSV_DATA = """time,open,high,low,close,tick_volume
2026-06-01T00:00:00Z,1.08140,1.08170,1.08131,1.08153,812
2026-06-01T01:00:00Z,1.08153,1.08190,1.08150,1.08176,640
2026-06-01T01:00:00Z,1.08153,1.08195,1.08149,1.08182,655
2026-06-01T02:00:00Z,1.08182,1.08188,1.08130,1.08141,
2026-06-01T04:00:00Z,1.08120,1.08125,1.08040,1.08050,903
2026-06-01T05:00:00Z,1.08050,1.08061,1.07980,1.07993,774
"""

OUTPUT = Path("EURUSD_H1_returns.csv")
INTERVAL = "1h"  # frekuensi data masukan yang dinyatakan


def load_data(source) -> pd.DataFrame:
    """Memuat CSV dan menetapkan indeks berzona waktu dalam UTC.

    Sengaja tidak mengurutkan: urutan baris berkas merupakan informasi
    tentang mutu ekspor. Pengurutan dilakukan setelah laporan.
    """
    df = pd.read_csv(source)
    # utc=True memaksakan satu zona; tanpanya pandas dapat mengembalikan indeks tanpa zona
    df["time"] = pd.to_datetime(df["time"], utc=True, format="ISO8601")
    return df.set_index("time")


def quality_report(df: pd.DataFrame, interval: str) -> dict:
    """Penghitung yang harus dilihat sebelum menghitung apa pun."""
    expected = pd.date_range(df.index.min(), df.index.max(), freq=interval, tz="UTC")
    return {
        "rows": int(len(df)),
        "index_duplicates": int(df.index.duplicated().sum()),
        "missing_cells": int(df.isna().sum().sum()),
        "missing_timestamps": int(len(expected.difference(df.index))),
        # Diukur SEBELUM pengurutan: setelah sort_index() pemeriksaan ini akan selalu
        # true dan tidak pernah mendeteksi berkas dengan kronologi kacau.
        "file_chronology_ok": bool(df.index.is_monotonic_increasing),
    }


def add_returns(df: pd.DataFrame) -> pd.DataFrame:
    """Imbal hasil log = ln(close_t / close_(t-1)). Satuan: logaritma natural, tidak berdimensi."""
    df = df[~df.index.duplicated(keep="last")].copy()
    df["log_return"] = np.log(df["close"]).diff()
    return df


def main() -> None:
    df = load_data(io.StringIO(CSV_DATA))  # dalam praktik: load_data(Path("EURUSD_H1.csv"))
    print("pemeriksaan mutu:", quality_report(df, INTERVAL))
    df = add_returns(df.sort_index())
    print("candle setelah deduplikasi:", len(df))
    print("jumlah imbal hasil log:", round(float(df["log_return"].sum()), 6))
    print("simpangan baku imbal hasil log (interval 1h dan 2h):",
          round(float(df["log_return"].std()), 6))
    df.to_csv(OUTPUT)
    print("disimpan:", OUTPUT)


if __name__ == "__main__":
    main()

Keluaran pengujian pada data yang ditempel dalam kode dan lingkungan yang dijelaskan — dapat direproduksi persis:

pemeriksaan mutu: {'rows': 6, 'index_duplicates': 1, 'missing_cells': 1, 'missing_timestamps': 1, 'file_chronology_ok': True}
candle setelah deduplikasi: 5
jumlah imbal hasil log: -0.00148
simpangan baku imbal hasil log (interval 1h dan 2h): 0.000467
disimpan: EURUSD_H1_returns.csv

Interpretasi. Skrip mendeteksi ketiga cacat yang ditanam sebelum menghitung apa pun — itulah seluruh nilainya. Jumlah imbal hasil log menjadi −0,00148; dengan lima candle tersisa setelah deduplikasi — yakni empat interval antaraharga teramati berurutan — berarti kurs turun total sekitar 0,15 persen dalam jendela tersebut. Enam adalah jumlah baris berkas, bukan candle: satu baris duplikat. Simpangan baku 0,000467 merupakan besaran per candle H1 individual, bukan tahunan atau harian; membandingkannya tanpa menyatakan interval tidak masuk akal. Imbal hasil log tidak berdimensi dan aditif sepanjang waktu — karena itu dijumlahkan, bukan dikalikan seperti imbal hasil sederhana.

Keterbatasan. Lima candle mengilustrasikan mekanisme, bukan sampel statistik — tidak ada kesimpulan pasar dari observasi sebanyak itu. Skrip tidak memodelkan spread, komisi, atau slippage karena bukan backtest; ia menghitung imbal hasil harga, bukan hasil akun. Deteksi stempel waktu hilang mengasumsikan data benar-benar berfrekuensi per jam sesuai pernyataan — jika melintasi akhir pekan, banyak stempel waktu “hilang” sepenuhnya normal sehingga ambang alarm harus mengikuti kalender perdagangan. Jebakan lain menyangkut ukurannya: skrip mendeteksi tetapi tidak mengisi celah, sehingga imbal hasil yang melintasi jam hilang mencakup dua interval, bukan satu. Dalam contoh ini satu dari empat imbal hasil demikian, tetapi semuanya tetap dilabeli besaran “per candle H1” — ini penyederhanaan, bukan ukuran presisi. Ada dua jalan yang jujur: indeks ulang data ke kisi jam lengkap dan biarkan nilai celah kosong, atau nyatakan pencampuran horizon sebagai keterbatasan pengukuran.

Mengapa kronologi diperiksa sebelum pengurutan. Jika load_data() diakhiri pemanggilan sort_index(), kolom file_chronology_ok akan selalu true — pemeriksaan dalam laporan tidak dapat mendeteksi apa pun dan berkas berurutan kacau lolos tanpa jejak. Pemeriksaan yang tidak dapat gagal lebih buruk daripada tanpa pemeriksaan karena memberi rasa validasi palsu. Karena itu urutan diukur pada data sebagaimana dimuat dari berkas, dan pengurutan baru dilakukan setelah laporan.

Status verifikasi: fragmen ini dijalankan pada lingkungan yang disebutkan dan keluaran di atas adalah keluaran program sesungguhnya. Pada versi pandas lain, format kamus atau pembulatannya mungkin berbeda.

5. Visualisasi: satu pertanyaan per grafik

Satu angka peringkas hasil menyembunyikan hal terpenting: kapan strategi merugi, berapa lama, dan apakah seluruh hasil bertumpu pada tiga transaksi. Grafik bukan hiasan laporan — melainkan cara menanyakan sesuatu pada data yang tidak dijawab tabel.

matplotlib merupakan pustaka grafik dasar Python: statis dan sintaksnya cukup canggung. plotly menyediakan grafik interaktif — arahkan kursor untuk tanggal dan nilai, perbesar fragmen, ekspor ke HTML. Pada data sangat besar dapat lebih lambat dan memerlukan instalasi terpisah. Untuk eksplorasi notebook, plotly lebih praktis; untuk grafik laporan, matplotlib.

Empat grafik yang patut dikuasai karena masing-masing menjawab pertanyaan berbeda:

  1. Kurva ekuitas – hasil kumulatif sepanjang waktu. Menjawab “apakah dan kapan bertumbuh”, bukan “berapa yang akan saya hasilkan”
  2. Grafik drawdown – jarak dari puncak berjalan setiap saat. Menunjukkan drawdown terdalam serta waktu pemulihan. Selalu nyatakan dihitung dari saldo atau ekuitas berjalan dan dalam persen atau mata uang
  3. Peta panas sesi – hasil menurut jam dan hari dalam seminggu. Memerlukan penetapan zona waktu terlebih dahulu; tanpanya menggambarkan jam yang tidak ada
  4. Distribusi imbal hasil – histogram hasil transaksi individual. Memperlihatkan ekor tebal dan asimetri yang tidak ditunjukkan satu metrik risiko
Grafik ekuitas dan puncak berjalan sintetis dengan drawdown minus sepuluh persen
Drawdown diukur dari puncak berjalan dengan perlakuan arus modal eksplisit.

Untuk grafik candle langsung dari DataFrame tersedia mplfinance, lapisan di atas matplotlib — dengan catatan yang langsung mengikuti aturan bagian 2: rilis terakhir Agustus 2023 dan masih bernomor prarilis (0.12.10b0) [13] [STATUS: 2026-10-08]. Ini tidak otomatis menggugurkannya, tetapi sebelum menambahkan ke requirements.txt, periksa pada data sendiri apakah bekerja dengan versi matplotlib dan pandas Anda — rilis baru tidak akan memeriksanya untuk Anda. Untuk mengubah skrip menjadi dasbor sederhana dengan slider dan dropdown tersedia streamlit, proyek yang dirilis berkala [13], dijalankan dengan streamlit run dashboard.py. Kedua pustaka menghemat waktu, keduanya menambah dependensi dalam requirements.txt, jadi tambahkan dengan sadar.

Aturan praktis: jika tidak dapat menyebut dalam satu kalimat pertanyaan yang dijawab grafik, Anda tidak memerlukannya. Grafik tanpa pertanyaan hanyalah dekorasi, dan dekorasi analisis hasil bisa mahal — menyiratkan kesimpulan yang tidak ada dalam data.

6. Backtesting dalam Python: bagian kode dan bagian metodologi

Di sinilah batas yang mudah terlewat. Python bertanggung jawab atas implementasi pengujian: memuat data, menghitung sinyal, menempatkan hasil pada periode tepat, membebankan biaya, dan menghitung metrik. Ia tidak bertanggung jawab atas metodologi: pembagian sampel, jumlah varian diuji, model eksekusi, penilaian ketahanan hasil. Metodologi dibahas dalam artikel terpisah — Akun demo, backtesting, dan forward testing – tidak ada pustaka lebih baik yang dapat menggantikannya.

pandas saja bukan backtest. Mengalikan kolom sinyal dengan kolom imbal hasil menghasilkan rangkaian hasil, tetapi tidak memodelkan kejadian antara keputusan dan eksekusi: waktu serta harga entri, spread, komisi, poin swap, pengisian parsial, penolakan order. Ini dapat diterima pada tahap penyaringan gagasan — asalkan disebut penyederhanaan, bukan hasil.

Biaya spread tetap dan variabel untuk tiga transaksi sintetis
Menurut asumsi, biayanya USD 3,00 atau USD 8,80; bukan klaim spread tipikal.

Jika ingin melampaui loop sendiri, ekosistem Python memiliki tiga pendekatan perangkat siap pakai. Tabel membandingkan cara kerja, bukan janji kinerja — waktu konkret bergantung pada data, perangkat keras, dan versi; jika perlu, ukur sendiri dan catat lingkungannya.

FiturPendekatan berbasis peristiwa lengkapPendekatan vektorisasiPendekatan berbasis peristiwa minimal
Contoh pustakabacktradervectorbtbacktesting.py
Cara menghitungCandle demi candle, dengan model order dan portofolio sendiriOperasi seluruh larik sekaligusCandle demi candle, model sederhana
Jumlah kode untuk strategi sederhanaBanyak (kelas, pewarisan)Sedikit jika berpikir dalam kolomSedikit
Penggunaan alamiLogika portofolio, banyak instrumen, order kompleksPenyaringan massal varian dan seleksi awalBelajar dan memeriksa cepat apakah gagasan masuk akal
Biaya utamaKurva belajar curamDebugging lebih sulit; kesalahan pergeseran diam-diam mudah terjadiKeterbatasan strategi kompleks
Yang diperiksa sebelum digunakanTanggal rilis terakhir PyPI, kompatibilitas versi pandas dan NumPy Anda, pemodelan biaya serta waktu pengisian
Laju pengembangan ketiga proyek sangat berbeda. Ini bukan detail koleksi: framework yang tertinggal dari rilis pandas dan NumPy cepat atau lambat memaksa pembekuan versi lama dalam lingkungan terpisah. Sebelum memilih, buka halaman PyPI dan periksa tanggal rilis terakhir serta rentang Python yang didukung. Setelah tahu yang dicari, perbedaan ketiganya jauh lebih tegas daripada kesan tabel bersama: backtrader pernah mengalami jeda rilis bertahun-tahun dan rilis terakhirnya April 2023 — ada fork komunitas dengan tingkat perawatan beragam; vectorbt tersedia sebagai proyek terbuka dan versi komersial yang dikembangkan terpisah (rilis terbuka Juli 2026), jadi pastikan versi mana yang dibahas dokumentasi yang dibaca; backtesting.py pernah mengalami jeda rilis bertahun-tahun, tetapi telah mengakhirinya dan merilis versi berikutnya pada Juli 2026 — bentuk lampau penting di sini. Ketiga tanggal berasal dari registri paket [13]. Tidak satu pun otomatis menggugurkan pustaka — ketidakcocokan dengan versi yang Anda gunakanlah yang menggugurkannya. [STATUS: 2026-08-22]

Baris terakhir bukan formalitas. Setiap framework mengasumsikan waktu dan harga pengisian order — sering kali asumsi itu, bukan logika strategi, yang menentukan hasil. Sebelum memercayai pustaka, cari tiga jawaban dalam dokumentasinya: pada harga berapa entri diisi, bagaimana biaya dibebankan, dan apa yang dilakukan pada gap antarcandle.

Dua kurva sintetis dengan batas in-sample dan out-of-sample eksplisit
Pisahkan pemilihan varian dari evaluasi data berikutnya; laporkan setiap percobaan.

Kebebasan kode sekaligus risiko. Tester bawaan platform menetapkan aturan tertentu sehingga melindungi dari beberapa kesalahan. Kode sendiri tidak melindungi apa pun: tidak bertanya apakah data pelatihan dan validasi terpisah, tidak menyadari sinyal memakai harga yang belum ada saat itu, tidak menghitung jumlah varian yang diuji. Seluruh tanggung jawab bergeser ke pembuat — itulah sebabnya bagian berikut membahas hal yang dapat rusak diam-diam dalam kode.

7. API broker: autentikasi, batas, kesalahan, lingkungan uji

“Broker menyediakan API” kadang dibaca sebagai “Saya memiliki akses akun tanpa batas dari skrip”. Bukan demikian. API merupakan endpoint dengan autentikasi, izin, batas laju permintaan, dan kode kesalahan yang ditetapkan. Kadang — tidak selalu — terbagi menjadi lingkungan uji dan produksi; cara pembagiannya harus dibaca dalam dokumentasi, bukan diasumsikan. Sebelum baris pertama, Anda harus mampu menjawab sembilan pertanyaan: bagaimana autentikasinya, izin token, jumlah permintaan per detik, perbedaan uji dan produksi, tipe data keluaran, kode kesalahan, pembatasan regional, kebijakan pemeliharaan koneksi, serta apakah token berhak memasang order.

Arsitektur IPC MT5, TWS atau IB Gateway, dan REST API langsung
Pustaka tidak menghapus kebutuhan terminal atau aplikasi perantara.

Batas itu konkret dan dipublikasikan — tetapi baca batas yang mana. Perbedaan ini hilang dalam kebanyakan panduan, padahal menentukan desain loop. Pada OANDA v20, dua angka berada dalam dua dokumen dan berarti dua hal berbeda. Panduan pengembang memberikan nilai yang diberlakukan: “120 permintaan per detik. Permintaan berlebih menerima kesalahan HTTP 429” untuk REST API, beserta batas dua puluh stream aktif dan dua koneksi baru per detik — semuanya dihitung per alamat IP pengirim [15]. Dokumen praktik terbaik justru memberikan rekomendasi: penyedia menyarankan pembatasan seratus permintaan per detik pada koneksi yang sudah terbentuk dan dua koneksi baru per detik [5]. [STATUS: 2026-08-22]

Jadi seratus per detik adalah ambang yang disarankan, sedangkan seratus dua puluh adalah ambang di atasnya server menjawab kesalahan 429. Kode yang mengasumsikan “100/s adalah maksimum” tidak merugikan, tetapi kode yang menganggapnya batas keras tanpa menangani 429 akan gagal saat pelampauan pertama — sebab batas yang diberlakukan berbeda dari rekomendasi. Itu angka satu penyedia, bukan aturan pasar: broker lain berbeda dan [FITUR BERGANTUNG PADA BROKER/AKUN]. Periksa dokumentasi penyedia sendiri sebelum menulis loop polling.

Koreksi untuk panduan populer — dan versi paragraf ini sebelumnya. Banyak materi menyebarkan angka “sekitar 30 permintaan per detik” untuk OANDA. Angka itu usang. Kekeliruan lebih umum dan sulit diketahui adalah mengutip rekomendasi 100 permintaan per detik sebagai maksimum. Artikel ini juga melakukannya sebelum koreksi. Rekomendasi dan batas yang diberlakukan merupakan dua besaran berbeda dari dua dokumen penyedia yang sama [5] [15], dan kode dengan batas tercampur membuang throughput atau terblokir tanpa penanganan. Verifikasi batas dalam dokumentasi, bukan tutorial — periksa apakah angka yang dibaca merupakan saran atau ambang pemutusan.

Lingkungan uji adalah host berbeda, bukan mode berbeda. Pada OANDA, pembagiannya terlihat dari alamat: api-fxpractice.oanda.com untuk lingkungan latihan dan api-fxtrade.oanda.com untuk produksi, dengan pasangan alamat serupa bagi stream data [15] [STATUS: 2026-08-22]. Ini kabar baik karena kesalahan dapat ditangkap kode: host produksi diberi kondisi tambahan. Lebih buruk jika penyedia memakai satu alamat dan hanya membedakan token — saat itu satu-satunya pembatas adalah konfigurasi Anda.

Empat jalur lazim menghubungkan Python ke akun — berbeda arsitektur, bukan tingkat “kecanggihan”:

SolusiLokasi koneksi sebenarnyaSyaratCatatan
OANDA v20 (REST + streaming)Skrip → server broker, langsung melalui HTTPAkun broker tersebut; host latihan dan produksi terpisahDokumentasi publik, batas eksplisit. Ini API satu penyedia, bukan standar industri
Interactive Brokers (TWS / IB Gateway)Skrip → aplikasi perantara lokal → server brokerMemerlukan TWS atau IB Gateway berjalan; model asinkronMenutup aplikasi perantara memutus koneksi skrip. Pustaka wrapper memudahkan penanganan, tetapi tidak menghapus ketergantungan itu. Perhatikan namanya: ib_insync yang sering dikutip belum memiliki rilis baru sejak Juli 2023 dan bergantung pada rilis TWS API lama; kelanjutan yang aktif dirawat adalah ib_async [13] [STATUS: 2026-10-08]
Paket MetaTrader5 resmiSkrip → terminal MT5 terpasang di komputer sama → server brokerTerminal MT5 harus terpasang di komputer yang sama; pemanggilan initialize() menjalankannya jika belum aktif dan memakai kredensial tersimpan di terminal atau diberikan dalam pemanggilan [18]; integrasi tersedia sejak build 2085 (2019) [7]Ini komunikasi dengan terminal, bukan API broker independen [6]. Ketergantungan terminal merupakan sifat, bukan cacat. Paket resmi PyPI hanya dapat diinstal pada Windows — pada macOS/Linux perlu menjalankan terminal bersama Python di Wine atau lapisan perantara terpisah [FITUR BERGANTUNG PADA VERSI/LINGKUNGAN]
Jembatan cloud eksternal (mis. MetaApi)Skrip → layanan eksternal → terminal di cloud penyedia → server brokerMemerlukan penyerahan akses akun ke pihak ketiga; harga dan batas bergantung paketMenambah mata rantai dan titik kepercayaan. Jembatan pengirim order membutuhkan kata sandi master, bukan investor — yaitu hak mengendalikan akun, bukan sekadar membacanya. Periksa harga pada penyedia [FITUR BERGANTUNG PADA PAKET]
Kata sandi investor dan master adalah dua keputusan berbeda. Akun broker MetaTrader memiliki dua kata sandi. Kata sandi investor memberikan akses baca saja: melihat posisi dan riwayat tanpa hak memasang atau mengubah order. Kata sandi master memberikan kendali penuh akun. Untuk layanan eksternal — jurnal, verifikator hasil, dasbor statistik — berikan paling jauh kata sandi investor. Jembatan cloud yang hendak mengirim order secara definisi tidak dapat bekerja dengan kata sandi investor: memerlukan master, sehingga menghubungkannya secara kualitatif berbeda dari memasang jurnal. Periksa dokumentasi terkini penyedia tertentu untuk mengetahui akses yang benar-benar diminta. Jika master sudah diserahkan dan akses itu tidak ingin diteruskan: ubah di terminal atau dasbor broker (membatalkan akses pihak ketiga), periksa riwayat order untuk operasi tak dikenal, dan aktifkan autentikasi tambahan jika tersedia. Satu hal lagi: akses baca pun mengungkap gaya, jam kerja, dan ukuran posisi Anda — ini keputusan privasi, bukan formalitas.

Di luar pasar mata uang patut dikenal juga ccxt – pustaka penyeragam akses bursa kripto. Tidak berlaku bagi akun FX/CFD broker ritel, tetapi kadang disalahartikan sebagai solusi universal.

cTrader. Jika platform Anda cTrader, algoritme ditulis di lingkungan cTrader Algo yang secara bawaan mendukung C# dan Python — termasuk cBot, indikator, serta plugin [8] [STATUS: 2026-08-22]. Modelnya berbeda dari yang di atas: kode berjalan di dalam platform, bukan proses terpisah yang terhubung API. Periksa cakupan fungsi dan cara menjalankan dalam dokumentasi terkini penyedia karena produk aktif dikembangkan.

Kunci dan kata sandi: jangan pernah dalam kode

Kunci API yang diketik pada skrip ikut masuk repositori, cadangan, dan riwayat versi — tidak hilang dari riwayat Git hanya dengan menghapus barisnya. Repositori publik dipindai otomatis untuk string semacam itu. Tempat rahasia yang dapat diterima hanya variabel lingkungan, berkas konfigurasi di luar repositori yang tercantum dalam .gitignore, atau pengelola rahasia yang memasok nilai melalui salah satunya.

"""Konfigurasi dan rahasia: tidak ada data sensitif dalam berkas kode."""
import os
from dataclasses import dataclass

HOSTS = {
    "practice": "https://api-fxpractice.oanda.com",
    "live": "https://api-fxtrade.oanda.com",
}


class MissingConfiguration(RuntimeError):
    """Dipicu jika data wajib untuk memulai tidak ada."""


@dataclass(frozen=True)
class Configuration:
    token: str
    account: str
    environment: str

    @property
    def host(self) -> str:
        return HOSTS[self.environment]


def _required(name: str) -> str:
    value = os.environ.get(name, "").strip()
    if not value:
        # Tanpa nilai default disengaja: token kosong akan mengirim permintaan
        # tanpa otorisasi alih-alih menghentikan program.
        raise MissingConfiguration(f"Variabel lingkungan tidak ada: {name}")
    return value


def load_config() -> Configuration:
    environment = os.environ.get("BROKER_ENV", "practice").strip().lower()
    if environment not in HOSTS:
        raise MissingConfiguration(f"BROKER_ENV harus salah satu dari {sorted(HOSTS)}")
    return Configuration(
        token=_required("BROKER_API_TOKEN"),
        account=_required("BROKER_ACCOUNT_ID"),
        environment=environment,
    )

Tiga keputusan dalam fragmen ini disengaja. Lingkungan default adalah latihan, bukan produksi — kesalahan konfigurasi menuju demo, bukan akun nyata. Variabel lingkungan hilang menghentikan program alih-alih menggantinya dengan string kosong. Token tidak memiliki nilai default di mana pun, bahkan komentar, karena rahasia dalam komentar tetap rahasia yang terbuka.

Paket python-dotenv yang disebut sebelumnya tidak menggantikan kode ini — mendahuluinya. Pemanggilan load_dotenv() (setelah from dotenv import load_dotenv) memuat berkas .env di luar repositori ke variabel lingkungan proses; barulah load_config() membacanya. Dengan begitu fungsi konfigurasi tidak tahu dan tidak perlu tahu asal nilai: berkas, pengaturan sistem, atau pengelola rahasia. Berkas .env itu sendiri masuk .gitignore bersama seluruh direktori konfigurasi.

Mengambil data: timeout, percobaan ulang, kode respons

Permintaan tanpa batas waktu dapat menggantung selamanya dan membekukan proses. Percobaan ulang tanpa batas dapat menghabiskan kuota dan memutus akses. Fragmen berikut menunjukkan pengaman minimum pembacaan data — serta satu pembedaan yang kelak menyelamatkan akun: hanya operasi idempoten yang boleh diulang, yakni pengulangan yang tidak mengubah keadaan di sisi broker.

import logging
import time

import requests

TIMEOUT_S = 10.0        # detik tanpa aktivitas koneksi/pembacaan, bukan tenggat seluruh pekerjaan
MAX_ATTEMPTS = 4            # jumlah upaya termasuk yang pertama
BACKOFF_S = 1.0         # dasar penundaan eksponensial, dalam detik

log = logging.getLogger("data")


class ApiError(RuntimeError):
    """Kesalahan yang tidak dapat diperbaiki dengan mencoba ulang."""


def fetch_candles(session: requests.Session, host: str, token: str,
                   instrument: str, parameters: dict) -> dict:
    """Hanya mengulang GET - permintaan yang tidak mengubah apa pun pada sisi broker."""
    url = f"{host}/v3/instruments/{instrument}/candles"
    headers = {"Authorization": f"Bearer {token}"}

    for attempt in range(1, MAX_ATTEMPTS + 1):
        delay = BACKOFF_S * 2 ** (attempt - 1)
        try:
            response = session.get(url, headers=headers, params=parameters, timeout=TIMEOUT_S)
        except requests.RequestException as error:
            if attempt == MAX_ATTEMPTS:
                raise ApiError(f"Tidak ada koneksi setelah {MAX_ATTEMPTS} upaya") from error
            log.warning("upaya %s: %s", attempt, error)
            time.sleep(delay)
            continue

        if response.status_code == 429:
            # Batas laju terlampaui. Header Retry-After, jika dikirim broker,
            # lebih dapat dipercaya daripada menebak sendiri.
            try:
                wait = float(response.headers.get("Retry-After", delay))
            except (TypeError, ValueError):
                # Spesifikasi juga mengizinkan tanggal HTTP, bukan jumlah detik;
                # tanpa cabang ini float() akan menghentikan seluruh pengunduhan data.
                wait = delay
            log.warning("batas laju, menunggu %.1f s", wait)
            time.sleep(wait)
            continue

        if 500 <= response.status_code < 600:
            log.warning("kesalahan server %s, mencoba ulang", response.status_code)
            time.sleep(delay)
            continue

        if response.status_code != 200:
            raise ApiError(f"HTTP {response.status_code}: {response.text[:200]}")

        try:
            data = response.json()
        except ValueError as error:
            raise ApiError("Respons JSON tidak valid") from error
        if not isinstance(data, dict):
            raise ApiError("Respons JSON harus berupa objek")
        if not isinstance(data.get("candles"), list) or not data["candles"]:
            raise ApiError("Tidak ada candle dalam respons - periksa instrumen dan rentang tanggal")
        return data

    raise ApiError(f"Tidak ada respons valid setelah {MAX_ATTEMPTS} upaya")

Status verifikasi: diuji offline tanpa terhubung ke broker. Fungsi fetch_candles() yang dipublikasikan dijalankan menggunakan mock sesi HTTP dalam memori, bukan server nyata. Sembilan skenario mencakup 429 dengan Retry-After dalam detik, diikuti 503 lalu sukses dalam skenario yang sama, daftar candle kosong, 404, header tanggal HTTP, percobaan habis, pengecualian koneksi, JSON tidak valid, respons bukan objek, serta tipe candles yang tidak valid. Pemanggilan sleep() diganti catatan penundaan yang diminta; pengujian tidak membuka soket dan tidak mengukur waktu jaringan. Lingkungan: Python 3.12.10, requests 2.34.2, Windows 11, diperiksa 2026-10-08. Berikut laporan skenario sesungguhnya:

A: 429 detik -> 503 -> 200: calls=3; waits=[3.0, 2.0]; satu candle
B: candle kosong: calls=1; waits=[]; kesalahan yang diharapkan
C: HTTP 404: calls=1; waits=[]; kesalahan yang diharapkan
D: cadangan tanggal HTTP: calls=2; waits=[1.0]; satu candle
E: batas upaya: calls=4; waits=[0.0, 0.0, 0.0, 0.0]; kesalahan yang diharapkan
F: kegagalan transport: calls=4; waits=[1.0, 2.0, 4.0]; kesalahan yang diharapkan
G: JSON tidak valid: calls=1; waits=[]; kesalahan yang diharapkan
H: JSON bukan objek: calls=1; waits=[]; kesalahan yang diharapkan
I: tipe candles salah: calls=1; waits=[]; kesalahan yang diharapkan

Apa yang dikonfirmasi hasil ini dan apa yang tidak. Ini mengonfirmasi perilaku yang diharapkan pada sembilan skenario yang disiapkan. A memeriksa bahwa 429 memilih penundaan tiga detik server, bukan satu detik kode; D memeriksa perilaku cadangan ketika float() tidak dapat mengurai tanggal HTTP. F memeriksa bahwa timeout=10 diteruskan, empat upaya dilakukan, dan pengecualian mengakhiri simulasi kegagalan transport. Ini tidak menetapkan tenggat koneksi nyata: timeout requests bukan batas waktu total pekerjaan dan penantiannya dimock. B, C, dan G–I memeriksa penolakan respons kosong atau tidak valid. Pengujian tidak memverifikasi ketersediaan API, setiap kolom data, atau perilaku jaringan broker tertentu.

Namun hasil ini tidak mengonfirmasi apa pun tentang broker itu sendiri. Jalur endpoint, nama parameter, bentuk respons, serta apakah penyedia mengirim Retry-After merupakan sifat API tertentu [FITUR BERGANTUNG PADA BROKER/AKUN] dan harus diperiksa dalam dokumentasi serta diuji pada akun latihan. Mock membuktikan ketepatan kode, bukan ketepatan asumsi tentang server orang lain — pembedaan itulah seluruh nilai pengujian.

Polling dibanding streaming dengan waktu kuotasi, permintaan, dan penerimaan terpisah
Penerimaan pesan cepat tidak membuktikan kesegaran harga atau waktu eksekusi order.

Latensi harus diukur, bukan dikutip. Rentang dalam panduan — “50–200 ms untuk API ini, 100–500 ms untuk yang itu” — tidak berguna karena tidak menyebut lokasi klien, koneksi, waktu, ataupun metode pengukuran. Daripada menyalin angka orang lain, pisahkan yang diukur: waktu respons endpoint data, waktu pengiriman order hingga konfirmasi diterima, waktu hingga konfirmasi terisi, dan usia data pasar saat keputusan. Empat besaran berbeda, hanya satu yang sehari-hari disebut “latensi”. Ukur masing-masing pada rantai sendiri, catat dalam log, dan sebut bersama tanggal, broker, serta jumlah percobaan. Ping ICMP bukan salah satu dari empat nilai itu.

Polling atau streaming. Pada polling REST berkala, usia data bergantung interval permintaan: setiap lima detik berarti kasus terburuk memakai harga lima detik lalu, sedangkan polling lebih sering menghabiskan kuota. Stream — WebSocket atau HTTP streaming menurut API — membalik susunannya: server mengirim saat ada pembaruan. Ini masuk akal jika reaksi di dalam candle penting atau polling menghabiskan kuota. Untuk strategi masuk saat pembukaan H1 berikutnya, polling biasa cukup dan lebih mudah dipelihara. Namun stream menambah tiga masalah yang tidak dimiliki polling. Pertama, celah setelah penyambungan ulang: saat koneksi putus tiga puluh detik, tidak ada yang memutar kembali tiga puluh detik itu dari soket — fragmen hilang harus diambil melalui permintaan historis terpisah sebelum rangkaian disebut lengkap. Kedua, mendeteksi koneksi mati: soket yang berhenti mengirim tampak sama bagi kode dengan pasar tanpa aktivitas, sehingga diperlukan sinyal kehadiran (heartbeat atau pesan keep-alive) dan penghitung keheningan sendiri yang setelah batasnya terlampaui menyatakan koneksi putus lalu menyambungkannya lagi. Ketiga, urutan dan duplikat: setelah pulih, pesan yang sudah diproses dapat diterima kembali. Masalahnya sama dengan order — diselesaikan melalui pengenal peristiwa dan pemeriksaan apakah sudah dicatat.

Batas data dan order terpisah. API yang serius memisahkan stream harga dari transaksi; batas operasi pengubah keadaan akun kadang jauh lebih rendah daripada pembacaan. Kode yang mencoba mengubah stop-loss belasan kali per detik saat pasar tegang dapat diputus justru pada jalur transaksi — meninggalkan posisi terbuka tanpa akses program sementara. Ini alasan menetapkan pembatas laju perubahan di sisi sendiri, bukan berharap broker memaafkan.

Otomatisasi tidak mengubah kondisi eksekusi. Model eksekusi broker, penanganan arus, dan mutu feed memengaruhi hasil terlepas pengirim order manusia atau skrip — pada strategi sensitif biaya entri, dapat menentukan hasil. Pokok ini dibahas pada bagian pemilihan broker: model eksekusi dan mutu eksekusi. Bagi kode, satu kesimpulan cukup: API mempercepat pengiriman permintaan eksekusi, tetapi tidak menciptakan likuiditas yang tidak ada. Terlihat jelas saat pergerakan tajam. Pada 15 Januari 2015, Swiss National Bank menghapus kurs minimum 1,20 franc per euro dan sekaligus menurunkan suku bunga ke −0,75 persen [17]. Mekanismenya umum dan tidak bergantung anekdot: jika sisi seberang tidak memiliki likuiditas pada level proteksi, order tidak terisi pada harga tertulis, melainkan harga pertama tersedia — jaraknya ditentukan buku order, bukan koneksi Anda. Skala kejadian tiap broker dibahas bagian pemilihan broker; bagi kode cukup: kecepatan pengiriman tidak menciptakan harga yang tidak ingin diperdagangkan siapa pun.

8. Dari notebook ke layanan: perbedaan kode eksekusi dan skrip

Inilah batas sebenarnya artikel. Notebook penghitung statistik jurnal boleh gagal di tengah tanpa akibat — jalankan ulang. Kode yang diizinkan menyentuh akun meninggalkan keadaan setelah kesalahan sama: posisi terbuka, order berubah sebagian, entri ganda. Perbedaannya bukan mutu kode, melainkan salah satunya menimbulkan konsekuensi yang tidak dapat dibatalkan.

Notebook riset ≠ program eksekusi. Sebelum kode berhak memasang order, harus lengkap: konfigurasi terpisah dari kode · rahasia di luar repositori · log setiap keputusan dan respons server · pengujian logika tanpa jaringan · percobaan ulang terbatas dan hanya operasi idempoten · timeout setiap pemanggilan jaringan · keadaan yang bertahan setelah proses dimulai ulang · pemantauan dengan sinyal kehadiran · kill switch penghenti entri baru · prosedur deployment yang menjelaskan penerapan versi baru. Satu unsur saja hilang berarti prototipe, bukan program eksekusi — perlakukan demikian, betapa pun baik kurva ekuitas pengujiannya.

Tiga tingkat otomatisasi – berbeda pada pengambil keputusan akhir, bukan teknologi:

Tingkat 1: notifikasi. Skrip memeriksa kondisi dan mengirim pesan. Tidak memiliki akses akun; token order tidak diperlukan. Manusia memutuskan dan mengeksekusi. Hanya pada tingkat ini kesalahan kode berbiaya sebatas kejengkelan.

Tingkat 2: persiapan dengan konfirmasi. Skrip menyusun order — instrumen, arah, ukuran — dan menunggu konfirmasi eksplisit operator sebelum mengirim. Tingkat ini menangkap kesalahan aritmetika sebelum menjadi posisi serta mempertahankan manusia dalam lingkar keputusan. Bagi trader ritel yang tidak hendak merawat perangkat lunak, ini biasanya tujuan akhir, bukan tahap peralihan — tetapi itu penilaian editorial, bukan temuan riset.

Tingkat 3: berjalan tanpa konfirmasi. Skrip membuka dan menutup posisi sendiri. Memerlukan seluruh kotak di atas, pengujian akun latihan, lalu akun berukuran posisi minimum, dan pengawasan pada awal operasi. Berapa transaksi cukup sebelum maju adalah soal ukuran sampel dan daya statistik, bukan angka bulat; dibahas dalam artikel tentang pengujian.

Cabang analisis offline terpisah dari gerbang dan adaptor eksekusi
Pastikan analisis benar dahulu; pengiriman memerlukan desain, pengujian, serta konfirmasi keadaan terpisah.

Lapisan eksekusi dengan gerbang keselamatan

Jika menampilkan kode pengirim order, tampilkan juga penghalangnya. Modul berikut dapat dijalankan seluruhnya dan tidak memiliki satu pun pemanggilan jaringan: menyusun order, memeriksa batas, dan berhenti pada gerbang. Pengiriman sendiri merupakan fragmen terpisah — sengaja bukan kode.

"""Lapisan eksekusi: validasi, mode uji, dan gerbang eksplisit sebelum live."""
import logging
import os
import sys
import uuid

ALLOWED_INSTRUMENTS = {"EUR_USD"}
MAX_UNITS = 1_000          # unit mata uang dasar, bukan lot - konvensi bergantung pada API

# Konfirmasi live tidak dapat dibaca dari berkas ini karena tidak ada di sini.
# Program mengambilnya dari variabel lingkungan dan menerima sebagai nilai benar
# nomor akun yang sadar ingin diperdagangkan operator - diketik manual di shell
# sebelum mulai. Jangkauan pembatas dijelaskan dalam kotak di bawah keluaran uji.
CONFIRMATION_VARIABLE = "BROKER_LIVE_CONFIRMATION"

# Tanpa konfigurasi ini tidak ada catatan log.info: tingkat root default
# adalah WARNING dan tidak ada tujuan penulisan. Berkas meninggalkan jejak setelah kegagalan,
# stream menampilkan hal sama secara langsung. Pada program lebih besar, konfigurasi log
# berada pada titik masuk program, bukan modul.
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
    handlers=[logging.FileHandler("execution.log", encoding="utf-8"),
              logging.StreamHandler(sys.stdout)],
)

log = logging.getLogger("execution")


class SafetyGateError(RuntimeError):
    """Menghentikan order sebelum keluar proses."""


def new_order_id() -> str:
    """Dipanggil SATU KALI per niat order dan disimpan sebelum pengiriman.

    Nilai sama harus digunakan setiap upaya berikutnya atas order sama;
    tanpanya duplikat tidak dapat dikenali.
    """
    return str(uuid.uuid4())


def build_order(instrument: str, units: int, order_id: str) -> dict:
    if instrument not in ALLOWED_INSTRUMENTS:
        raise SafetyGateError(f"Instrumen tidak ada dalam daftar izin: {instrument}")
    # bool adalah subkelas int sehingga isinstance(True, int) bernilai true: tanpa penolakan
    # eksplisit, boolean akan lolos validasi dan masuk payload.
    if isinstance(units, bool) or not isinstance(units, int) or units == 0:
        raise SafetyGateError("Ukuran harus bilangan bulat bukan nol")
    if abs(units) > MAX_UNITS:
        raise SafetyGateError(
            f"Ukuran {abs(units)} melampaui batas {MAX_UNITS}")
    return {
        "order": {
            "type": "MARKET",
            "instrument": instrument,
            "units": str(units),
            # Pengenal berasal dari luar dan tidak berubah antarpercobaan.
            # Jika dibuat di sini, setiap retry akan membawa nilai berbeda.
            # Nama kolom dan batasnya merupakan milik API tertentu.
            "clientExtensions": {"id": order_id},
        }
    }


def _operator_confirmation() -> str:
    """Membaca nilai dari luar berkas. Kode tidak mengetahui atau menyimpannya di mana pun."""
    return os.environ.get(CONFIRMATION_VARIABLE, "").strip()


def submit_order(session, cfg, instrument: str, units: int, order_id: str,
                  test_mode: bool = True) -> dict | None:
    """Secara default tidak mengirim. Host produksi memerlukan dua keputusan."""
    order = build_order(instrument, units, order_id)

    if test_mode:
        log.info("MODE UJI [%s] order disusun, tidak ada yang dikirim: %s",
                 cfg.environment, order)
        return None

    if cfg.environment == "live" and _operator_confirmation() != cfg.account:
        raise SafetyGateError(
            "Host produksi memerlukan konfirmasi eksplisit operator")

    # Gerbang lolos - di sinilah modul berakhir. Tidak ada pengiriman,
    # karena tidak ada cara jujur menjalankannya; struktur dijelaskan dalam sketsa berikut.
    raise NotImplementedError(
        "Pengiriman tidak diimplementasikan dalam modul ini - lihat sketsa struktural")

Pengenal order dibuat satu kali, bukan setiap percobaan. Agar duplikat dapat dikenali setelah koneksi putus, nilainya harus dihitung saat muncul niat memasang order, disimpan permanen sebelum pengiriman, dan digunakan lagi pada setiap percobaan order yang sama. Jika uuid.uuid4() berada di dalam penyusunan order, setiap percobaan mengirim nilai berbeda sehingga permintaan kedua tidak dapat dikenali sebagai pengulangan yang pertama. Karena itulah build_order() menerima pengenal sebagai argumen, bukan membuatnya sendiri.

Yang tidak diselesaikan pengenal itu. Kolom clientExtensions pada payload di atas adalah solusi satu penyedia, bukan standar industri atau jaminan idempotensi terdokumentasi. Dokumentasi OANDA menyebutnya data khusus opsional pada order dan memperingatkan terpisah: “Jangan menetapkan, mengubah, menghapus tradeClientExtensions jika akun Anda terkait MT4” [14] (diperiksa 2026-08-22). Tidak ada janji order pasar berulang dengan pengenal sama ditolak sebagai duplikat — order pasar terisi langsung sehingga jendela pengenalannya minimal. Pengenalan duplikat bergantung bukan pada kolom payload, melainkan dua hal di sisi Anda: register niat permanen yang ditulis sebelum pengiriman, serta pencocokan dengan daftar order dan transaksi broker setelah kegagalan. Periksa nama kolom dan batasnya dalam dokumentasi API sendiri — penyedia lain menggunakan nama berbeda atau tidak menyediakannya.

Mengirim order: sketsa struktural

Di bawah bukan fungsi untuk disalin, melainkan sketsa struktural: urutan langkah dan nama operasi yang dapat dibawa ke kode Anda, bukan ditempel lalu dijalankan. Bentuk ini pilihan, bukan kelalaian. Redaksi tidak menjalankan cabang ini dan tidak berniat melakukannya: pengiriman order memerlukan akun broker, dan kode transaksi yang diuji pada akun orang lain bukan pengujian, melainkan risiko orang lain. Fungsi lengkap akan menjadi artefak yang tidak pernah dijalankan siapa pun — terutama mengajarkan kepercayaan pada kode belum terverifikasi. Pilihan sama diterapkan pada sketsa MQL di artikel 11.5 serta sketsa Pine Script di artikel 11.2.

SKETSA STRUKTURAL - mengirim order dan menangani respons yang tidak datang
Panduan arah bagi lapisan HTTP. Bukan kode lengkap yang dapat dijalankan.

deklarasi: StanNieznany - respons tidak datang sehingga tidak diketahui apakah
             order ada. Tipe terpisah, bukan subkelas kesalahan jaringan: kesalahan
             jaringan boleh diulang, keadaan ini tidak boleh diulang.

masukan: order yang sudah disusun dan divalidasi, order_id, konfigurasi, sesi HTTP
keluaran: respons broker atau StanNieznany - jangan pernah sukses diam-diam

1. REGISTER PERMANEN - sebelum apa pun
   tulis di luar memori proses: order_id, stempel waktu UTC,
   instrument, units, environment, status := INTENT
   paksa penulisan ke disk sebelum melanjutkan
   tanpa langkah ini restart menghilangkan informasi niat,
   sehingga program tidak tahu apa yang sudah dicoba setelah mulai lagi

2. JURNAL - juga sebelum pengiriman
   tulis ke log: order_id + environment + isi order
   token tidak pernah masuk jurnal
   samarkan nomor akun kecuali empat karakter terakhir
   jurnal dibaca manusia sesudah kejadian; register langkah 1 dibaca program

3. SATU PERMINTAAN, NOL PERCOBAAN ULANG
   POST ke jalur order dalam dokumentasi API Anda
   header otorisasi memakai token konfigurasi, jangan dari kode
   timeout wajib - tanpanya pemanggilan dapat menggantung selamanya

4. RESPONS TIDAK DATANG (timeout atau kesalahan transport)
   JANGAN ulangi - POST tidak idempoten
   status dalam register := UNKNOWN
   munculkan StanNieznany
   REKONSILIASI KEADAAN: minta daftar order dan transaksi broker
   beberapa menit terakhir, cari order_id, dan hanya setelah
   memastikan apakah order ada, buat keputusan baru
   (prosedur ini tidak ada dalam sketsa - lihat paragraf di bawahnya)

5. RESPONS DATANG
   tulis ke log: kode HTTP + isi respons yang dipangkas
   jika kode di luar (200, 201) -> status := REJECTED -> munculkan kesalahan
   jika isi bukan JSON         -> status := UNKNOWN  -> munculkan StanNieznany
   selain itu                  -> status := ACCEPTED -> kembalikan respons

yang tidak dan tidak dapat dicakup sketsa:
   tidak ada fungsi siap tempel yang dapat dijalankan terhadap
   akun - juga tidak ada nama kolom level proteksi karena berasal
   dari dokumentasi API tertentu, bukan contoh orang lain.

Yang tidak dilakukan sketsa ini – sama pentingnya dengan yang dijelaskan. Sketsa tidak menambah level proteksi, baik stop loss maupun take profit. Bukan kelalaian — konvensinya berbeda antar-API (kolom payload terpisah, jarak atau harga absolut, masa berlaku sendiri), jadi fragmennya harus berasal dari dokumentasi broker Anda, bukan contoh orang lain. Akibat praktisnya tepat pada langkah 4: setelah StanNieznany Anda mungkin memiliki posisi terbuka tanpa proteksi dan tanpa kepastian bahwa posisi itu ada. Dalam kode yang berhak mengirim order, level proteksi harus ikut permintaan entri yang sama — tanpanya ada jendela posisi hidup tanpa stop.

Bukan hanya proteksi yang hilang. Dari daftar kotak “Notebook riset ≠ program eksekusi”, modul di atas dan sketsa ini belum memiliki pemantauan, kill switch, atau prosedur deployment; keadaan yang bertahan setelah restart muncul pada langkah 1, tetapi hanya sebagai persyaratan, bukan implementasi. Bagian log, keadaan, dan pengujian membicarakannya — bukan menunjukkan kode. Kesimpulannya sama: materi belajar arsitektur, bukan program eksekusi.

Gerbang keselamatan diuji offline – tanpa jaringan, broker, atau rahasia, dengan konfigurasi mock. Berikut seluruh pengujian, bukan hanya keluarannya: tanpanya Anda harus percaya kata kami, persis jenis kepercayaan yang diperingatkan artikel ini.

"""Menguji gerbang tanpa jaringan: konfigurasi mock, tanpa rahasia atau POST."""
from dataclasses import dataclass


@dataclass(frozen=True)
class MockConfiguration:
    environment: str = "practice"
    host: str = "https://api-fxpractice.oanda.com"
    token: str = "dummy-token-for-offline-test"
    account: str = "000-000-0000000-000"


def test_gates() -> None:
    cfg = MockConfiguration()
    order_id = new_order_id()
    print("konfigurasi:", cfg.environment, cfg.host, "token_length=", len(cfg.token))

    # session=None disengaja: jika kode tetap mencoba POST,
    # pengujian gagal dengan AttributeError alih-alih lolos diam-diam.
    result = submit_order(None, cfg, "EUR_USD", 100, order_id)
    assert result is None
    print("1. mode uji: tidak ada yang dikirim - OK")

    for description, instrument, units in [
        ("instrumen tidak diizinkan", "GBP_JPY", 100),
        ("ukuran melebihi batas", "EUR_USD", 5000),
        ("ukuran nol", "EUR_USD", 0),
        ("nilai boolean alih-alih angka", "EUR_USD", True),
    ]:
        try:
            build_order(instrument, units, order_id)
        except SafetyGateError as error:
            print(f"2. gerbang [{description}]:", error)
        else:
            raise AssertionError(f"Gerbang yang diharapkan tidak terpicu: {description}")

    cfg_live = MockConfiguration(environment="live", host="https://api-fxtrade.oanda.com")
    try:
        submit_order(None, cfg_live, "EUR_USD", 100, order_id, test_mode=False)
    except SafetyGateError as error:
        print("3. gerbang [mode live tanpa konfirmasi]:", error)
    else:
        raise AssertionError("Mode live lolos tanpa konfirmasi")

    print("semua gerbang lolos")


if __name__ == "__main__":
    test_gates()

Keluaran pengujian — Python 3.12.10 seperti bagian 2, tanpa jaringan dan paket di luar pustaka standar: setelah pengiriman dihapus, modul eksekusi tidak lagi memerlukan requests. Baris kedua merupakan catatan jurnal: isi yang sama masuk bersamaan ke berkas execution.log dan keluaran standar. Stempel waktu serta pengenal order berbeda setiap pengujian — sisanya dapat diulang hingga setiap karakter:

konfigurasi: practice https://api-fxpractice.oanda.com token_length= 28
1. mode uji: tidak ada yang dikirim - OK
2. gerbang [instrumen tidak diizinkan]: Instrumen tidak ada dalam daftar izin: GBP_JPY
2. gerbang [ukuran melebihi batas]: Ukuran 5000 melampaui batas 1000
2. gerbang [ukuran nol]: Ukuran harus bilangan bulat bukan nol
2. gerbang [nilai boolean alih-alih angka]: Ukuran harus bilangan bulat bukan nol
3. gerbang [mode live tanpa konfirmasi]: Host produksi memerlukan konfirmasi eksplisit operator
semua gerbang lolos

Apa yang dikonfirmasi keluaran ini dan apa yang tidak. Lima kasus negatif memicu gerbang yang diharapkan: instrumen tidak diizinkan, ukuran berlebih, nol, boolean, serta live tanpa konfirmasi. Assertion juga memeriksa mode uji; audit tambahan memeriksa batas ±1000, pelampauan batas, dan tipe tidak valid. Argumen pemanggilan pertama adalah None, dan pengiriman tidak diimplementasikan sehingga pengujian tidak dapat memasang order. Keluaran tidak menguji API broker, ketahanan log, atau penyamaran semua kolom sensitif. Token dummy tidak dicetak, hanya panjangnya. Pada proyek sendiri, uji penulisan serta pemulihan log, aturan penyuntingan data, dan kegagalan disk secara terpisah.

Namun pengujian tidak mengonfirmasi perilaku terhadap API nyata — tidak mungkin, sebab cabang pengiriman tidak ada pada modul ini. Digantikan sketsa struktural sehingga satu-satunya tempat pengiriman pun hilang. Itulah perbedaan “kami tidak menjalankannya” dengan “tidak ada yang dapat dijalankan”: yang pertama meninggalkan artefak siap tempel; yang kedua struktur yang harus diimplementasikan sendiri, sekaligus membaca dokumentasi broker sendiri.

Pengujian juga harus mendeteksi gerbang yang hilang. Karena itu ia mengassert hasil mode uji dan memunculkan AssertionError setelah setiap kasus negatif bila pengecualian yang diharapkan tidak muncul. Pesan akhir hanya muncul setelah pemeriksaan lolos. Ini tetap contoh kecil, tidak mencakup format respons broker, semua tipe, atau pemulihan keadaan. Pada proyek besar, pindahkan kasus ke pytest, tambah kasus positif dan jalankan setiap validator berubah.

Gerbang konfirmasi melindungi dari kesalahan, bukan program. Jangkauannya perlu disebut eksplisit agar tidak dianggap lebih kuat. Nilai konfirmasi bukan konstanta berkas; dibaca dari variabel lingkungan BROKER_LIVE_CONFIRMATION dan dibandingkan dengan nomor akun, sehingga operator harus mengetik sadar nomor yang hendak diperdagangkan di shell. Cukup untuk tiga kesalahan nyata: menjalankan produksi “sekadar mencoba”, tertukar akun, atau tidak sengaja mematikan mode uji. Tidak lebih. Kode dalam proses sama dapat menetapkan variabel sendiri dan gerbang tidak menghalanginya — konfirmasi dari lingkungan sendiri bukan perlindungan terhadap program. Pembatasan hak sesungguhnya berada di luar kode: token tanpa hak memasang order bila broker membedakannya, serta batas ukuran posisi pada sisi akun [FITUR BERGANTUNG PADA BROKER/AKUN].

Unit bukan lot. Komentar di samping MAX_UNITS bukan dekorasi. API menyatakan ukuran dengan konvensi berbeda — unit mata uang dasar, lot, kontrak — dan kekeliruan tidak menyebabkan kesalahan sintaks atau pengecualian. Justru eksposur dua atau tiga orde lebih besar meskipun kode bekerja benar. Sebelum mengirim, periksa konvensi dokumentasi dan konfirmasi dengan satu transaksi ukuran minimum di akun latihan. Ketahui dua batas validator sebelum dipakai pada perhitungan sendiri. Ukuran dari pandas atau NumPy bertipe numpy.int64, bukan subkelas tipe bawaan int — validator menolak dengan pesan bilangan bulat meskipun nilainya memang bulat. Gagal secara aman tetapi membingungkan; lewati angka DataFrame melalui int(...) sebelum masuk build_order(). Batas kedua: order_id sama sekali tidak diperiksa dan anotasi tipe tidak memaksakan apa pun — karena pengenalan duplikat bergantung pada pengenal itu, periksa panjang dan format yang diizinkan API lalu tegakkan seperti batas ukuran.

Log, keadaan, dan pengujian

Log, bukan print(). Modul logging memberikan tingkat keparahan, stempel waktu, dan penulisan berkas — tidak ada yang aktif sendiri. Tanpa konfigurasi tingkat default adalah WARNING dan tidak ada tujuan penulisan sehingga pemanggilan log.info(...) dibuang diam-diam: kode tampak mencatat tetapi tidak menyimpan apa pun. Ini sebab berulang jurnal kosong setelah kegagalan. Konfigurasi sekali saja pada titik masuk program — skrip yang dijalankan, bukan modul yang diimpor. Pemanggilan logging.basicConfig(...) ada pada modul eksekusi di atas hanya karena modul itu sekaligus seluruh program; komentarnya jelas menyebut pada proyek besar lokasinya keliru. Alasannya konkret: konfigurasi dalam modul berjalan saat impor dan mengambil alih log seluruh proses; pemanggilan logging.basicConfig(...) selanjutnya di titik masuk asli tidak melakukan apa pun — fungsi tidak aktif setelah root logger memiliki tujuan (kecuali memakai force=True), tanpa sinyal kesalahan [4]. Pengaturan Anda hilang, log menuju lokasi pilihan modul impor: cacat diam-diam yang sama dari sisi lain. Catat keputusan beserta alasannya, isi permintaan, dan respons server — pangkas respons agar halaman kesalahan HTML tidak membanjiri jurnal, lalu naikkan batas saat diagnosis. Token satu pengecualian: jangan pernah dicatat. Log tanpa respons broker tidak memungkinkan penentuan apakah masalahnya logika, jaringan, atau server.

Jurnal eksekusi juga data sensitif. Isi order lengkap masuk execution.log, dan pada program lengkap juga respons broker: nomor akun, pengenal order, ukuran, harga. Token tidak ada dan tidak boleh ada — sepuluh pengujian menghasilkan sebelas catatan dan nol kemunculan token — tetapi sisanya cukup untuk memperlakukan berkas seperti konfigurasi berisi rahasia: masukkan execution.log ke .gitignore bersama .env. Selain itu, .gitignore bukan kontrol akses: tidak membatasi izin berkas, menghapusnya setelah waktu tertentu, atau menyamarkan isi. Karena itu sketsa mengharuskan penyamaran nomor akun kecuali empat karakter terakhir serta log kolom dari daftar yang Anda tentukan — bukan seluruh respons server. Dua hal lagi perlu dibenahi sebelum operasi tanpa pengawasan. Jalur: execution.log bersifat relatif sehingga berkas dibuat pada direktori kerja proses, bukan di samping skrip. Saat dijalankan Task Scheduler atau cron — seperti cara skrip ini memang dijalankan, dibahas di bawah — direktori kerja mungkin berbeda jauh sehingga jurnal dasar pemulihan kegagalan berada bukan di lokasi pencarian Anda. Gunakan jalur absolut dari lokasi skrip atau konfigurasi. Ukuran: mengikuti saran paragraf sebelumnya, berkas tumbuh tanpa batas karena contoh tidak memiliki rotasi. logging.handlers.RotatingFileHandler ada dalam pustaka standar yang sama dan — setelah menambahkan import logging.handlers, sebab import logging saja tidak mengekspos submodul tersebut — menggantikan FileHandler tanpa mengubah konfigurasi lain.

Keadaan harus bertahan setelah restart. Proses pada akhirnya akan berhenti — pembaruan sistem, listrik padam, atau Ctrl+C Anda sendiri. Jika informasi order hanya dalam memori, setelah restart program tidak tahu apa yang telah dilakukan. Solusi minimum: simpan pengenal order ke berkas atau basis data sebelum mengirim, lalu cocokkan dengan keadaan akun API saat mulai. Prosedur itulah yang mencegah entri ganda setelah koneksi putus.

Pengujian tanpa jaringan. Logika sinyal dan validasi batas dapat diuji dengan data siap pakai tanpa koneksi, seperti gerbang di atas. Uji yang membutuhkan API aktif bukan unit test dan tidak dapat dijalankan tiap perubahan. Mulai dari yang dapat diuji offline: kondisi entri, ukuran posisi, respons terhadap data kosong dan data bercelah.

Penghentian terkendali. Program eksekusi harus merespons sinyal penghentian: selesaikan operasi, simpan keadaan, lalu keluar — bukan terputus di tengah pengiriman order. Terpisah, miliki kondisi penghenti entri baru saat batas rugi harian terlampaui. Penetapan dan perhitungannya dibahas dalam bagian manajemen risiko kursus; dalam kode ini kondisi biasa sebelum setiap entri, bukan fungsi pustaka.

Tempat menjalankannya. Server yang berjalan terus, sinkronisasi jam sistem, kelanjutan setelah restart, serta prosedur pemulihan kegagalan dibahas dalam Otomatisasi perdagangan: EA, skrip, dan VPS. Cara menjalankan berbeda bukan kenyamanan, melainkan kejadian setelah kegagalan: Task Scheduler pada Windows atau cron pada Unix menjalankan skrip berkala dan mengakhirinya setelah selesai; layanan sistem (misalnya unit systemd) menjaga proses terus berjalan dan dapat memulai ulang setelah keluar; pengelola proses melakukan hal sama pada lapisan aplikasi. Pilihan pekerjaan berkala singkat atau proses jangka panjang menentukan tempat keadaan disimpan dan siapa yang memastikan hanya satu instans. Bagi kode Python, satu hal penting: skrip scheduler harus menoleransi pengujian sebelumnya belum selesai — periksa keadaan sebelum bertindak, jangan berasumsi mulai dari nol.

Lihat juga: Perdagangan algoritmik – arsitektur sistem dan posisi otomatisasi dalam seluruh proses keputusan.

9. Look-ahead dan kebocoran data dalam kode Python

Look-ahead bukan kesalahan metodologi, melainkan satu baris kode — karena itulah masuk artikel ini, bukan metodologi pengujian. Hasil yang ditempatkan pada waktu tertentu memakai informasi yang belum ada pada waktu tersebut.

Bentuk paling umum pada pandas sangat sederhana: kolom sinyal dikalikan kolom imbal hasil baris yang sama. Imbal hasil baris t terbentuk antara penutupan candle sebelumnya dan penutupan candle t. Sinyal dari penutupan candle t baru diketahui setelah imbal hasil itu terjadi. Mengalikan keduanya menghadiahkan hasil pergerakan yang belum Anda ketahui saat keputusan.

Asumsi contoh berikut: delapan observasi per jam sintetis, indeks UTC, nilai ilustratif, tanpa biaya transaksi — tujuannya menunjukkan mekanisme pergeseran, bukan mengevaluasi aturan. Lingkungan seperti bagian 2: Python 3.12.10, pandas 3.0.5.

"""Look-ahead dalam satu baris: aturan sama, dua hasil berbeda."""
import pandas as pd

index = pd.date_range("2026-06-01", periods=8, freq="1h", tz="UTC")
df = pd.DataFrame(
    {"close": [1.0800, 1.0810, 1.0825, 1.0815, 1.0830, 1.0845, 1.0840, 1.0855]},
    index=index,
)
df["sma"] = df["close"].rolling(3).mean()
df["return"] = df["close"].pct_change()

# Aturan: posisi long ketika penutupan di atas rata-rata tiga candle.
df["position"] = (df["close"] > df["sma"]).astype(int)

# SALAH: posisi baris t dikalikan imbal hasil baris yang sama.
# Imbal hasil t terbentuk antara penutupan t-1 dan t sehingga keputusan
# menggunakan harga yang belum ada saat entri.
df["result_with_lookahead"] = df["position"] * df["return"]

# BENAR: posisi pada penutupan t baru berlaku pada imbal hasil t+1.
df["result_without_lookahead"] = df["position"].shift(1) * df["return"]

print(df[["result_with_lookahead", "result_without_lookahead"]].sum().round(6))

Keluaran pengujian:

result_with_lookahead       0.005082
result_without_lookahead    0.001384
dtype: float64

Interpretasi. Aturan sama, data sama, hanya satu pemanggilan shift(1) yang berbeda — jumlah imbal hasil sederhana berkala turun dari 0,005082 ke 0,001384, yakni kurang dari 30 persen nilai awal. Delapan observasi tentu ilustrasi, bukan pengukuran; pada data bertahun-tahun, kesalahan sama dapat mengubah strategi rata-rata menjadi spektakuler, dan yang spektakuler menjadi mustahil. Kedua nilai tidak berdimensi dan tanpa spread atau komisi — setelah biaya ditambahkan, versi benar lebih rendah lagi.

Catatan tentang aritmetika itu sendiri. pct_change() menghasilkan imbal hasil sederhana yang — seperti bagian 4 — dikompaun melalui perkalian, bukan penjumlahan; hanya imbal hasil log boleh dijumlahkan. Penjumlahan di sini merupakan pendekatan sadar karena pada orde seperseribu, selisih jumlah dan kompaun benar pada contoh ini kurang dari 0,00001, lebih dari dua orde lebih kecil daripada perbedaan yang hendak ditunjukkan. Untuk perhitungan hasil akun, bukan mekanisme pergeseran, kompaunkan (1 + r) atau gunakan imbal hasil log lalu jumlahkan.

Waspadai pergeseran ganda. Pergeseran dilakukan pada satu tempat: kolom posisi atau data masukan sinyal — jangan keduanya. Kode yang menghitung sinyal dari harga bergeser kemudian menggeser posisi lagi menunda entri dua periode dan diam-diam merendahkan hasil. Kesalahan arah itu lebih tidak berbahaya daripada look-ahead, tetapi sama tidak benarnya.

Tabel harga membandingkan penempatan imbal hasil bias dan setelah pergeseran
Di sini penggunaan penutupan masa depan mengubah hasil −0,98% menjadi +2,00% semu.

Bentuk look-ahead lainnya dalam kode Python – semuanya bermuara pada pertanyaan: apakah nilai ini diketahui saat keputusan?

  • Menggunakan high atau low candle saat ini untuk kondisi entri, padahal baru diketahui setelah ditutup
  • Penskalaan atau normalisasi seluruh data sebelum pembagian pelatihan dan validasi — parameternya membawa informasi masa depan
  • Indikator penghalus atau penanda titik balik dengan candle berikutnya; nilai historisnya berbeda dari yang terlihat live
  • Data makro versi pascarevisi, bukan nilai publikasi asli
  • Order diisi pada harga penutupan candle pembentuk sinyal

Kebocoran saat penyiapan data. Butir kedua layak dibahas tersendiri karena dilakukan refleks dalam Python. Memanggil scaler pada seluruh DataFrame sebelum pembagian periode hanya satu baris yang tampak tidak berbahaya, tetapi membawa pengetahuan distribusi masa depan ke bagian validasi. Parameter transformasi hanya ditentukan dari data terdahulu dan diterapkan ke data selanjutnya — jangan sebaliknya.

Dua versi kode menempatkan sinyal pada imbal hasil saat ini atau berikutnya
Pergeseran sinyal memperbaiki kesalahan waktu tertentu dalam model ini; tidak membuktikan profitabilitas.

Pengujian berganda. Makin banyak varian pada data sama, makin besar peluang yang terbaik kebetulan. Pada model nol eksplisit, jika 10.000 pengujian masing-masing berpeluang positif palsu 0,05, jumlah harapan hasil berlabel “signifikan” adalah 0,05 × 10.000 = 500. Harapan ini berasal dari penjumlahan probabilitas tanpa memerlukan independensi; ketergantungan mengubah distribusi jumlah, bukan rata-ratanya di bawah asumsi tersebut. Orde besaran menunjukkan mengapa jumlah percobaan wajib dilaporkan bersama hasil.

Ada koreksi formal yang memperhitungkan jumlah percobaan — Deflated Sharpe Ratio, diusulkan Bailey dan López de Prado pada 2014 [12]. Sebelum memakai implementasi siap pakai, periksa status dan lisensi pustakanya; sejumlah proyek telah mengubah model distribusi. Rumus tersedia pada publikasi sumber dan dapat diimplementasikan mandiri.

Yang tidak dicakup bagian ini. Overfitting, seleksi sampel, walk-forward, survivorship bias, serta ketahanan hasil terhadap kenaikan biaya merupakan metodologi pengujian, dibahas dalam artikel tentang backtesting dan forward testing. Di sini hanya hal yang dapat rusak — dan diperiksa — dalam kode.

Memeriksa kode sebelum memercayai hasil:

  1. Apakah hasil baris t hanya memakai data yang tersedia sebelum keputusan?
  2. Apakah pergeseran tepat satu kali — pada sinyal atau data, bukan keduanya?
  3. Apakah waktu dan harga pengisian dinyatakan eksplisit?
  4. Apakah biaya transaksi masuk model, atau sengaja dihilangkan dengan penjelasan?
  5. Apakah pembagian pelatihan dan validasi kronologis?
  6. Apakah transformasi data ditentukan hanya dari bagian terdahulu?
  7. Apakah jumlah varian tercatat bersama hasil?
  8. Apakah indeks waktu berzona eksplisit, meningkat tanpa duplikat?
Aturan praktis: hasil luar biasa baik adalah hipotesis kesalahan, bukan keunggulan. Sebelum memutuskan menemukan hal istimewa, periksa daftar satu per satu lalu jalankan pada data dengan jawaban diketahui — misalnya harga konstan yang imbal hasil perubahan harganya nol sebelum biaya; transaksi bisa merugi setelah biaya. Pengujian dengan jawaban diketahui mendeteksi lebih banyak kesalahan daripada memandangi kurva ekuitas.

10. Berapa banyak kode yang sebenarnya diperlukan

Tidak harus menjadi pengembang. Anda harus cukup tahu agar tidak tertipu kode sendiri atau skrip unduhan repositori.

TingkatKeterampilanManfaatPerkiraan waktu
0 – tanpa kodeTidak adaMenganalisis sebatas yang diizinkan platform dengan cara yang ditentukannya—
1 – membaca kodeMemahami skrip orang lain, mengubah parameter, menjalankannya dalam lingkungan terisolasiMemeriksa look-ahead dan apakah skrip repositori membebankan biayaBeberapa minggu
2 – analisis sendiripandas, numpy, matplotlib; skrip analisis sendiriAnalisis jurnal dan data pasar tanpa batas lembar kerjaBeberapa bulan
3 – integrasiAPI, penanganan kesalahan, log, penjadwalan, pengujianNotifikasi dan persiapan order dengan konfirmasiEnam bulan atau lebih
4 – kode eksekusiPengujian otomatis, manajemen keadaan, deployment, pemantauanProgram yang boleh berjalan tanpa konfirmasi setiap keputusanDalam hitungan tahun praktik

Waktu kolom terakhir hanya perkiraan, bergantung titik awal dan jam kerja nyata per minggu — perlakukan sebagai orde besaran, bukan janji.

Tingkat 1 dan 2 tidak memberi keunggulan pasar. Yang diberikan adalah kemampuan menilai apakah solusi orang lain masuk akal dan berhenti membeli sistem yang tidak dapat diverifikasi. Nilainya nyata, tetapi berbeda dari yang dijual penyedia perangkat siap pakai. Tingkat 4 diperlukan untuk merawat program eksekusi, bukan untuk berdagang lebih baik. Tinjauan penggunaan Python dalam keuangan ada pada buku Hilpisch [11], dengan satu catatan bagi semua buku kode: periksa fragmen terhadap versi pustaka terkini sebelum dipakai karena beberapa antarmuka telah berubah sejak terbit.

Kode buatan model bahasa. Asisten dapat menulis banyak kode analitis lebih cepat daripada Anda, tetapi juga kode look-ahead yang tampak benar serta fungsi yang tidak ada pada versi pustaka Anda. Tanpa keterampilan tingkat 1, keduanya tidak terdeteksi. Kemampuan serta keterbatasan model bahasa dibahas dalam AI dan LLM dalam perdagangan.

Urutan belajar yang masuk akal – setiap langkah membangun yang sebelumnya dan diakhiri sesuatu yang bekerja:

  1. Dasar bahasa – variabel, daftar, fungsi, kondisi. Cukup untuk membaca skrip orang lain tanpa menebak
  2. pandas pada data sendiri – muat ekspor jurnal, hitung statistik menurut hari dan instrumen. Jika lebih suka buku daripada dokumentasi, tahap ini dibahas lengkap buku McKinney, pembuat pandas [10]
  3. Grafik – buat kurva ekuitas dan drawdown dari hasil sebelumnya
  4. Pemeriksaan mutu data – ulangi contoh bagian 4 pada berkas sendiri: zona waktu, duplikat, celah
  5. Integrasi baca saja – ambil data API akun latihan dengan token tanpa hak transaksi jika penyedia membedakannya

Urutannya disengaja. Langkah kelima baru setelah Anda mampu memeriksa data unduhan — jika tidak, yang diotomatiskan adalah kesalahan, bukan pekerjaan.

Penggunaan paling bernilai justru paling tidak spektakuler. Analisis jurnal sendiri dalam pandas menunjukkan sumber hasil sebenarnya serta sumber yang hanya Anda bayangkan. Biasanya lebih bermanfaat daripada membuat program eksekusi dan hanya memerlukan tingkat 2 — bukan karena diukur seseorang, melainkan inilah satu-satunya analisis pada data sendiri, bukan orang lain.

Mulai dari satu tugas: muat data sendiri, hitung sesuatu yang dapat diperiksa manual pada tiga baris, lalu bandingkan. Jika hasil Python cocok dengan hitungan jari, fondasinya ada. Jika tidak, Anda menemukan bug pertama — awal yang lebih baik daripada skrip langsung bekerja tetapi tidak dipahami.

Python tidak memperbaiki strategi buruk. Paling jauh ia mempercepat penemuan bahwa strategi itu buruk.

FAQ – Python dalam perdagangan

Apakah Python cukup cepat untuk otomatisasi perdagangan?
Bergantung letak hambatannya. Pada strategi masuk pembukaan H1 berikutnya, komputasi Python dapat diabaikan dibanding perjalanan order melalui jaringan dan server broker. Untuk perdagangan dalam candle satu menit, seluruh rantai penting: usia data saat keputusan, waktu respons endpoint, dan waktu konfirmasi pengisian. Jangan mengutipnya dari ingatan — ukur pada rantai serta broker sendiri, catat tanggal dan jumlah percobaan. Python tidak cocok untuk perdagangan berlatensi sangat rendah, tetapi itu kelas masalah berbeda dari perdagangan ritel.
Berapa lama belajar Python hingga berguna untuk perdagangan?
Untuk membaca kode orang lain dan mengubah parameter — beberapa minggu kerja teratur. Untuk mandiri menganalisis jurnal dengan pandas dan grafik — beberapa bulan. Bergantung titik awal serta jam per minggu, jadi anggap orde besaran. Tidak perlu kursus seluruh bahasa: kelas, decorator, atau konkurensi tidak dibutuhkan untuk memuat berkas dan menghitung statistik. Belajar tercepat melalui data sendiri karena segera tahu hasilnya masuk akal atau tidak.
Dapatkah Python dihubungkan ke akun MT4 atau MT5?
Untuk MT5 tersedia paket resmi MetaTrader5 yang dipasang melalui pip — hanya pada Windows: macOS dan Linux tidak memiliki instalasi resmi, sehingga terminal bersama Python harus dijalankan melalui Wine atau lapisan perantara tersendiri [FITUR BERGANTUNG PADA VERSI/LINGKUNGAN]. Pahami pula sifat paket: berkomunikasi dengan terminal MetaTrader 5 pada komputer yang sama, bukan langsung ke server broker. Terminal harus terpasang tetapi tidak perlu sudah aktif: dokumentasi menyebut pemanggilan initialize() menjalankan terminal bila perlu; parameter login opsional karena jika tidak diberikan, data tersimpan di terminal digunakan [18]. Bukan detail kosmetik: skrip terjadwal dapat membuka terminal dan login tanpa keterlibatan Anda — nyaman, tetapi “terminal tadi tertutup” bukan pengaman. Integrasi diperkenalkan dalam versi terminal 2019 (build 2085) [7]. Ini bukan REST API independen dari broker dan jangan disebut demikian. Alternatifnya jembatan cloud eksternal, yang menambah mata rantai serta akses akun bagi pihak ketiga. Jalur awal termudah dan teraman tetap searah: ekspor terminal ke berkas, analisis Python, keputusan serta eksekusi manusia.
Di mana menyimpan kunci API dan apa yang dilakukan jika bocor?
Hanya dalam variabel lingkungan atau konfigurasi di luar repositori yang dicantumkan dalam .gitignore. Jangan dalam kode, komentar, atau notebook — sel notebook juga menyimpan keluaran sehingga token tercetak menetap di berkas. Jika masuk repositori, menghapus baris sia-sia karena tetap di riwayat. Satu-satunya respons efektif adalah mencabut kunci di broker lalu membuat yang baru, baru setelah itu merapikan riwayat. Terpisah, batas ukuran posisi di sisi akun patut ditetapkan sebagai perlindungan kedua yang independen dari kode.
Apakah kode artikel ini sudah diperiksa?
Ya — setiap blok kode, dijelaskan satu per satu. Contoh pemuatan CSV bagian 4 dan perbandingan pergeseran sinyal bagian 9 dijalankan pada lingkungan tercantum (Python 3.12.10, pandas 3.0.5) dan hasilnya keluaran aktual. Blok konfigurasi diuji pada kelima cabangnya, dari variabel lingkungan hilang hingga penolakan nama lingkungan tak dikenal. Gerbang lapisan eksekusi diuji offline tanpa jaringan atau rahasia. Fragmen API dijalankan dengan mock sesi HTTP dalam memori, tanpa server lokal atau koneksi jaringan — bukan koneksi ke 127.0.0.1 ataupun broker; skenario respons yang didokumentasikan diuji, termasuk batas percobaan habis dan pengecualian koneksi. Namun tidak ada satu baris pun yang mengirim order: cabang pengiriman diganti sketsa struktural karena kode transaksi tidak diuji pada akun orang lain, dan fungsi siap pakai tanpa uji hanya mengajarkan kepercayaan pada kode belum terverifikasi. Menjalankan kode bukan mengonfirmasi asumsi server orang lain: periksa jalur endpoint, bentuk respons, serta batas dalam dokumentasi broker sendiri dan akun latihan.
Dapatkah strategi repositori publik dipercaya?
Tidak tanpa pemeriksaan sendiri, bukan karena penulis berniat buruk. Kekurangan lazim: pergeseran sinyal tidak diperiksa, tanpa model biaya, lingkungan serta versi tidak dijelaskan, dan jumlah varian sebelum hasil terpilih tidak disebutkan. Sebelum menjalankan: baca asal sinyal dan periode hasil, periksa keberadaan biaya, buat ulang lingkungan dari berkas dependensi lalu jalankan terisolasi. Jika versi pustaka tidak ada, hasil tidak dapat direproduksi — tidak dapat diverifikasi maupun dibantah.

Sumber dan referensi

  1. Dokumentasi pandas 3.0.5 – pandas.pydata.org/docs – struktur data, indeks waktu dan zona waktu, resample, penanganan nilai hilang; versi diperiksa 2026-08-22
  2. pandas – catatan rilis 2.2.0 dan 3.0.0 – pandas.pydata.org/docs/whatsnew – penghentian bertahap parameter method dalam fillna() dan perubahan perilaku default
  3. NumPy – dokumentasi dan catatan rilis – numpy.org/doc – vektorisasi, generator angka acak dan seed
  4. Dokumentasi Python – docs.python.org/3 – modul venv, logging dan timeit, penanganan pengecualian
  5. OANDA v20 – Praktik Terbaik – developer.oanda.com/rest-live-v20/best-practices – laju permintaan yang direkomendasikan pada koneksi terbentuk (100/s) serta koneksi baru (2/s); diperiksa 2026-08-22
  6. MQL5 – integrasi Python – mql5.com/en/docs/python_metatrader5 – paket MetaTrader5 dan komunikasinya dengan terminal
  7. MetaTrader 5 – catatan rilis build 2085 – metatrader5.com/en/releasenotes/terminal/2085 – pengenalan integrasi Python terminal
  8. cTrader Algo – dokumentasi – help.ctrader.com/ctrader-algo – dukungan bawaan C# dan Python untuk cBot, indikator, dan plugin; status 2026-08-22
  9. TA-Lib (binding Python) – pypi.org/project/TA-Lib – paket biner PyPI, instalasi tanpa kompilasi pustaka C
  10. McKinney, W. – Python untuk Analisis Data, O'Reilly, edisi ke-3, 2022 – pandas, numpy, deret waktu
  11. Hilpisch, Y. – Python untuk Keuangan, O'Reilly, edisi ke-2, 2018 – penggunaan Python dalam keuangan; verifikasi fragmen kode terhadap versi pustaka terkini
  12. Bailey, D. H., López de Prado, M. – Deflated Sharpe Ratio, Journal of Portfolio Management, 2014 – koreksi rasio Sharpe terhadap jumlah varian yang diuji
  13. PyPI – halaman paket dan antarmuka JSON registri – pypi.org – tanggal dan nomor rilis terbaru pustaka dalam teks (mplfinance, streamlit, openpyxl, pandas-ta, backtrader, vectorbt, backtesting.py, ib_insync, ib_async); diperiksa 2026-10-08
  14. OANDA v20 – API Order dan Transaksi – developer.oanda.com/rest-live-v20/transaction-df – clientExtensions sebagai data khusus opsional pada order, serta peringatan pengaturannya untuk akun terkait MT4; tanpa jaminan idempotensi order pasar terdokumentasi; diperiksa 2026-08-22
  15. OANDA v20 – Panduan Pengembangan – developer.oanda.com/rest-live-v20/development-guide – batas REST yang diberlakukan sebesar 120 permintaan per detik dengan respons HTTP 429, 20 stream aktif dan 2 koneksi baru per detik, dihitung per IP pengirim; diperiksa 2026-08-22
  16. Microsoft – Spesifikasi dan Batas Excel – support.microsoft.com – ukuran lembar: 1.048.576 baris × 16.384 kolom; diperiksa 2026-08-22
  17. Swiss National Bank – siaran pers 15 Januari 2015 – snb.ch – penghapusan kurs minimum 1,20 franc per euro dan penurunan bunga ke −0,75 persen
  18. MQL5 – MetaTrader5.initialize() – mql5.com/en/docs/python_metatrader5/mt5initialize_py – menjalankan terminal saat membuat koneksi jika diperlukan serta parameter opsional path, login, password dan server dengan data tersimpan dalam terminal; diperiksa 2026-08-22

Kemajuan belajar Anda

Selesai membaca pelajaran ini? Tandai sendiri. Membuka atau menggulir halaman tidak menandainya sebagai selesai.

Kemajuan memerlukan JavaScript dan penyimpanan browser.

Disimpan hanya di browser ini. Tanpa akun atau sinkronisasi antarperangkat. Menghapus data situs dapat menghapus kemajuan Anda.

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.