1. Warum automatisierte Faktorentdeckung notwendig ist
Wenn du dich schon einmal mit quantitativem Trading beschäftigt hast, kennst du mit Sicherheit den Begriff „Faktor“. Was ist ein Faktor? Vereinfacht gesagt: ein marktwirtschaftliches Signal, das durch Daten ausgedrückt wird. Zum Beispiel Preis-Momentum, abnormales Handelsvolumen, Bollinger-Bänder – sie werden verwendet, um vorherzusagen, ob ein Coin in naher Zukunft steigen oder fallen wird.
Klingt einfach, aber wer wirklich Faktorenforschung betreibt, weiß, wie schwierig das ist:
Fundierte finanzielle Kenntnisse und tiefgehender Hintergrund in mathematischer Statistik
Große Mengen sauberer historischer Daten
Strenge Backtesting-Frameworks
Dazu kommt ein Problem, das nie verschwindet: Faktoren zerfallen
Ein Signal, das heute wirksam ist, kann morgen völlig nutzlos sein – weil Marktteilnehmer lernen, sich anpassen und diesen Mechanismus ausarbitrieren. Daher ist Faktorentdeckung keine einmalige Aufgabe, sondern erfordert kontinuierliche Iteration.
In diesem Artikel wird ein System vorgestellt, das diesen Prozess automatisiert: In festgelegten Intervallen wird zyklisch eine vollständige Faktorentdeckung → Validierung → Aussortierung → Signalkombination → Orderausführung durchgeführt. Maschinelle Iteration ersetzt manuelle Wiederholungen, sodass die Strategie stets mit dem Markt Schritt hält.
2. Gesamtsystemarchitektur
Der traditionelle Prozess der Faktorentdeckung läuft so ab: Forscher stellt Hypothese auf → Code schreiben → Backtest durchführen → Filtern → Produktivsetzung → Nach Monaten feststellen, dass der Faktor nicht mehr funktioniert → Neustart. Der gesamte Zyklus kann Wochen oder Monate dauern.
Dieses System komprimiert den gesamten Kreislauf auf eine automatische Ausführung in festgelegten Intervallen:
| Schritt | Modul | Erklärung |
|---|---|---|
| Schritt 1 | Zielpool abrufen | Hochliquide Perpetual-Kontrakte nach Handelsvolumen filtern, Marktstatus erkennen |
| Schritt 2 | Faktorpool prüfen | Aktuellen Gesundheitszustand der Faktoren analysieren, Erkundungsrichtung für diese Runde bestimmen |
| Schritt 3 | KI-generierte Faktoren | Unter Rahmenbedingungen neue, kandidatenreiche Faktoren durch KI generieren |
| Schritt 4 | IC-Validierung | Informationskoeffizienten mit historischen Daten berechnen, ungültige Faktoren aussortieren |
| Schritt 5 | Korrelationsfilter & Aussortierung | Redundante Faktoren entfernen, Pool schlank und effizient halten |
| Schritt 6 | Signalkombination & Orderausführung | Gewichtete Kombination der Scores, Orders bei Überschreitung des Schwellenwerts auslösen |
Das System wird von zwei Schedulern gesteuert: Der langsame Trigger führt stündlich einen vollständigen Faktor-Iterationsprozess durch; der schnelle Trigger pollt sekündlich den Positionsstatus, verwaltet Stop-Loss und Take-Profit und aktualisiert das Dashboard.
3. Detaillierte Erläuterung der einzelnen Module und Kerncode
3.1 Zielpool abrufen
Zu Beginn jeder Runde ruft das System die Echtzeit-Kurse aller Perpetual-Kontrakte von der Börse ab, sortiert nach Handelsvolumen und wählt die Top N aus. Liquidität ist die Grundvoraussetzung für die Wirksamkeit von Faktoren – kleine Coins haben spärliches Volumen, jedes Signal wird leicht verzerrt.
Gleichzeitig wird das historische Volatilitäts-Perzentil der 4-Stunden-Kerzen von BTC ermittelt, um den Marktzustand zu bewerten (normal / high_vol / low_vol / volatile). Diese Bewertung beeinflusst direkt die Richtung der von der KI generierten Faktoren.
javascript
// Zielpool nach Handelsvolumen filtern (hochliquide)
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));
// BTC-Volatilitäts-Perzentil ermitteln, Marktzustand bestimmen
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;
// Vergleich mit historischer Volatilität, Perzentil bestimmen
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 Faktorpool-Status prüfen
Bevor die KI neue Faktoren generieren lässt, prüft das System den aktuellen Gesundheitszustand des Faktorpools: Welche Faktoren haben in letzter Zeit einen stetigen Rückgang des IC (Zerfall) gezeigt, welche Dimensionen wurden noch nicht abgedeckt. Diese Informationen werden als direkte Rahmenbedingungen an die KI weitergegeben, um eine erneute Erkundung bereits zerfallener Richtungen zu vermeiden.
javascript
const factorPool = JSON.parse(_G('afi_factorPool') || '[]');
const icHistory = JSON.parse(_G('afi_icHistory') || '{}');
const icDecayWindow = $vars.icDecayWindow || 48; // Fensterlänge für kürzliche Daten
const icDecayThreshold = $vars.icDecayThreshold || -0.01; // Schwellenwert für Zerfallserkennung
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
});
}
}
// Dynamisch bestimmen, wie viele neue Faktoren in dieser Runde erkundet werden sollen
const explorationBuffer = $vars.explorationBuffer || 3;
const explorationCount = Math.max(
explorationBuffer,
targetFactorCount - validCount + explorationBuffer
);
const action = factorPool.length === 0 ? 'generate_initial' : 'iterate_factors';
3.3 Prompt erstellen, KI Faktoren erfinden lassen
Die KI erhält keine offene Aufgabe, sondern ein Framework mit Rahmenbedingungen. Der Prompt enthält: aktuellen Marktzustand, Liste vorhandener Faktoren (keine Wiederholungen), in letzter Zeit zerfallene Faktoren (keine Feinabstimmung), bereits abgedeckte Dimensionen, noch nicht erforschte Dimensionen.
Auf diese Weise werden die generierten Kandidatenfaktoren wirklich neue Richtungen erkunden, anstatt lediglich vorhandene Faktoren mit anderen Parametern erneut zu testen.
javascript
// 迭代模式 Prompt 关键片段
const usedDimensions = factorPool
.map(f => f.name + '(' + (f.rationale || '') + ')')
.join(', ') || '暂无';
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=' + avg + ' 近期IC=' + recent + ' | 逻辑: ' + f.rationale;
}).join('\n') || '暂无';
const degradedSummary = degradedFactors.length > 0
? degradedFactors.map(f =>
f.name + ': 近期IC=' + f.recentIC + ' | 原逻辑: ' + f.rationale
).join('\n')
: '本轮无衰减因子';
prompt += '【当前有效因子(不需要生成变体)】\n' + validSummary + '\n\n';
prompt += '【近期衰减因子(禁止在这些维度上微调)】\n' + degradedSummary + '\n\n';
prompt += '【已覆盖维度(禁止重复)】\n' + usedDimensions + '\n\n';
prompt += '【尚未探索的维度(优先从这里选)】\n' + unusedSample + '\n\n';
prompt += '生成 ' + explorationCount + ' 个全新方向因子:\n';
prompt += '1. 必须与已覆盖维度完全不同,禁止微调失效因子\n';
prompt += '2. 优先从尚未探索的维度中选取\n';
prompt += '3. 优先设计非线性组合因子\n';
prompt += '4. 针对当前 ' + marketState + ' 市场状态设计\n';
Das System Prompt der KI enthält die vollständigen Spezifikationen der TA-Funktionen der Erfinderplattform, Code-Format-Einschränkungen, Vorkenntnisse über den Kryptomarkt sowie eine Liste aller erkundbaren Faktordimensionen (vollständiger Inhalt siehe Quellcode der Strategie). Das Ausgabeformat ist streng reines JSON (ohne Markdown-Umhüllung):
json
{
"factors": [
{
"name": "MomentumAcceleration",
"rationale": "Kurzfristige Momentum-Beschleunigung, erfasst den Wendepunkt der Aufholträgheit von Privatanlegern",
"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 IC-Validierung: Daten sprechen, nicht Intuition
Der IC (Informationskoeffizient) misst: Wie hoch ist die Korrelation zwischen dem Querschnittsranking, das mit dem Faktor berechnet wurde, und dem tatsächlichen prozentualen Anstieg/Rückgang der nächsten Kerze? Je höher der IC, desto genauer die Vorhersage des Faktors.
Die Validierung erfolgt mittels Walk-Forward-Analyse (historische Wiedergabe): Man nimmt die letzten Hunderte von Kerzen. Zu jedem Zeitpunkt t werden die Daten von t-1 verwendet, um den Faktorwert zu berechnen und die Kursbewegung der t-ten Kerze vorherzusagen. Strenge zeitliche Ausrichtung – Zukunftsfunktionen werden ausgeschlossen.
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];
// Daten der Periode t-1 zur Berechnung des Faktors verwenden (slice(0, t) enthält nicht die t-te Kerze)
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 });
// Rendite der t-ten Kerze vorhersagen (echte Rendite)
nRets.push({
sym,
ret: (fullRecords[t].Close - fullRecords[t-1].Close) / fullRecords[t-1].Close
});
}
if (fVals.length < 8) continue;
// Rank IC (Spearman-Korrelationskoeffizient) berechnen
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 };
}
Die IC-Schwelle wird durch die Variable
$vars.icThresholdgesteuert, Standardwert 0,02. Dies ist eine relativ großzügige Einstiegshürde, die sich gut eignet, um offensichtlich ungültige Faktoren schnell auszusortieren. Für eine strengere statistische Signifikanzkontrolle kann der Wert je nach Situation erhöht werden. Faktoren, die die Schwelle nicht erreichen, werden unabhängig von der logischen Eleganz direkt aussortiert.
3.5 Korrelationsfilterung & Verdrängung der Schlechtesten
Faktoren, die die IC-Validierung bestehen, müssen noch zwei weitere Hürden nehmen:
Erste Hürde: Korrelationsfilter. Wenn zwei Faktoren im Querschnittswerte stark ähnlich sind (|corr| > Schwellwert), wird der mit dem höheren IC behalten, der andere verworfen. Wie bei zwei Stimmen, die auf denselben Gedanken zurückgehen: Eine Stimme genügt, die zweite bringt keine neue Perspektive.
Zweite Hürde: Verdrängung der Schlechtesten. Der Faktorpool hat eine Kapazitätsobergrenze. Bei Überschreitung werden die Faktoren nach ihrer Performance sortiert, die schlechtesten fliegen raus. Faktoren mit kontinuierlich rückläufigem IC in letzter Zeit nehmen am Ranking mit ihrem jüngsten IC teil (nicht dem historischen Durchschnitt), was einen stärkeren Verdrängungsdruck erzeugt.
javascript
// Korrelationsfilter (behält die mit der höchsten IC und verwirft redundante Faktoren mit hoher Korrelation)
const corrThreshold = $vars.corrThreshold || 0.7;
survivedFactors.sort((a, b) => b.icAvg - a.icAvg); // Zuerst absteigend nach IC sortieren
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) {
// Erfasse den absorbierten korrelierten Faktor (für Dashboard-Anzeige)
selected.corrGroup = (selected.corrGroup ? selected.corrGroup + ',' : '')
+ factor.name;
isRedundant = true;
break;
}
}
if (!isRedundant) decorrelatedFactors.push({ ...factor, corrGroup: '' });
}
// Ausscheidung der Schlechtesten: Zerfallende Faktoren verwenden den aktuellen IC statt des historischen Durchschnitts für das Ranking
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));
Hinweis: Die Korrelationsberechnung basiert auf den Faktorwerten des aktuellen Querschnitts. Dies kann zu bestimmten Zeitpunkten zu gelegentlichen Fehlentscheidungen führen. Robuster wäre die Verwendung der durchschnittlichen Korrelation über mehrere historische Querschnitte – dies ist eine mögliche Verbesserung für die Zukunft.
3.6 Signalsynthese und Rebalancing-Ausführung
Sobald der Faktorpool stabil ist, berechnet das System für jedes Zielobjekt eine Gesamtbewertung: Die Querschnittswerte jedes Faktors werden Z-score-normalisiert und dann entsprechend der jeweiligen aktuellen IC gewichtet überlagert – Faktoren mit besserer Performance erhalten einen höheren Anteil, Faktoren mit negativem aktuellen IC erhalten ein Gewicht von Null.
javascript
// Faktorgewichte: Gewichtung nach aktuellem IC (negative IC-Faktoren erhalten Gewicht Null)
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); // Negativer IC → Gewicht 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);
// Z-score-Normalisierung
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;
}
// Synthetische Bewertung
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;
}
// Schwellenwertfilterung: Unklare Signale werden übersprungen, kein Einstieg
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);
Bei der Rebalancing-Ausführung werden zunächst alte Positionen glattgestellt, die nicht in der aktuellen Liste enthalten sind. Anschließend werden die neuen Signale proportional zum Eigenkapital des Kontos eröffnet:
javascript
const positionRatio = $vars.positionRatio || 0.8; // 总权益使用比例
const maxLeverage = $vars.maxLeverage || 3;
const account = exchange.GetAccount();
const equity = account.Equity || account.Balance;
const perAmt = (equity * positionRatio) / (longList.length + shortList.length);
// 平掉不在目标集合里的旧仓位
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));
// 清除止盈追踪状态
const cm = sym.match(/^(.+)_USDT/);
if (cm) { _G(cm[1] + '_maxpnl', null); _G(cm[1] + '_trail', null); }
}
}
// 开入新信号仓位(使用市价单,-1 表示市价)
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 Positionsüberwachung: Stop-Loss / Take-Profit / dynamischer Trailing-Stop
Der schnelle Trigger läuft im Sekundentakt, überwacht den schwebenden Gewinn/Verlust aller Positionen in Echtzeit und führt drei Ausstiegslogiken aus:
- Fester Stop-Loss: Automatische Schließung, wenn der schwebende Verlust
STOP_LOSS_PCT(Standard 5%) übersteigt - Fester Take-Profit: Automatische Schließung, wenn der schwebende Gewinn
TAKE_PROFIT_PCT(Standard 10%) übersteigt - Dynamischer Trailing-Stop: Aktiviert, wenn der schwebende Gewinn
TRAIL_TRIGGER(3%) erreicht; der Drawdown-Schwellenwert passt sich dynamisch an den maximalen Gewinn an
javascript
const STOP_LOSS_PCT = $vars.stopLossPct || 5;
const TAKE_PROFIT_PCT = $vars.takeProfitPct || 10;
const TRAIL_TRIGGER = 3; // 浮盈达到 3% 后启动移动止盈
// 动态回撤阈值:最高浮盈越高,允许的回撤空间越大
function getDynamicTrailDrawdown(maxPnl) {
if (maxPnl >= 7) return 3; // 最高盈利 ≥7%,允许回撤 3%
if (maxPnl >= 4) return 2; // 最高盈利 ≥4%,允许回撤 2%
return 1.5; // 其他情况,回撤 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;
// 追踪最高浮盈
let maxPnl = _G(coin + '_maxpnl');
if (maxPnl === null) { maxPnl = pnlPct; _G(coin + '_maxpnl', maxPnl); }
else if (pnlPct > maxPnl) { maxPnl = pnlPct; _G(coin + '_maxpnl', maxPnl); }
// 启动移动止盈
if (!_G(coin + '_trail') && maxPnl >= TRAIL_TRIGGER) {
_G(coin + '_trail', true);
Log(coin + ' 启动移动止盈,浮盈: +' + pnlPct.toFixed(2) + '%');
}
const trailDrawdown = getDynamicTrailDrawdown(maxPnl);
let reason = null;
if (_G(coin + '_trail') && (maxPnl - pnlPct) >= trailDrawdown)
reason = '移动止盈(回撤 ' + (maxPnl - pnlPct).toFixed(2) + '%, 阈值 ' + trailDrawdown + '%)';
if (!reason && pnlPct <= -STOP_LOSS_PCT) reason = '止损(' + pnlPct.toFixed(2) + '%)';
if (!reason && pnlPct >= TAKE_PROFIT_PCT) reason = '止盈(' + pnlPct.toFixed(2) + '%)';
if (reason) {
exchange.CreateOrder(pos.Symbol, isLong ? 'closebuy' : 'closesell', -1, amt);
Log(coin, '触发', reason);
_G(coin + '_maxpnl', null); _G(coin + '_trail', null);
}
}
}
4. Wichtige Designentscheidungen
4.1 Warum Rank IC (anstelle von Pearson IC)
Rank IC verwendet Ränge anstelle von Rohwerten zur Berechnung der Korrelation und ist gegenüber Extremwerten (Ausreißern) in den Faktoren von Natur aus robust. Die Preisverteilung im Kryptomarkt ist stark fat-tailed, Pearson IC kann leicht durch einige wenige extreme Kerzen verzerrt werden, während Rank IC stabiler ist.
4.2 Strikte zeitliche Ausrichtung, Vermeidung von Future Lookahead Bias
Sowohl bei der IC-Validierung als auch bei der Online-Signalberechnung wird einheitlich der Faktorwert aus Periode t-1 verwendet, um die Rendite von Periode t vorherzusagen: Bei der Validierung wird records = fullRecords.slice(0, t) gesetzt, um zukünftige Daten physisch abzuschneiden. Egal wie der KI-generierte Faktor-Code auf records[n] zugreift, er erhält nur Historie bis t-1. Online wird der letzte Kerzenstrich entfernt (slice(0, n-1)) und der Faktorwert für die Vorhersage der nächsten Kerze berechnet. Beide Logiken sind identisch, um eine künstlich hohe IC durch Lookahead zu vermeiden.
4.3 Gewichtung neuerer IC-Werte, adaptive Gewichtung an den Markt
Die Faktorgewichte sind nicht fest, sondern werden dynamisch an die aktuelle IC angepasst. Wenn ein Faktor zu versagen beginnt (aktuelle IC sinkt), reduziert sich sein Gewicht bei der Signalkombination automatisch, bis es auf null fällt. Das System kann die Neugewichtung ohne manuelles Eingreifen durchführen.
4.4 Dual-Trigger-Architektur
Die Faktoriteration ist eine rechenintensive Aufgabe (Kerzenabruf + IC-Backtest + KI-Aufruf), die stündlich einmal ausreichend ist; der Positionsschutz ist eine zeitkritische Aufgabe, die eine Reaktion in Sekundenschnelle erfordert. Indem beide in Trigger mit unterschiedlichen Frequenzen aufgeteilt werden, werden gegenseitige Blockaden vermieden.
Fünf. Live-Beobachtung: Faktor-Iterationsprozess
Nach zwei Tagen Live-Handel wurden folgende Phänomene beobachtet:
- Frühe in den Pool aufgenommene Faktoren hatten historische IC-Werte im Bereich von 0,04 bis 0,07 und bestanden damit die grundlegende Schwelle.
- Mit fortschreitender Iteration sank die aktuelle IC fast aller Faktoren – einige fielen von 0,06 auf 0,008, andere sogar in den negativen Bereich. Dies zeigt, dass die von diesen Faktoren erfassten Signale im aktuellen Marktumfeld an Wirksamkeit verlieren.
- Nachdem das System die Abschwächung erkannt hat, erkundet es in der nächsten Runde bevorzugt noch nicht abgedeckte Dimensionen, um neue Kandidatenfaktoren zur Ablösung zu finden. Der gesamte Prozess erfordert kein manuelles Eingreifen.
Zwei Tage sind eine relativ kurze Zeitspanne, um die Anpassungsfähigkeit des Systems abschließend zu validieren. Hier wird lediglich festgehalten, dass das System die geplanten Iterationen durchgeführt hat. Aussagekräftigere Schlussfolgerungen erfordern eine kontinuierliche Beobachtung über einen längeren Zeitraum. Dennoch zeigt dieser Prozess bereits die grundlegende Logik des Systemdesigns: Es hält nicht an unwirksamen Signalen fest, sondern versucht kontinuierlich neue Dimensionen.
Sechs. Schlussbemerkung
Dieses System wurde nicht entwickelt, um zu beweisen, dass KI den Markt schlagen kann. Sondern vielmehr, um zu zeigen, dass im Zeitalter der KI auch normale Menschen versuchen können, was früher nur den besten Institutionen vorbehalten war.
Faktor-Mining, Strategie-Iteration, automatische Ausführung – all das, was früher ein Team, umfangreiche Dateninfrastruktur und jahrelange Erfahrung erforderte, kann heute mit einem Workflow umgesetzt werden.
Das bedeutet nicht, dass es stabil profitieren wird. Der Markt bleibt immer komplexer als jedes System. Aber es bedeutet, dass die Hürden sinken, die Werkzeuge leistungsfähiger werden und die Möglichkeit für normale Menschen, sich daran zu beteiligen, steigt.
⚠️ Risikohinweis: Jede Strategie birgt Verlustrisiken. Dieser Artikel dient ausschließlich der technischen Weiterbildung und stellt keine Anlageberatung dar. Vor dem Live-Handel sind unbedingt ausreichende Tests durchzuführen.
Strategiequellcode: Adaptiver Faktor-Mining-Quant-Strategie-Testversion
- 1


