I. Mengapa Perlu Penambangan Faktor Otomatis
Jika Anda pernah bersentuhan dengan perdagangan kuantitatif, pasti pernah mendengar istilah "faktor". Apa itu faktor? Sederhananya, faktor adalah sinyal pasar yang diekspresikan dalam bentuk data. Misalnya momentum harga, anomali volume perdagangan, posisi Bollinger Bands — digunakan untuk memprediksi apakah suatu koin akan naik atau turun dalam beberapa waktu ke depan.
Kedengarannya sederhana, tetapi siapa pun yang benar-benar melakukan penelitian faktor tahu betapa sulitnya hal ini:
Pengetahuan keuangan yang solid dan latar belakang statistika matematika yang mendalam
Data historis bersih dalam jumlah besar
Kerangka backtest yang ketat
Dan masih harus menghadapi masalah yang tidak bisa dihindari: Faktor akan mengalami peluruhan (decay)
Sinyal yang efektif hari ini, mungkin besok sudah tidak berguna lagi — karena pelaku pasar belajar, beradaptasi, dan mengarbitrase pola tersebut. Jadi penambangan faktor bukanlah pekerjaan sekali jadi, melainkan perlu diulang secara berkelanjutan.
Yang diperkenalkan dalam artikel ini adalah sebuah sistem yang mengotomatiskan proses tersebut: melakukan siklus penambangan faktor → validasi → eliminasi → sintesis sinyal → eksekusi perdagangan secara berulang pada interval tetap. Menggantikan pengulangan manual dengan iterasi mesin, sehingga strategi selalu mengikuti ritme perubahan pasar.
II. Arsitektur Sistem Secara Keseluruhan
Alur penambangan faktor tradisional adalah: peneliti mengajukan hipotesis → menulis kode → menjalankan backtest → menyaring → online → beberapa bulan kemudian ditemukan tidak efektif → mulai lagi. Seluruh siklus bisa memakan waktu berminggu-minggu bahkan berbulan-bulan.
Sistem ini memampatkan seluruh siklus menjadi eksekusi otomatis pada interval tetap:
| Langkah | Modul | Keterangan |
|---|---|---|
| Langkah 1 | Mendapatkan kumpulan aset | Menyaring kontrak perpetual dengan likuiditas tinggi berdasarkan volume perdagangan, mendeteksi kondisi pasar |
| Langkah 2 | Memeriksa kumpulan faktor | Menganalisis kesehatan faktor saat ini, menentukan arah eksplorasi putaran ini |
| Langkah 3 | AI menghasilkan faktor | Dalam kerangka batasan, meminta AI menghasilkan kandidat faktor dari dimensi baru |
| Langkah 4 | Verifikasi IC | Menghitung information coefficient dengan data historis, mengeliminasi faktor yang tidak efektif |
| Langkah 5 | Filter korelasi & eliminasi terbawah | Menghapus faktor yang tumpang tindih informasinya, menjaga kumpulan faktor tetap ramping namun cukup |
| Langkah 6 | Sintesis sinyal & eksekusi | Mensintesis skor dengan bobot, sinyal di atas ambang memicu penyesuaian portofolio |
Sistem digerakkan oleh dua penjadwal: pemicu lambat menjalankan satu siklus lengkap penambangan faktor pada level jam; pemicu cepat memeriksa status posisi setiap detik, menangani take profit & stop loss serta penyegaran dashboard.
III. Penjelasan Detail Setiap Modul dan Kode Inti
3.1 Mendapatkan Kumpulan Aset
Setiap awal putaran, sistem mengambil data real-time semua kontrak perpetual dari bursa, mengurutkan berdasarkan volume perdagangan dan mengambil N teratas. Likuiditas adalah prasyarat efektivitas faktor — koin kecil dengan volume jarang, sinyal apa pun mudah terdistorsi.
Secara bersamaan, mendeteksi persentil volatilitas historis garis 4 jam BTC untuk menentukan kondisi pasar secara keseluruhan (normal / high_vol / low_vol / volatile), penilaian ini akan secara langsung memengaruhi preferensi arah faktor yang dihasilkan oleh AI.
javascript
// Menyaring aset dengan likuiditas tinggi berdasarkan volume perdagangan
const topN = $vars.topN || 150;
const tickers = exchange.GetTickers();
const filtered = tickers
.filter(t => t.Symbol.endsWith('USDT.swap'))
.map(t => ({ symbol: t.Symbol, quoteVolume: t.Last * t.Volume }))
.sort((a, b) => b.quoteVolume - a.quoteVolume)
.slice(0, topN)
.map(t => t.symbol);
_G('afi_symbolPool', JSON.stringify(filtered));
// Mendeteksi persentil volatilitas BTC, menentukan kondisi pasar
const btcR = exchange.GetRecords('BTC_USDT.swap', PERIOD_H4);
const n = btcR.length;
const returns20 = [];
for (let i = n - 20; i < n; i++)
returns20.push(Math.abs((btcR[i].Close - btcR[i-1].Close) / btcR[i-1].Close));
const avgVol = returns20.reduce((a, b) => a + b, 0) / returns20.length;
// Membandingkan dengan volatilitas seluruh sejarah, menentukan persentil
const allVols = [];
for (let i = 1; i < n; i++)
allVols.push(Math.abs((btcR[i].Close - btcR[i-1].Close) / btcR[i-1].Close));
allVols.sort((a, b) => a - b);
let btcVolPercentile = allVols.findIndex(v => v >= avgVol) / allVols.length;
let marketState = 'normal';
if (btcVolPercentile > 0.8) marketState = 'high_vol';
else if (btcVolPercentile < 0.3) marketState = 'low_vol';
_G('afi_marketState', marketState);
_G('afi_btcVolPct', btcVolPercentile.toFixed(2));
3.2 Memeriksa Status Kumpulan Faktor
Sebelum meminta AI menghasilkan faktor baru, sistem terlebih dahulu memeriksa kesehatan kumpulan faktor saat ini: faktor mana yang IC-nya terus menurun akhir-akhir ini (peluruhan), dimensi mana yang belum tercakup. Informasi ini akan langsung diteruskan ke AI sebagai batasan, untuk menghindari eksplorasi ulang arah yang sudah tidak efektif.
javascript
const factorPool = JSON.parse(_G('afi_factorPool') || '[]');
const icHistory = JSON.parse(_G('afi_icHistory') || '{}');
const icDecayWindow = $vars.icDecayWindow || 48; // Panjang jendela terkini
const icDecayThreshold = $vars.icDecayThreshold || -0.01; // Ambang deteksi peluruhan
const targetFactorCount = $vars.targetFactorCount || 10;
const degradedFactors = [];
for (const factor of factorPool) {
const icArr = icHistory[factor.name] || [];
if (icArr.length >= 20) {
const window = Math.min(icArr.length, icDecayWindow);
const recentAvg = icArr.slice(-window).reduce((a, b) => a + b, 0) / window;
if (recentAvg < icDecayThreshold)
degradedFactors.push({
name: factor.name,
recentIC: recentAvg.toFixed(4),
rationale: factor.rationale
});
}
}
// Menentukan secara dinamis berapa banyak faktor baru yang perlu dieksplorasi pada putaran ini
const explorationBuffer = $vars.explorationBuffer || 3;
const explorationCount = Math.max(
explorationBuffer,
targetFactorCount - validCount + explorationBuffer
);
const action = factorPool.length === 0 ? 'generate_initial' : 'iterate_factors';
3.3 Membangun Prompt, Membiarkan AI Menemukan Faktor
AI tidak menerima tugas terbuka, melainkan kerangka dengan batasan. Prompt berisi: kondisi pasar saat ini, daftar faktor yang sudah ada (dilarang duplikat), faktor yang baru saja mengalami peluruhan (dilarang penyetelan halus), dimensi yang sudah tercakup, dimensi yang belum dieksplorasi.
Dengan demikian, kandidat faktor yang dihasilkan benar-benar merupakan upaya ke arah baru, bukan sekadar menjalankan ulang faktor yang sudah ada dengan parameter berbeda.
javascript
// Iteration Mode Prompt key snippet
const usedDimensions = factorPool
.map(f => f.name + '(' + (f.rationale || '') + ')')
.join(', ') || 'Tidak ada';
const validSummary = validFactors.map(f => {
const arr = icHistory[f.name] || [];
const avg = arr.length > 0
? (arr.reduce((a,b) => a+b, 0) / arr.length).toFixed(4) : 'N/A';
const recent = arr.length >= 20
? (arr.slice(-20).reduce((a,b) => a+b, 0) / 20).toFixed(4) : 'N/A';
return f.name + ': IC historis=' + avg + ' IC terkini=' + recent + ' | Logika: ' + f.rationale;
}).join('\n') || 'Tidak ada';
const degradedSummary = degradedFactors.length > 0
? degradedFactors.map(f =>
f.name + ': IC terkini=' + f.recentIC + ' | Logika asli: ' + f.rationale
).join('\n')
: 'Tidak ada faktor yang menurun pada putaran ini';
prompt += '【Faktor valid saat ini (tidak perlu menghasilkan varian)】\n' + validSummary + '\n\n';
prompt += '【Faktor yang baru saja menurun (dilarang melakukan fine-tuning pada dimensi ini)】\n' + degradedSummary + '\n\n';
prompt += '【Dimensi yang sudah tercakup (dilarang duplikasi)】\n' + usedDimensions + '\n\n';
prompt += '【Dimensi yang belum dieksplorasi (prioritaskan dari sini)】\n' + unusedSample + '\n\n';
prompt += 'Hasilkan ' + explorationCount + ' faktor arah baru:\n';
prompt += '1. Harus benar-benar berbeda dari dimensi yang sudah tercakup, dilarang melakukan fine-tuning pada faktor yang gagal\n';
prompt += '2. Prioritaskan dari dimensi yang belum dieksplorasi\n';
prompt += '3. Prioritaskan merancang faktor kombinasi non-linear\n';
prompt += '4. Rancang khusus untuk kondisi pasar ' + marketState + ' saat ini\n';
System Prompt AI telah menyertakan spesifikasi fungsi TA lengkap dari platform inventor, batasan format kode, pengetahuan apriori pasar kripto, serta daftar dimensi faktor yang dapat dieksplorasi (konten lengkap lihat kode sumber strategi). Format output secara ketat adalah JSON murni (tanpa pembungkus Markdown):
json
{
"factors": [
{
"name": "MomentumAcceleration",
"rationale": "Akselerasi momentum jangka pendek, menangkap titik balik inersia mengejar kenaikan investor ritel",
"code": "(records[n-1].Close - records[n-6].Close)/records[n-6].Close - (records[n-2].Close - records[n-7].Close)/(records[n-7].Close + 0.0001)",
"direction": 1,
"type": "exploration"
}
]
}
3.4 Verifikasi IC: Data yang Bicara, Bukan Intuisi
IC (Information Coefficient) mengukur: seberapa tinggi korelasi antara peringkat cross-sectional yang dihitung menggunakan faktor dengan peringkat pergerakan harga riil pada candle K berikutnya. Semakin tinggi IC, semakin akurat prediksi faktor tersebut.
Metode verifikasinya adalah walk-forward: ambil beberapa ratus candle K masa lalu, pada setiap titik waktu t, hitung nilai faktor menggunakan data pada waktu t-1, prediksi pergerakan candle K ke-t. Urutan waktu disejajarkan secara ketat, tanpa future function.
javascript
function calcRankICFull(code, symRecords, factorName) {
const syms = Object.keys(symRecords);
const icList = [];
const minLen = 30;
const allLengths = syms.map(s => symRecords[s].length);
const minSymLen = Math.min(...allLengths);
const testLen = Math.min(500, minSymLen - 1);
for (let t = minLen; t < testLen; t++) {
const fVals = [], nRets = [];
for (const sym of syms) {
const fullRecords = symRecords[sym];
// Gunakan data periode t-1 untuk menghitung faktor (slice(0, t) tidak menyertakan candle K ke-t)
const records = fullRecords.slice(0, t);
const n = records.length;
const v = (function() { return eval(code); })();
if (isNaN(v) || !isFinite(v)) continue;
fVals.push({ sym, val: v });
// Prediksi return riil candle K ke-t
nRets.push({
sym,
ret: (fullRecords[t].Close - fullRecords[t-1].Close) / fullRecords[t-1].Close
});
}
if (fVals.length < 8) continue;
// Hitung Rank IC (koefisien korelasi Spearman)
const fRank = {}, rRank = {};
[...fVals].sort((a,b) => a.val - b.val).forEach((x,i) => fRank[x.sym] = i);
[...nRets].sort((a,b) => a.ret - b.ret).forEach((x,i) => rRank[x.sym] = i);
const ss = fVals.map(x => x.sym);
const fr = ss.map(s => fRank[s]);
const rr = ss.map(s => rRank[s]);
const n2 = ss.length;
const fm = fr.reduce((a,b) => a+b, 0) / n2;
const rm = rr.reduce((a,b) => a+b, 0) / n2;
const num = fr.map((f,i) => (f-fm) * (rr[i]-rm)).reduce((a,b) => a+b, 0);
const den = Math.sqrt(
fr.map(f => (f-fm)**2).reduce((a,b) => a+b, 0) *
rr.map(r => (r-rm)**2).reduce((a,b) => a+b, 0)
);
if (den > 0) icList.push(num / den);
}
const avgIC = icList.length > 0
? icList.reduce((a,b) => a+b, 0) / icList.length : 0;
return { avgIC, icList };
}
Ambang batas IC dikendalikan oleh variabel
$vars.icThreshold, default 0.02. Ini adalah ambang masuk yang relatif longgar, cocok untuk menyaring faktor yang jelas-jelas tidak efektif dengan cepat; jika diperlukan kontrol signifikansi statistik yang lebih ketat, nilai ini dapat dinaikkan sesuai situasi aktual. Faktor yang tidak melewati ambang batas, tidak peduli seberapa sempurna logikanya, langsung dieliminasi.
3.5 Filter Korelasi & Eliminasi Peringkat Terbawah
Faktor yang telah lulus verifikasi IC masih harus melewati dua rintangan:
Pertama: Filter Korelasi. Jika skor cross-sectional dari dua faktor sangat mirip (|corr| > ambang batas), pertahankan faktor dengan IC lebih tinggi, buang yang lain. Seperti dua suara yang berasal dari pemikiran orang yang sama, cukup digabungkan menjadi satu suara, suara tambahan tidak menambah sudut pandang.
Kedua: Eliminasi Peringkat Terbawah. Pool faktor memiliki batas kapasitas, kelebihan akan diurutkan berdasarkan kinerja, yang terburuk akan keluar. Faktor dengan IC yang terus menurun akhir-akhir ini akan berpartisipasi dalam peringkat menggunakan IC terkini, bukan rata-rata historis, sehingga menanggung tekanan eliminasi yang lebih besar.
javascript
// Filter korelasi (pertahankan IC tertinggi, buang faktor redundan yang sangat berkorelasi)
const corrThreshold = $vars.corrThreshold || 0.7;
survivedFactors.sort((a, b) => b.icAvg - a.icAvg); // Urutkan berdasarkan IC menurun terlebih dahulu
const decorrelatedFactors = [];
for (const factor of survivedFactors) {
let isRedundant = false;
for (const selected of decorrelatedFactors) {
const corr = Math.abs(calcCorrelation(
factorScoresMap[factor.name],
factorScoresMap[selected.name]
));
if (corr > corrThreshold) {
// Catat faktor terkait yang diserap (untuk tampilan dashboard)
selected.corrGroup = (selected.corrGroup ? selected.corrGroup + ',' : '')
+ factor.name;
isRedundant = true;
break;
}
}
if (!isRedundant) decorrelatedFactors.push({ ...factor, corrGroup: '' });
}
// Eliminasi peringkat terbawah: faktor yang memburuk menggunakan IC terbaru, bukan rata-rata historis untuk peringkat
const targetFactorCount = $vars.targetFactorCount || 10;
decorrelatedFactors.sort((a, b) => {
const scoreA = a.isDecaying ? a.recentIC : a.icAvg;
const scoreB = b.isDecaying ? b.recentIC : b.icAvg;
return scoreB - scoreA;
});
const finalPool = decorrelatedFactors.slice(0, targetFactorCount);
_G('afi_factorPool', JSON.stringify(finalPool));
Catatan: Perhitungan korelasi didasarkan pada skor faktor di cross-section saat ini, terkadang mungkin ada kesalahan sesaat. Pendekatan yang lebih kokoh adalah mengambil rata-rata korelasi dari beberapa cross-section historis, yang merupakan arah perbaikan di masa depan.
3.6 Sintesis Sinyal dan Eksekusi Rebalancing
Setelah kumpulan faktor stabil, sistem menghitung skor komposit untuk setiap instrumen: nilai cross-section setiap faktor dinormalisasi dengan Z-score, kemudian ditimbang dan dijumlahkan berdasarkan IC terbaru masing-masing – semakin baik kinerja suatu faktor, semakin besar bobotnya; bobot faktor dengan IC terbaru negatif diatur menjadi nol.
javascript
// Bobot faktor: penimbang IC terbaru (bobot faktor IC negatif diatur ke nol)
const weights = {};
let totalW = 0;
for (const f of factorPool) {
const arr = icHistory[f.name] || [];
const recentArr = arr.slice(-48);
const recentIC = recentArr.length > 0
? recentArr.reduce((a,b) => a+b, 0) / recentArr.length : 0;
const w = Math.max(0, recentIC); // IC negatif → bobot 0
weights[f.name] = w;
totalW += w;
}
if (totalW > 0)
Object.keys(weights).forEach(k => weights[k] /= totalW);
else
factorPool.forEach(f => weights[f.name] = 1 / factorPool.length);
// Normalisasi Z-score
function zscore(fname) {
const vals = validSyms
.map(s => ({ sym: s, val: rawMatrix[s][fname] }))
.filter(x => x.val !== null);
if (vals.length < 5) return {};
const mean = vals.reduce((a,b) => a + b.val, 0) / vals.length;
const std = Math.sqrt(vals.reduce((a,b) => a + (b.val - mean)**2, 0) / vals.length);
const r = {};
vals.forEach(x => r[x.sym] = std > 0 ? (x.val - mean) / std : 0);
return r;
}
// Skor sintesis
const scores = {};
for (const sym of validSyms) {
let score = 0;
for (const f of factorPool) {
const z = zscore(f.name)[sym];
if (z !== undefined) score += weights[f.name] * f.direction * z;
}
scores[sym] = score;
}
// Filter ambang batas: sinyal yang tidak jelas langsung dilewati, tidak masuk posisi
const longShortN = $vars.longShortN || 5;
const longThreshold = $vars.longThreshold || 0.3;
const shortThreshold = $vars.shortThreshold || -0.3;
const sorted = Object.keys(scores).sort((a,b) => scores[b] - scores[a]);
const longList = sorted.filter(s => scores[s] >= longThreshold).slice(0, longShortN);
const shortList = sorted.slice().reverse()
.filter(s => scores[s] <= shortThreshold).slice(0, longShortN);
Saat mengeksekusi rebalancing, pertama-tama tutup posisi lama yang tidak termasuk dalam daftar putaran ini, kemudian buka sinyal baru secara proporsional berdasarkan ekuitas akun:
javascript
const positionRatio = $vars.positionRatio || 0.8; // Rasio penggunaan total ekuitas
const maxLeverage = $vars.maxLeverage || 3;
const account = exchange.GetAccount();
const equity = account.Equity || account.Balance;
const perAmt = (equity * positionRatio) / (longList.length + shortList.length);
// Tutup posisi lama yang tidak ada dalam set target
const targetSet = new Set([...longList, ...shortList]);
for (const sym of Object.keys(currentHoldings)) {
if (!targetSet.has(sym)) {
const pos = currentHoldings[sym];
const isLong = pos.Type === PD_LONG || pos.Type === 0;
exchange.CreateOrder(sym, isLong ? 'closebuy' : 'closesell', -1, Math.abs(pos.Amount));
// Hapus status trailing stop-profit
const cm = sym.match(/^(.+)_USDT/);
if (cm) { _G(cm[1] + '_maxpnl', null); _G(cm[1] + '_trail', null); }
}
}
// Buka posisi baru berdasarkan sinyal (menggunakan order pasar, -1 berarti pasar)
function openPos(sym, isLong) {
exchange.SetMarginLevel(sym, maxLeverage);
const market = allMarkets[sym];
const price = exchange.GetTicker(sym).Last;
const ctVal = (market.CtVal && market.CtVal > 0) ? market.CtVal : 1;
const amtPrec = market.AmountPrecision !== undefined ? market.AmountPrecision : 0;
const minQty = (market.MinQty && market.MinQty > 0) ? market.MinQty : 1;
const maxQty = (market.MaxQty && market.MaxQty > 0) ? market.MaxQty : 999999;
let qty = _N(perAmt / price / ctVal, amtPrec);
qty = Math.min(Math.max(qty, minQty), maxQty);
exchange.CreateOrder(sym, isLong ? 'buy' : 'sell', -1, qty);
}
3.7 Pemantauan Posisi: Stop Loss / Take Profit / Trailing Stop Profit Dinamis
Pemicu cepat berjalan setiap detik, memantau laba mengambang semua posisi secara real-time, dan menjalankan tiga logika keluar:
- Stop Loss Tetap: Kerugian mengambang melebihi
STOP_LOSS_PCT(default 5%) akan keluar secara otomatis - Take Profit Tetap: Keuntungan mengambang melebihi
TAKE_PROFIT_PCT(default 10%) akan keluar secara otomatis - Trailing Stop Profit Dinamis: Diaktifkan ketika keuntungan mengambang mencapai
TRAIL_TRIGGER(3%), dan ambang penarikan disesuaikan secara dinamis berdasarkan keuntungan mengambang tertinggi
javascript
const STOP_LOSS_PCT = $vars.stopLossPct || 5;
const TAKE_PROFIT_PCT = $vars.takeProfitPct || 10;
const TRAIL_TRIGGER = 3; // Aktifkan trailing stop setelah keuntungan mengambang mencapai 3%
// Ambang penarikan dinamis: semakin tinggi keuntungan maksimum, semakin besar ruang penarikan yang diizinkan
function getDynamicTrailDrawdown(maxPnl) {
if (maxPnl >= 7) return 3; // Keuntungan maks >=7%, izinkan penarikan 3%
if (maxPnl >= 4) return 2; // Keuntungan maks >=4%, izinkan penarikan 2%
return 1.5; // Kasus lainnya, izinkan penarikan 1.5%
}
function monitorTPSL(positions, tickers) {
for (const pos of (positions || [])) {
if (Math.abs(pos.Amount) === 0) continue;
const cm = pos.Symbol.match(/^(.+)_USDT/); if (!cm) continue;
const coin = cm[1];
const ticker = tickers[coin + '_USDT.swap']; if (!ticker) continue;
const isLong = pos.Type === PD_LONG || pos.Type === 0;
const cur = ticker.Last;
const ent = pos.Price;
const amt = Math.abs(pos.Amount);
const pnlPct = (cur - ent) * (isLong ? 1 : -1) / ent * 100;
// Lacak keuntungan mengambang tertinggi
let maxPnl = _G(coin + '_maxpnl');
if (maxPnl === null) { maxPnl = pnlPct; _G(coin + '_maxpnl', maxPnl); }
else if (pnlPct > maxPnl) { maxPnl = pnlPct; _G(coin + '_maxpnl', maxPnl); }
// Aktifkan trailing stop-profit
if (!_G(coin + '_trail') && maxPnl >= TRAIL_TRIGGER) {
_G(coin + '_trail', true);
Log(coin + ' mengaktifkan trailing stop-profit, laba mengambang: +' + pnlPct.toFixed(2) + '%');
}
const trailDrawdown = getDynamicTrailDrawdown(maxPnl);
let reason = null;
if (_G(coin + '_trail') && (maxPnl - pnlPct) >= trailDrawdown)
reason = 'Trailing stop-profit (penarikan ' + (maxPnl - pnlPct).toFixed(2) + '%, ambang ' + trailDrawdown + '%)';
if (!reason && pnlPct <= -STOP_LOSS_PCT) reason = 'Stop loss(' + pnlPct.toFixed(2) + '%)';
if (!reason && pnlPct >= TAKE_PROFIT_PCT) reason = 'Take profit(' + pnlPct.toFixed(2) + '%)';
if (reason) {
exchange.CreateOrder(pos.Symbol, isLong ? 'closebuy' : 'closesell', -1, amt);
Log(coin, 'Terpicu', reason);
_G(coin + '_maxpnl', null); _G(coin + '_trail', null);
}
}
}
4. Keputusan Desain Kunci
4.1 Mengapa Menggunakan Rank IC (bukan Pearson IC)
Rank IC menggunakan peringkat alih-alih nilai mentah untuk menghitung korelasi, sehingga secara alami lebih kokoh terhadap nilai ekstrem (outlier) dalam faktor. Distribusi harga di pasar kripto memiliki ekor yang tebal (fat tail), sehingga Pearson IC mudah terdistorsi oleh beberapa candle K-line ekstrem, sedangkan Rank IC menawarkan stabilitas yang lebih baik.
4.2 Penyelarasan Urutan Waktu yang Ketat, Menghilangkan Fungsi Masa Depan
Baik validasi IC maupun perhitungan sinyal online secara seragam menggunakan nilai faktor periode t-1 untuk memprediksi pengembalian periode t: saat validasi, records yang diberikan adalah fullRecords.slice(0, t), sehingga data masa depan terpotong secara fisik. Kode faktor yang dihasilkan AI, tidak peduli bagaimana mereferensikan records[n], hanya akan mengakses data historis hingga t-1; secara online, candle K-line terakhir dihapus (slice(0, n-1)) untuk menghitung nilai faktor, memprediksi naik/turunnya candle K-line berikutnya. Logika keduanya identik, menghindari inflasi IC akibat melihat data masa depan.
4.3 Pembobotan IC Terkini, Bobot Beradaptasi dengan Pasar
Bobot faktor tidaklah tetap, melainkan disesuaikan secara dinamis berdasarkan IC terbaru. Ketika suatu faktor mulai kehilangan efektivitasnya (IC terbaru menurun), bobotnya dalam sintesis sinyal secara otomatis berkurang hingga nol, dan sistem dapat melakukan rebalancing bobot tanpa intervensi manual.
4.4 Arsitektur Pemicu Ganda
Iterasi faktor adalah tugas komputasi berat (pengambilan candlestick + backtest IC + panggilan AI), cukup dijalankan sekali per jam; perlindungan posisi adalah tugas yang sensitif terhadap waktu, membutuhkan respons dalam hitungan detik. Memisahkan keduanya ke dalam pemicu dengan frekuensi berbeda, untuk menghindari saling memblokir.
5. Pengamatan Langsung: Proses Iterasi Faktor
Setelah berjalan langsung selama dua hari, fenomena berikut teramati:
- Faktor yang masuk pool pada tahap awal umumnya memiliki IC historis antara 0,04 hingga 0,07, melewati ambang batas dasar.
- Seiring berjalannya iterasi, IC terbaru hampir semua faktor menurun, ada yang dari 0,06 turun menjadi 0,008, ada yang jatuh ke nilai negatif. Ini menunjukkan bahwa sinyal yang ditangkap faktor-faktor tersebut mulai kehilangan efektivitas di lingkungan pasar saat ini.
- Setelah sistem mendeteksi penurunan, pada putaran berikutnya ia memprioritaskan eksplorasi dimensi yang belum tercakup, untuk mencari faktor kandidat baru sebagai pengganti. Seluruh proses tidak memerlukan intervensi manual.
Dua hari adalah waktu yang relatif singkat, belum cukup untuk memverifikasi secara menyeluruh apakah kemampuan adaptif sistem benar-benar efektif. Di sini hanya dicatat bahwa sistem menjalankan tindakan iterasi sesuai yang diharapkan. Kesimpulan yang lebih bermakna membutuhkan pengamatan berkelanjutan dalam jangka waktu yang lebih panjang. Namun proses itu sendiri sudah menunjukkan bahwa logika dasar desain sistem berjalan: ia tidak akan bertahan pada sinyal yang sudah tidak efektif, melainkan terus mencoba dimensi baru.
6. Penutup
Membangun sistem ini bukan untuk membuktikan bahwa AI bisa mengalahkan pasar. Melainkan untuk mengatakan bahwa di era AI ini, banyak hal yang dulu hanya bisa dilakukan oleh institusi top, kini orang biasa juga memiliki kesempatan untuk mencobanya.
Penemuan faktor, iterasi strategi, eksekusi otomatis – hal-hal yang dulu membutuhkan tim, infrastruktur data besar, dan akumulasi bertahun-tahun untuk dibangun, kini bisa dijalankan dengan sebuah alur kerja.
Ini tidak berarti akan menghasilkan keuntungan yang stabil. Pasar akan selalu lebih kompleks dari sistem mana pun. Tapi ini berarti bahwa hambatan semakin rendah, alat semakin kuat, dan kemungkinan bagi orang biasa untuk berpartisipasi semakin besar.
⚠️ Peringatan Risiko: Strategi apa pun memiliki risiko kerugian. Konten artikel ini hanya untuk referensi pembelajaran teknis, bukan merupakan saran investasi. Pastikan untuk melakukan pengujian yang memadai sebelum bertrading langsung.
Kode Sumber Strategi: Versi Uji Coba Strategi Kuantitatif Penambangan Faktor Adaptif
- 1


