# Инструкция для нейронки: генерация стратегии и параметров

Этот файл можно передавать сторонней нейронке. Он описывает только публичный контракт стратегии и файла параметров `params.json`. Здесь нет кода движка, API, базы данных, очередей, оптимизатора и внутренней файловой структуры.

## Задача нейронки

По описанию пользователя сгенерируй ровно два файла:

1. STRATEGY_KEY.js
2. STRATEGY_KEY.json



Ответ должен содержать только два блока файлов и ничего больше.

````text
=== FILE: src/strategies/STRATEGY_KEY.js ===
```javascript
<код стратегии>
```
=== FILE: params_sets/STRATEGY_KEY.json ===
```json
{
  "strategy": "STRATEGY_KEY"
}
```
````

Файл параметров может быть двух типов:

- **обычный фиксированный набор** — без поля `optimize`;
- **набор для поиска параметров** — с полем `optimize`.

Если пользователь явно не просит оптимизацию, не добавляй `optimize` без необходимости.

## Роль стратегии

Хостовый бэктестер сам считает PnL, комиссии, slippage, банк, размер ставки, просадку и итоговые метрики. Стратегия должна только:

- подготовить свои индикаторы;
- решить, когда открыть сделку;
- решить, когда закрыть открытую сделку;
- вернуть достижимые цены входа и выхода.

Стратегия не должна читать файлы, обращаться в сеть, запускать процессы или считать деньги.

## Формат бара

Каждый бар в массиве `d` имеет поля:

```javascript
{
  t: Date,     // время бара
  o: number,  // open
  h: number,  // high
  l: number,  // low
  c: number,  // close
  v?: number  // volume, может отсутствовать
}
```

Гарантированы только `t`, `o`, `h`, `l`, `c`. Поле `v` можно использовать только после проверки `typeof bar.v === "number"`.

Если стратегия опирается на объем, объяви это явно, чтобы хост заранее проверил входной файл:

```javascript
export function requiredColumns() {
  return ["volume"];
}
```

Не рассчитывай, что в данных уже есть `rsi`, `ema`, `wma`, `bbU`, `bbL`, `atr`, `adx`, `alma` и другие индикаторы. Если индикатор нужен, считай его сам в `prepareIndicators`.

Никогда не заглядывай в будущее: для бара `i` можно использовать только `d[0] ... d[i]`.

## Обязательные экспорты

Файл стратегии обязан экспортировать:

```javascript
export const defaults = {
  strategy: "STRATEGY_KEY"
};

export function onBar(state, d, i, p) {
  return null;
}

export function onBarInPos(state, trade, d, i, p) {
  return null;
}
```

### `defaults`

`defaults` содержит значения параметров стратегии по умолчанию. Все параметры, которые использует код, должны иметь дефолт.

Пример:

```javascript
export const defaults = {
  strategy: "STRATEGY_KEY",
  ema_fast: 20,
  ema_slow: 50,
  tp_pct: 0.05,
  sl_pct: 0.03,
  trade_direction: "both"
};
```

### `onBar(state, d, i, p)`

Вызывается, когда позиции нет. Возвращает сигнал входа или `null`.

Допустимые сигналы:

```javascript
return { side: "long", entryPrice: d[i].c };
return { side: "short", entryPrice: d[i].c };
return null;
```

`side` должен быть только `"long"` или `"short"`. `entryPrice` должен быть реально достижимой ценой внутри текущего бара, обычно `d[i].c`.

Если нужно передать свои значения в открытую сделку, используй поля с префиксом `_`:

```javascript
return {
  side: "long",
  entryPrice: d[i].c,
  _stopLoss: d[i].c * 0.97,
  _takeProfit: d[i].c * 1.05
};
```

Поля без префикса `_`, кроме `side` и `entryPrice`, не гарантированы.

### `onBarInPos(state, trade, d, i, p)`

Вызывается, когда позиция открыта. Возвращает выход или `null`.

Допустимый выход:

