Эксплуатация на VPS

Цель VPS-контура - запустить Китобоя так, чтобы он мог жить без ручного присмотра, но при этом не имел права тихо ломаться.

До боевых ордеров live должен пройти период наблюдения:

2-4 недели без ручного вмешательства

Что должно быть на VPS

Минимально:

репозиторий Decima-8-Sanctum
собранный decima8/build-os/d8_agent_cli
собранный store/market/build/market_to_vsb
собранный store/market/build/market_bake_brain_specs
Python окружение
Bybit ключи в защищенном env/config
директория store/market/runs/live
логирование stdout/stderr
мониторинг диска
systemd или аналогичный supervisor

Что должно запускаться

Базовый live:

python3 store/market/tools/whaler_s300_stage_live.py \
  --env mainnet \
  --category linear \
  --symbol BTCUSDT

До включения боевых ордеров:

inventory = paper/dry-run
order executor = disabled

После включения боевых ордеров:

director -> order intent -> risk gate -> exchange executor -> exchange reconciliation

Нельзя смешивать paper и real

Боевой слой должен быть отдельным:

director decision
  -> order intent
  -> risk gate
  -> exchange order
  -> exchange fill
  -> reconciled inventory

Paper inventory не должен притворяться биржевым балансом.

Нужны два состояния:

model_inventory
exchange_inventory

И отдельная сверка:

model position == exchange position
model qty      == exchange qty
model side     == exchange side

Health metrics

Минимальные метрики:

process_alive
start_time
uptime_seconds
last_trade_time
last_ws_message_time
last_frame_time
last_s300_time
last_s1800_time
last_director_write_time
ws_messages
trades
frames
reconnects
signals
decisions
equity
position_side
position_qty
realized_pnl
mark_pnl
drawdown
free_disk_gb

Файлы мониторинга

Каждый live run должен иметь:

director.json
director.tsv
director.html
mtf.s300.html
mtf.s1800.html
frames.mtf.s300.jsonl
frames.mtf.s1800.jsonl
tape.mtf.s300.raw8.vsb
tape.mtf.s1800.raw8.vsb

director.json - машинное состояние.

director.tsv - таблица решений.

director.html - человеческий монитор.

Аварийные условия

Kill switch должен срабатывать при:

нет ws messages дольше N секунд
нет trades дольше N секунд при активном рынке
нет новых frames дольше N секунд
director не обновлялся дольше N секунд
reconnect storm
exchange inventory != model inventory
max daily loss
max weekly loss
max consecutive losses
disk free ниже порога
исключение в order executor
неизвестный position side
битый config

Kill switch должен:

запретить новые входы
записать причину
отправить alert
по настройке закрыть позицию или оставить только manual mode

Диск

Диск уже был реальной проблемой. Поэтому на VPS нельзя бесконечно копить тяжелые артефакты.

Сохранять постоянно:

director.json
director.tsv
director.html
frames.mtf.s300.jsonl
frames.mtf.s1800.jsonl
логи решений
логи ордеров

Сжимать или удалять по retention policy:

сырой trade stream
старые tape.raw8.vsb
старые html heatmaps
временные run.jsonl
debug reports

Минимальная политика:

последние 7 дней: полный live context
последние 30 дней: director + frames + order logs
старше 30 дней: сжатые summaries

Alerts

Нужны alerts:

процесс упал
нет данных
много reconnects
позиция открыта слишком долго
дневной лимит близко
kill switch active
диск заполнен
расхождение inventory
ошибка ордера

Канал может быть любым: Telegram, email, webhook. Важно, чтобы alert содержал:

symbol
run id
time
severity
reason
position
equity
последний decision
ссылка/путь на director.html

Перед боевыми ордерами

Checklist:

  • live работает 2-4 недели без ручного вмешательства;
  • live vs offline replay сверены на одном периоде;
  • funding учтен;
  • slippage учтен;
  • комиссия соответствует бирже;
  • max daily loss задан;
  • max weekly loss задан;
  • kill switch проверен;
  • dry-run order intents пишутся корректно;
  • exchange reconciliation реализован;
  • order executor умеет idempotency;
  • все решения директора журналируются;
  • есть способ быстро отключить торговлю.

Боевой order executor

Executor не должен принимать "сигналы". Он должен принимать только decision/order intent от директора.

Пример order intent:

{
  "run_id": "s300_stage_BTCUSDT_20260613_022832",
  "symbol": "BTCUSDT",
  "decision_id": "2026-06-13T02:28:32Z-000123",
  "action": "open_short",
  "side": "Sell",
  "reduce_only": false,
  "notional_fraction": 0.5,
  "reason": "v42 signal S42011, s1800 phase allows short",
  "max_slippage_bps": 5.0
}

Executor отвечает:

принял
проверил риск
отправил ордер
получил fill
сверил позицию
записал результат

Идемпотентность

Каждый order intent должен иметь уникальный id.

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

Правило:

один decision_id -> максимум один exchange order

Режимы запуска

Нужно явно различать:

observe: live data + director, без inventory
paper: live data + director + paper inventory
dry-run-orders: order intents пишутся, но не отправляются
testnet: боевые вызовы на testnet
mainnet-small: mainnet минимальным размером
mainnet: полноценный режим

Переход между режимами должен быть config change, а не правка кода.