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

Этот файл можно передавать сторонней нейронке. Он описывает только публичный контракт стратегии и `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"
}
```
````

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

Хостовый бэктестер сам считает 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 {}; }
```

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

### `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`.

### `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, депозит, риск, размер ставки, фильтры процесса оптимизации.

### `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-объектом:

```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` параметры движка;
- не используй комментарии в 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>Timeframe` используй только `15m`, `1h`, `4h`, `6h`, `8h` или `1d`;
- не клади `File`, `Path`, `Symbol`, `Coin`, `Pair`, `Timeframe`, `Tf`, `Interval` в `optimize`;
- если код предполагает фиксированный таймфрейм, например BTC 1D, параметр `<name>Timeframe` должен быть `"1d"`.

## Запрещено

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

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

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

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

Ниже шаблон, который можно адаптировать. Перед ответом замени все `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) {
  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`;
- код не содержит запрещенных конструкций;
- есть `defaults`, `onBar`, `onBarInPos`;
- если стратегия использует объем, есть `requiredColumns`, возвращающий `["volume"]`;
- если есть `prepareIndicators`, есть `indicatorCacheKey`;
- все индикаторы считаются без будущих баров;
- входы и выходы используют достижимые цены;
- `params_sets/STRATEGY_KEY.json` является валидным JSON;
- `optimize` содержит только параметры стратегии и только непустые массивы.