```javascript
return { exitPrice: price, reason: "TP" };
return { exitPrice: price, reason: "SL" };
return { exitPrice: price, reason: "SIGNAL" };
return null;
```

Если `reason` начинается с `"SL"` или равен `"HARD_SL"`, хост считает это стоп-лоссом.

`exitPrice` должен быть достижимым внутри текущего бара:

- для long TP: `bar.h >= target`;
- для long SL: `bar.l <= stop`;
- для short TP: `bar.l <= target`;
- для short SL: `bar.h >= stop`;
- для сигнального выхода обычно используй `bar.c`.

## Модель позиции и исполнения

Хост ведет одну открытую позицию за раз. Поддерживаются long, short и стратегия, которая выбирает сторону для каждого нового входа.

Не моделируй pyramiding, scale-in, DCA, усреднение, несколько независимых позиций, одновременный hedge long/short, сетку из нескольких ордеров и частичные выходы. Первый возвращенный выход закрывает позицию целиком.

После входа хост начинает проверять выход на следующих барах. Если позиция закрылась на текущем баре, новый вход рассматривается на следующей свече. Позиция, оставшаяся открытой на последнем баре истории, автоматически не закрывается — при необходимости добавь явное правило выхода.

Если в одном баре одновременно достижимы TP и SL, фактический порядок движения цены неизвестен. Явно задай порядок проверок в `onBarInPos`; это будет прозрачным допущением свечного бэктеста.

## Разрешенные дополнительные экспорты

Можно использовать только эти дополнительные экспорты:

```javascript
export const skipWmaGuard = true;
export const usePnlWinRate = true;

export function requiredColumns(p) { return []; }
export function indicatorCacheKey(p) { return "STRATEGY_KEY|..."; }
export function prepareIndicators(bars, p) { return bars; }
export function* gridParams(pBase, arr) { yield pBase; }
export function filterSignal(signal, bar, p, context) { return true; }
export function onTradeClose(state) { return {}; }
export function buildContext(p) { return {}; }
```

Не экспортируй ничего другого.

Вспомогательные функции стратегии должны оставаться локальными:

```javascript
function _myHelper(...) {
  ...
}
```

Нельзя писать:

```javascript
export function _myHelper(...) {
  ...
}
```

если `_myHelper` не входит в список разрешенных экспортов выше.

### `skipWmaGuard`

Если стратегия сама считает индикаторы и не использует заранее существующие `bar.wma`, `bar.bbU`, `bar.bbL`, поставь:

```javascript
export const skipWmaGuard = true;
```

Для большинства новых стратегий это нужно.

### `usePnlWinRate`

Если win rate должен считаться по знаку PnL, можно поставить:

```javascript
export const usePnlWinRate = true;
```

### `requiredColumns(p)`

Возвращает обязательные колонки входных данных. Для стратегий на объеме возвращай `["volume"]`; если колонки нет, хост остановит прогон с понятной ошибкой.

### `prepareIndicators(bars, p)`

Используй для расчета индикаторов. Функция получает массив баров, добавляет свои поля и **обязана вернуть массив баров**.

Правила:

- считай только из `o`, `h`, `l`, `c`, `v`;
- проверяй lookback;
- не используй будущие бары;
- не мутируй `p`;
- если есть `prepareIndicators`, добавь `indicatorCacheKey`;
- `prepareIndicators` должна возвращать массив на каждом пути выполнения;
- нельзя использовать голый `return;`;
- нельзя возвращать `undefined`, `null`, `true` или `false`;
- при недостатке истории все равно возвращай исходный массив баров.

Правильно:

```javascript
export function prepareIndicators(bars, p) {
  if (!Array.isArray(bars)) return [];

  if (bars.length < 50) {
    return bars;
  }

  // расчет индикаторов

  return bars;
}
```

Неправильно:

```javascript
export function prepareIndicators(bars, p) {
  if (bars.length < 50) return;

  // расчет индикаторов
}
```

