一、なぜ自動化された因子マイニングが必要か
もしあなたがクオンツトレーディングに触れたことがあれば、必ず「因子」という言葉を聞いたことがあるでしょう。因子とは何か?簡単に言えば、データで表現された市場のシグナルです。例えば、価格のモメンタム、出来高の異常、ボリンジャーバンドの位置など——これらは、今後一定期間にあるコインが上昇するか下落するかを予測するために使われます。
聞こえは簡単ですが、実際に因子研究を行う人なら誰でも、この作業がいかに難しいか知っています:
確かな金融知識と深い数理統計学のバックグラウンド
大量のクリーンな過去データ
厳格なバックテストフレームワーク
さらに、常に避けて通れない問題があります:因子は減衰するということです。
今日有効なシグナルも、数日後にはまったく機能しなくなることがあります——市場参加者は学習し、適応し、その法則をアービトラージしてしまうからです。そのため、因子マイニングは決して一度きりの作業ではなく、継続的な反復が必要です。
本稿で紹介するのは、まさにこのプロセスを自動化するシステムです:一定間隔でサイクルを繰り返し、完全な因子マイニング→検証→淘汰→シグナル合成→注文取引を実行します。機械による反復で人手による繰り返し作業を置き換え、戦略が常に市場の変化のリズムに追従できるようにします。
二、システム全体のアーキテクチャ
従来の因子マイニングのフローは以下の通りです:研究者が仮説を立てる→コードを書く→バックテストを実行する→選別する→本番投入する→数ヶ月後に効果がなくなる→やり直す。このサイクル全体に数週間から数ヶ月かかることもあります。
このシステムでは、サイクル全体を一定間隔で自動実行するように圧縮しています:
| ステップ | モジュール | 説明 |
|---|---|---|
| Step 1 | 銘柄プールの取得 | 出来高で流動性の高い無期限先物を選別し、市場状態を検出 |
| Step 2 | 因子プールの確認 | 現在の因子の健全性を分析し、今回の探索方向を決定 |
| Step 3 | AIによる因子生成 | 制約フレームワーク内で、AIに新しい次元の候補因子を生成させる |
| Step 4 | IC検証 | 過去データで情報係数を計算し、無効な因子を排除 |
| Step 5 | 相関フィルタリング&末位淘汰 | 情報が重複する因子を除去し、因子プールを精選かつ適切な数に保つ |
| Step 6 | シグナル合成&注文 | 加重合成スコアを計算し、閾値を超えたシグナルでポジション調整を実行 |
システムは2つのスケジューラで駆動されます:低速トリガーは時間単位で完全な因子反復プロセスを1回実行し、高速トリガーは秒単位でポジション状態をポーリングし、利確・損切りの処理とダッシュボードの更新を行います。
三、各モジュールの詳細とコアコード
3.1 銘柄プールの取得
各ラウンドの開始時に、システムは取引所からすべての無期限先物のリアルタイム行情を取得し、出来高順に上位N銘柄を選びます。流動性は因子の有効性の前提です——小型コインは出来高がまばらで、どのシグナルも歪みやすくなります。
同時に、BTCの4時間足ボラティリティの歴史的なパーセンタイルを検出し、市場全体の状態(normal / high_vol / low_vol / volatile)を判断します。この判断は、AIが因子を生成する際の方向性の偏好に直接影響します。
javascript
// 按成交额筛选高流动性标的
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 波动率分位,判断市场状态
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;
// 与全历史波动率比较,确定分位数
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 因子プールの状態確認
AIに新しい因子を生成させる前に、システムは現在の因子プールの健全性をチェックします:どの因子が最近ICの継続的な低下(減衰)を示しているか、どの次元がまだカバーされていないか。これらの情報はそのまま制約条件としてAIに渡され、すでに効果のなくなった方向の探索を繰り返すことを防ぎます。
javascript
const factorPool = JSON.parse(_G('afi_factorPool') || '[]');
const icHistory = JSON.parse(_G('afi_icHistory') || '{}');
const icDecayWindow = $vars.icDecayWindow || 48; // 近期窗口长度
const icDecayThreshold = $vars.icDecayThreshold || -0.01; // 衰减判定阈值
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
});
}
}
// 动态决定本轮需要探索多少个新因子
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の構築とAIによる因子の発明
AIに与えられるのはオープンなタスクではなく、制約のあるフレームワークです。Promptには以下が含まれます:現在の市場状態、既存の因子リスト(重複禁止)、最近減衰している因子(微調整禁止)、既にカバーされている次元、まだ探索されていない次元。
このようにして生成される候補因子は、本当に新しい方向への試みであり、既存の因子にパラメータを変えて再実行するだけではありません。
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';
AI の System Prompt には、Inventor プラットフォームの完全な TA 関数仕様、コード形式制約、暗号市場の事前知識、および探索可能なすべての因子次元のリスト(完全な内容は戦略ソースコード参照)が組み込まれています。出力形式は純粋な JSON(Markdown ラップなし)と厳格に定められています:
json
{
"factors": [
{
"name": "MomentumAcceleration",
"rationale": "短期モメンタム加速度、個人投資家の追い越し慣性転換点を捉える",
"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 検証:データが語る、直感に頼らない
IC(情報係数、Information Coefficient)は、因子から計算された断面順位と、次のローソク足の実際の騰落率順位との間の相関の高さを測定します。IC が高いほど、その因子の予測精度が高いことを示します。
検証方式はウォークフォワード(Walk-Forward): 過去数百本のローソク足を用い、各時点 t において、t-1 時点のデータで因子値を計算し、t 本目のローソク足の騰落を予測します。時系列は厳密に一致させ、未来関数(未来関数)を排除します。
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];
// t-1 期のデータで因子を計算(slice(0, t) は 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 });
// t 本目のローソク足の実収益を予測
nRets.push({
sym,
ret: (fullRecords[t].Close - fullRecords[t-1].Close) / fullRecords[t-1].Close
});
}
if (fVals.length < 8) continue;
// Rank IC(スピアマン相関係数)を計算
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 };
}
IC 閾値は変数
$vars.icThresholdで制御され、デフォルトは 0.02 です。これは比較的緩やかな入門基準で、明らかに無効な因子を迅速に除外するのに適しています。より厳密な統計的有意性管理が必要な場合は、実際の状況に応じて値を引き上げてください。閾値を通過しなかった因子は、ロジックがどれほど完璧でも直接淘汰されます。
3.5 相関フィルタリング & 最下位淘汰
IC 検証を通過した因子は、さらに 2 つの関門を通過する必要があります:
第一関門: 相関フィルタリング。 2 つの因子の断面スコアが非常に類似している場合(|corr| > 閾値)、IC が高い方を残し、もう一方を破棄します。これは、2 票が実質的に同じ人の考えである場合、1 票にまとめればよく、余分な票は新たな視点をもたらさないのと同じです。
第二関門: 最下位淘汰。 因子プールには容量上限があり、超過した分はパフォーマンス順に並べ替えられ、最も悪いものが除外されます。直近 IC が継続的に低下している因子は、履歴平均 IC ではなく直近 IC でランキングに参加し、より大きな淘汰圧力を受けます。
javascript
// 相関フィルタリング(ICが最も高いものを保持し、冗長な因子を排除)
const corrThreshold = $vars.corrThreshold || 0.7;
survivedFactors.sort((a, b) => b.icAvg - a.icAvg); // まずICの降順に並べ替え
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) {
// 吸収された相関因子を記録(ダッシュボード表示用)
selected.corrGroup = (selected.corrGroup ? selected.corrGroup + ',' : '')
+ factor.name;
isRedundant = true;
break;
}
}
if (!isRedundant) decorrelatedFactors.push({ ...factor, corrGroup: '' });
}
// 最下位淘汰:減衰因子は過去平均ではなく直近ICで順位付け
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));
注:相関計算は現時点のクロスセクションにおける因子スコアに基づくため、あるタイミングで偶発的に誤判定が生じる可能性がある。より堅牢な方法は、過去複数のクロスセクションの平均相関を取ることであり、これは今後の改善点である。
3.6 シグナル合成とポジション調整の実行
因子プールが安定した後、システムは各銘柄に対して総合スコアを計算する。各因子のクロスセクション値をZスコア標準化し、それぞれの直近ICで加重して重ね合わせる。パフォーマンスが良い因子ほど比率が大きく、直近ICが負の因子の重みはゼロとする。
javascript
// 因子の重み:直近ICで加重(負のICの因子は重みをゼロに)
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 → 重み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スコア標準化
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;
}
// 合成スコア
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;
}
// しきい値フィルタリング:シグナルが不明確なものはスキップし、エントリーしない
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);
ポジション調整の実行時には、まず今回のリストに含まれない既存ポジションを決済し、その後アカウントの純資産に応じて等比率で新たなシグナルを建てる。
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 ポジション監視:ストップロス / 利確 / ダイナミックトレーリングストップ
高速トリガーは秒単位で実行され、すべての保有ポジションの含み損益をリアルタイムで監視し、以下の3つの離脱ロジックを実行します。
- 固定ストップロス:含み損が
STOP_LOSS_PCT(デフォルト5%)を超えた場合、自動的に決済 - 固定利確:含み益が
TAKE_PROFIT_PCT(デフォルト10%)を超えた場合、自動的に決済 - ダイナミックトレーリングストップ:含み益が
TRAIL_TRIGGER(3%)に達した後に有効化され、最大含み益に応じて逆指値が動的に調整されます
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.1 なぜRank IC(Pearson ICではなく)を使うのか
Rank ICは生の値ではなく順位を使用して相関係数を計算するため、因子の極端な値(外れ値)に対して本質的にロバストです。暗号通貨市場の価格分布は裾が重く、Pearson ICは少数の極端なローソク足によって歪められやすいのに対し、Rank ICはより安定しています。
4.2 時系列の厳密なアライメント、未来関数の排除
IC検証とオンラインシグナル計算では、どちらも統一してt-1期の因子値を使用してt期のリターンを予測します。検証時には、recordsにfullRecords.slice(0, t)を渡し、将来データを物理的に切り捨てます。これにより、AIが生成した因子コードがどのようにrecords[n]を参照しても、アクセスできるのはt-1までの履歴のみになります。オンライン時には、最後のローソク足を取り除いて(slice(0, n-1))因子値を計算し、次のローソク足の値動きを予測します。両者のロジックは完全に一致しており、将来のデータを見ることによるICの見かけ上の向上を防ぎます。
4.3 最近のICへの重み付け、重みは市場に応じて適応
因子の重みは固定ではなく、直近のICに応じて動的に調整されます。ある因子が効力を失い始めると(直近のICが低下)、シグナル合成におけるその重みは自動的に減少し、最終的にゼロになります。システムは人手を介さずに重みのリバランスを完了できます。
4.4 デュアルトリガーアーキテクチャ
因子の反復は再計算タスク(ローソク足取得+ICバックテスト+AI呼び出し)であり、時間単位で1回実行すれば十分です。一方、ポジション保護は時間に敏感なタスクであり、秒単位の応答が必要です。この2つを異なる頻度のトリガーに分割することで、互いのブロックを回避します。
五、実戦観察:因子反復プロセス
実戦で2日間稼働させた後、以下の現象が観察されました。
- 初期にプールに入った因子の過去ICは概ね0.04~0.07の範囲にあり、基本閾値を通過しました。
- 反復が進むにつれて、ほぼ全ての因子の直近ICが低下しました。あるものは0.06から0.008に下がり、あるものは負の値にまで落ち込みました。これは、これらの因子が捉えていたシグナルが現在の市場環境において効力を失いつつあることを示しています。
- システムは減衰を検出すると、次のラウンドでは優先的にまだカバーされていない次元を探索し、新しい候補因子を見つけて置き換えます。このプロセス全体に人手は必要ありません。
2日間という期間は短く、システムの適応能力が本当に有効かどうかを十分に検証するには至りません。ここでは単にシステムが想定通りに反復動作を実行したことを記録しており、より意味のある結論を得るには、より長期にわたる継続観察が必要です。しかし、このプロセス自体から、システム設計の基本ロジックが機能していることがわかります。すなわち、効力を失ったシグナルに固執せず、新しい次元を継続的に試みるということです。
六、最後に
このシステムを構築したのは、AIが市場に打ち勝てることを証明するためではありません。そうではなく、このAI時代において、かつてはトップクラスの機関だけができたことも、一般人にも試す機会が生まれているということを伝えたいのです。
因子発掘、戦略反復、自動実行――これらは以前であればチーム、大規模なデータインフラ、そして数年の蓄積が必要でしたが、今日では一つのワークフローで動かすことができます。
これは安定した収益を約束するものではありません。市場は常にどのシステムよりも複雑です。しかし、それは参入障壁が下がり、ツールが強力になり、一般人がこの領域に参加する可能性が広がっていることを意味します。
⚠️ リスク注意:いかなる戦略にも損失リスクがあります。本記事の内容は技術学習の参考のみを目的としており、投資助言を構成するものではありません。実戦投入前には必ず十分なテストを行ってください。
戦略ソースコード: 適応型因子発掘定量戦略テスト版
- 1


