一、Зачем нужна автоматическая добыча факторов
Если вы сталкивались с количественной торговлей, вы наверняка слышали термин "фактор". Что такое фактор? Проще говоря, это рыночный сигнал, выраженный через данные. Например, импульс цены, аномалии объема, полосы Боллинджера — всё это используется для прогнозирования, вырастет или упадет конкретная монета в ближайшее время.
Звучит просто, но те, кто реально занимается исследованием факторов, знают, насколько это сложно:
Глубокие знания в финансах и фундаментальная статистическая подготовка
Большой объем чистых исторических данных
Строгая структура для бэктестинга
И ещё одна неизбежная проблема: факторы затухают. Сегодня сигнал работает, а через несколько дней может полностью потерять эффективность — участники рынка учатся, адаптируются и арбитражируют эту закономерность. Поэтому добыча факторов — это не разовая работа, а непрерывный процесс итераций.
В этой статье представлена система, автоматизирующая этот процесс: фиксированные интервалы, циклическое выполнение полного цикла добычи факторов → проверка → отбраковка → синтез сигналов → исполнение сделок. Машина заменяет ручную работу, позволяя стратегии всегда соответствовать изменяющейся рыночной динамике.
二、Общая архитектура системы
Традиционный процесс добычи факторов выглядит так: исследователь формулирует гипотезу → пишет код → запускает бэктест → отбирает → внедряет → через несколько месяцев находит, что фактор перестал работать → всё заново. Весь цикл может занимать недели или даже месяцы.
Эта система сжимает весь цикл до автоматического выполнения через фиксированный интервал:
| Шаг | Модуль | Описание |
|---|---|---|
| Шаг 1 | Получение пула инструментов | Отбор высоколиквидных бессрочных контрактов по объёму торгов, определение состояния рынка |
| Шаг 2 | Проверка пула факторов | Анализ текущего здоровья факторов, определение направления для данного цикла |
| Шаг 3 | Генерация факторов AI | В рамках ограничений AI генерирует новые факторы-кандидаты |
| Шаг 4 | Валидация IC | Расчёт информационного коэффициента на исторических данных, отбраковка неэффективных факторов |
| Шаг 5 | Фильтрация корреляции и отсев слабых | Удаление дублирующих информацию факторов, сохранение пула факторов компактным и эффективным |
| Шаг 6 | Синтез сигналов и исполнение | Взвешенный синтез скора, сигналы, превышающие порог, запускают ребалансировку |
Система управляется двумя планировщиками: медленный триггер выполняет полный цикл итерации факторов с часовым интервалом; быстрый триггер опрашивает состояние позиций с секундным интервалом, обрабатывая стоп-лосс/тейк-профит и обновляя дашборд.
三、Подробное описание модулей и основной код
3.1 Получение пула инструментов
В начале каждого цикла система загружает текущие рыночные данные всех бессрочных контрактов с биржи, сортирует по объёму торгов и берёт первые N. Ликвидность — это основа эффективности фактора: у мелких монет объёмы разрежены, и любой сигнал легко искажается.
Одновременно определяется квантиль волатильности 4-часовых свечей BTC за последнее время, чтобы оценить общее состояние рынка (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 Построение промпта и генерация факторов AI
AI получает не открытую задачу, а структурированную среду с ограничениями. Промпт содержит: текущее состояние рынка, список существующих факторов (запрещено повторение), недавно затухшие факторы (запрещена мелкая настройка), уже покрытые измерения, а также ещё не исследованные области.
Таким образом, генерируемые факторы-кандидаты действительно направлены на поиск новых направлений, а не на повторное прогон существующих факторов с изменёнными параметрами.
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 встроены полные спецификации TA-функций платформы Inventor, ограничения по формату кода, предварительные знания о крипторынке, а также полный перечень всех доступных для исследования факторных измерений (полное содержание см. в исходном коде стратегии). Формат вывода строго чистый 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. Временна́я последовательность строго выровнена, исключены future-функции.
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-валидацию, проходят ещё две проверки:
Первая: фильтрация корреляции. Если поперечные оценки двух факторов высоко похожи (|corr| > порог), сохраняется тот, у которого IC выше, второй отбрасывается. Аналогично: если два голоса по сути отражают одно и то же мнение, достаточно одного, второй не добавляет новой информации.
Вторая: выбывание худших (bottom‑killing). Пул факторов имеет верхний предел вместимости; при превышении факторы ранжируются по производительности, и наихудшие выбывают. Факторы с недавним устойчивым падением 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 → вес равен 0)
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 Мониторинг позиций: Стоп-лосс / Тейк-профит / Динамический трейлинг-стоп
Быстрый триггер запускается каждую секунду, в реальном времени отслеживает плавающую прибыль всех позиций и реализует три логики выхода:
- Фиксированный стоп-лосс**: Плавающий убыток превышает
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 + вызов ИИ), достаточно выполнять один раз в час. Защита позиции — это чувствительная ко времени задача, требующая отклика на уровне секунд. Разделяем их на триггеры разной частоты, чтобы избежать взаимных блокировок.
V. Наблюдения на реальном счёте: процесс итерации факторов
После двух дней работы на реальном счёте наблюдались следующие явления:
- Факторы, попавшие в пул на раннем этапе, имели историческую IC в основном в диапазоне от 0,04 до 0,07, прошли базовый порог.
- По мере продвижения итераций недавняя IC почти всех факторов начала снижаться. У некоторых она упала с 0,06 до 0,008, у некоторых вошла в отрицательную зону. Это говорит о том, что сигналы, захватываемые этими факторами, теряют эффективность в текущих рыночных условиях.
- После обнаружения снижения система в следующем раунде приоритетно исследует ещё не охваченные измерения, чтобы найти новые факторы-кандидаты для замены. Весь процесс не требует вмешательства человека.
Двух дней слишком мало, чтобы полностью проверить, действительно ли адаптивная способность системы эффективна. Здесь просто зафиксировано, что система выполнила итерации, как и ожидалось. Более значимые выводы требуют более длительного непрерывного наблюдения. Но сам этот процесс уже показывает, что базовая логика конструкции системы работает: она не цепляется за потерявшие эффективность сигналы, а постоянно пробует новые измерения.
VI. В заключение
Эта система создавалась не для того, чтобы доказать, что ИИ может победить рынок. А для того, чтобы сказать: в эпоху ИИ у обычных людей тоже есть возможность попробовать то, что раньше могли делать только ведущие институты.
Добыча факторов, итерация стратегий, автоматическое исполнение — то, что раньше требовало команду, обширную инфраструктуру данных и годы накопления, сегодня можно запустить с помощью одного рабочего процесса.
Это не означает, что она будет стабильно приносить прибыль. Рынок всегда сложнее любой системы. Но это означает, что порог входа снижается, инструменты становятся мощнее, и вероятность того, что обычные люди смогут участвовать в этом, растёт.
⚠️ Предупреждение о рисках: Любая стратегия сопряжена с риском убытков. Содержание данной статьи предназначено только для технического изучения и не является инвестиционной рекомендацией. Перед реальной торговлей обязательно проведите тщательное тестирование.
Исходный код стратегии: Адаптивная стратегия количественной торговли с добычей факторов (тестовая версия)
- 1