### `indicatorCacheKey(p)`

Обязателен, если есть `prepareIndicators`. В ключ должны входить все параметры, которые влияют на индикаторы.

Пример:

```javascript
export function indicatorCacheKey(p) {
  return `STRATEGY_KEY|emaFast=${p.ema_fast}|emaSlow=${p.ema_slow}`;
}
```

### `gridParams(pBase, arr)`

Используй для оптимизации параметров. `arr(key, fallback)` возвращает массив из `params.optimize[key]` или `[fallback]`.

Пример:

```javascript
export function* gridParams(pBase, arr) {
  for (const ema_fast of arr("ema_fast", pBase.ema_fast))
  for (const ema_slow of arr("ema_slow", pBase.ema_slow))
  for (const tp_pct of arr("tp_pct", pBase.tp_pct))
  for (const sl_pct of arr("sl_pct", pBase.sl_pct)) {
    yield { ...pBase, ema_fast, ema_slow, tp_pct, sl_pct };
  }
}
```

Оптимизируй только параметры стратегии. Не добавляй параметры хостового движка: комиссии, slippage, депозит, риск, размер ставки, фильтры процесса оптимизации.

Не раздувай сетку бессмысленными или условно неактивными осями.

Например, если:

- `tp_pct` используется только при `tp_mode = "fixed"`;
- `tp_touch_n` используется только при `tp_mode = "bb"`;

не делай без необходимости большой плоский Cartesian product по `tp_mode`, `tp_pct` и `tp_touch_n`.

То же касается:

- `sl_mode` + параметров конкретного SL;
- `use_filter` + параметров этого фильтра;
- любого переключателя режима + параметров, которые работают только внутри одного режима.

Если пользователь просит широкую оптимизацию, старайся выбирать только действительно значимые параметры и избегать почти дублирующих соседних значений.

### `filterSignal(signal, bar, p, context)`

Вызывается после `onBar` и до открытия позиции. Верни `true`, чтобы разрешить вход, или `false`, чтобы отклонить его. В `context` гарантированы `state`, `index` и `bars`; дополнительные поля используй только если сам добавил их через `buildContext`.

### `onTradeClose(state)`

Вызывается после закрытия сделки. Верни объект с тем состоянием, которое нужно сохранить до следующей сделки. Если хук не задан, состояние стратегии после сделки сбрасывается.

### `buildContext(p)`

Используй для подготовки дополнительного контекста перед прогоном, например для обработки уже загруженного `p._externalData`. Не читай в нем файлы, не обращайся к сети и не импортируй модули.

## `params_sets/STRATEGY_KEY.json`

Файл параметров должен быть валидным JSON-объектом.

### Вариант 1: обычный фиксированный набор

```json
{
  "strategy": "STRATEGY_KEY",
  "ema_fast": 20,
  "ema_slow": 50,
  "tp_pct": 0.05,
  "sl_pct": 0.03,
  "trade_direction": "both"
}
```

Такой набор используется для обычного одиночного прогона.

### Вариант 2: набор для поиска параметров

```json
{
  "strategy": "STRATEGY_KEY",
  "ema_fast": 20,
  "ema_slow": 50,
  "tp_pct": 0.05,
  "sl_pct": 0.03,
  "trade_direction": "both",
  "optimize": {
    "ema_fast": [10, 20, 30],
    "ema_slow": [50, 100],
    "tp_pct": [0.03, 0.05, 0.08],
    "sl_pct": [0.02, 0.03, 0.05]
  }
}
```

Правила:

- `strategy` должен точно равняться `STRATEGY_KEY`;
- `optimize` необязателен;
- если `optimize` есть, это объект, где каждое значение — непустой массив;
- не клади в `optimize` параметры движка;
- не клади в `optimize` параметры выбора внешнего источника данных;
- не используй комментарии в JSON;
- не используй `NaN`, `Infinity`, функции или trailing commas;
- если пользователь не просит поиск параметров, обычного фиксированного набора достаточно.

## Внешние данные

Если стратегия использует другой инструмент или другой таймфрейм как фильтр, не читай файлы самостоятельно. Опиши внешний ряд через параметры:

```json
{
  "btcRegimeFilter": true,
  "btcRegimeSymbol": "BTCUSDT",
  "btcRegimeTimeframe": "1d",
  "btcRegimeFile": "BTCUSDT"
}
```

Хост сам загрузит данные в:

```javascript
p._externalData?.btcRegime?.bars
```

Каждый внешний бар имеет `{ t, o, h, l, c, v? }`. Используй только внешний бар с `externalBar.t <= d[i].t`.

Правила:

- не указывай абсолютные пути Windows;
- для `<name>File` лучше использовать символ, например `"BTCUSDT"` или `"SBER"`;
- `<name>File`, `<name>Path`, `<name>Symbol`, `<name>Coin`, `<name>Pair` не должны быть пустыми строками, если присутствуют;
- для `<name>Timeframe` используй только `15m`, `1h`, `4h`, `6h`, `8h` или `1d`;
- не клади `File`, `Path`, `Symbol`, `Coin`, `Pair`, `Timeframe`, `Tf`, `Interval` в `optimize`;
- если код предполагает фиксированный таймфрейм, например BTC 1D, параметр `<name>Timeframe` должен быть `"1d"`;
- если пользователь явно запросил внешний фильтр или внешний инструмент, не удаляй эту функцию только ради прохождения валидации — исправь selector или параметры источника.

Неправильно:

```json
{
  "btcRegimeFilter": true,
  "btcRegimeFile": ""
}
```

## Запрещено

В коде стратегии запрещены:

- `require(...)`;
- `import ... from ...`;
- `import(...)`;
- `fs`, `path`, `net`, `http`, `https`, `child_process`, `crypto`, `os`, `vm`;
- `process`;
- `fetch`;
- `XMLHttpRequest`;
- `eval`;
- `Function`;
- `globalThis`;
- чтение файлов;
- сетевые запросы;
- запуск процессов;
- сохранение состояния в глобальных переменных между прогонами.

Разрешен только обычный JavaScript с `export`.

Если пользователь в своем ТЗ просит способ реализации, который противоречит этим ограничениям, сохрани торговую логику, но реализуй ее совместимым способом.

Например, если пользователь просит `import()` для индикатора, не используй `import()` — реализуй нужный расчет локальной функцией внутри файла стратегии.

## Безопасный шаблон стратегии

Ниже шаблон, который можно адаптировать. Перед ответом замени все `STRATEGY_KEY` на реальный ключ.

```javascript
const isNum = (x) => typeof x === "number" && Number.isFinite(x);

export const defaults = {
  strategy: "STRATEGY_KEY",
  ema_fast: 20,
  ema_slow: 50,
  tp_pct: 0.05,
  sl_pct: 0.03,
  trade_direction: "both"
};

export const skipWmaGuard = true;
export const usePnlWinRate = true;

export function indicatorCacheKey(p) {
  return `STRATEGY_KEY|ema_fast=${Number(p.ema_fast) || 20}|ema_slow=${Number(p.ema_slow) || 50}`;
}

function ema(values, len) {
  const out = Array(values.length).fill(null);
  const n = Math.max(2, Math.floor(Number(len) || 2));
  const k = 2 / (n + 1);
  let prev = null;
  for (let i = 0; i < values.length; i++) {
    const v = values[i];
    if (!isNum(v)) continue;
    prev = prev == null ? v : v * k + prev * (1 - k);
    out[i] = prev;
  }
  return out;
}

export function prepareIndicators(bars, p) {
  if (!Array.isArray(bars)) return [];

  const closes = bars.map((b) => b.c);
  const fast = ema(closes, p.ema_fast);
  const slow = ema(closes, p.ema_slow);
  for (let i = 0; i < bars.length; i++) {
    bars[i].emaFast = fast[i];
    bars[i].emaSlow = slow[i];
  }
  return bars;
}

export function onBar(state, d, i, p) {
  const bar = d[i];
  const prev = d[i - 1];
  if (!prev || !isNum(bar.emaFast) || !isNum(bar.emaSlow) || !isNum(prev.emaFast) || !isNum(prev.emaSlow)) {
    return null;
  }

  const direction = String(p.trade_direction || "both").toLowerCase();
  const crossedUp = prev.emaFast <= prev.emaSlow && bar.emaFast > bar.emaSlow;
  const crossedDown = prev.emaFast >= prev.emaSlow && bar.emaFast < bar.emaSlow;

  if ((direction === "both" || direction === "long") && crossedUp) {
    return {
      side: "long",
      entryPrice: bar.c,
      _takeProfit: bar.c * (1 + Number(p.tp_pct || 0.05)),
      _stopLoss: bar.c * (1 - Number(p.sl_pct || 0.03))
    };
  }

  if ((direction === "both" || direction === "short") && crossedDown) {
    return {
      side: "short",
      entryPrice: bar.c,
      _takeProfit: bar.c * (1 - Number(p.tp_pct || 0.05)),
      _stopLoss: bar.c * (1 + Number(p.sl_pct || 0.03))
    };
  }

  return null;
}

export function onBarInPos(state, trade, d, i, p) {
  const bar = d[i];
  const tp = trade._takeProfit;
  const sl = trade._stopLoss;

  if (trade.side === "long") {
    if (isNum(tp) && bar.h >= tp) return { exitPrice: tp, reason: "TP" };
    if (isNum(sl) && bar.l <= sl) return { exitPrice: sl, reason: "SL" };
  } else {
    if (isNum(tp) && bar.l <= tp) return { exitPrice: tp, reason: "TP" };
    if (isNum(sl) && bar.h >= sl) return { exitPrice: sl, reason: "SL" };
  }

  return null;
}

export function* gridParams(pBase, arr) {
  for (const ema_fast of arr("ema_fast", pBase.ema_fast))
  for (const ema_slow of arr("ema_slow", pBase.ema_slow))
  for (const tp_pct of arr("tp_pct", pBase.tp_pct))
  for (const sl_pct of arr("sl_pct", pBase.sl_pct))
  for (const trade_direction of arr("trade_direction", pBase.trade_direction)) {
    yield { ...pBase, ema_fast, ema_slow, tp_pct, sl_pct, trade_direction };
  }
}
```

Пример параметров:

```json
{
  "strategy": "STRATEGY_KEY",
  "ema_fast": 20,
  "ema_slow": 50,
  "tp_pct": 0.05,
  "sl_pct": 0.03,
  "trade_direction": "both",
  "optimize": {
    "ema_fast": [10, 20, 30],
    "ema_slow": [50, 100],
    "tp_pct": [0.03, 0.05, 0.08],
    "sl_pct": [0.02, 0.03, 0.05],
    "trade_direction": ["both"]
  }
}
```

## Финальная проверка перед ответом

Перед выдачей результата проверь:

- есть ровно два блока файлов;
- ключ `STRATEGY_KEY` одинаковый в имени файла, `defaults.strategy` и `params.strategy`;
- код не содержит запрещенных конструкций;
- нет неожиданных `export`;
- есть `defaults`, `onBar`, `onBarInPos`;
- если стратегия использует объем, есть `requiredColumns`, возвращающий `["volume"]`;
- если есть `prepareIndicators`, есть `indicatorCacheKey`;
- если есть `prepareIndicators`, она возвращает массив баров на каждом пути выполнения;
- все индикаторы считаются без будущих баров;
- входы и выходы используют достижимые цены;
- `params_sets/STRATEGY_KEY.json` является валидным JSON;
- если `optimize` есть, он содержит только параметры стратегии и только непустые массивы;
- engine-параметры и selector-параметры внешних данных не находятся в `optimize`;
- внешние selector-параметры не являются пустыми строками.
