---
title: "Simulate"
method: POST
path: "/simulate"
---

# Simulate

`POST /simulate`

## Request

- Base URL: `https://api.pruviq.com` (the document declares no server; this is the origin it was published from)
- URL: `https://api.pruviq.com/simulate`
- Auth: none declared

Offload the CPU-bound backtest to a worker thread so the single-worker
event loop keeps answering /health (prevents watchdog false-restarts).

## Request body

- SimulationRequest: 시뮬레이션 요청 `extra="forbid"` — 모르는 필드는 **거부**한다(2026-09-20, Q-B97). 왜: 이 파일에 `extra` 선언이 한 줄도 없어서 Pydantic v2 기본값 `ignore` 가 적용돼 있었다. 그래서 `{"params": {...}}` 처럼 한 겹 감싸 보내면 **전 필드가 기본값으로 떨어지고 HTTP 200 에 전혀 다른 답**이 나갔다(실측 27배 차이). 호출자는 자기 입력이 버려진 걸 알 길이 없다 — 오답이 성공처럼 보인다. 사람에게도 나쁘지만 **에이전트에게 특히 나쁘다**: 사람은 화면 숫자가 이상하면 멈추지만, 프로그램은 200 을 받으면 그대로 인용한다. 회귀 위험은 켜기 전에 실측했다 — `/simulate` 를 부르는 프론트 4곳의 전송 키 합집합 13개가 **전부 이 모델의 필드**다(모델에만 있는 8개는 무관): SimulatorPage(자동실행) 13 · StrategyDemo 5 · TrustGapPanel 9 · simRequest 10 빌더의 커스텀 전략 본문(`...customSnapshot`)은 이 엔드포인트가 아니라 `/backtest` 로 간다 — 그쪽 모델은 이 변경의 범위가 아니다.
  - `record_provenance` boolean: Capture actual engine/input hashes for a fresh registry-strategy execution; bypass result cache, retain compute limits
  - `strategy` string: Strategy ID
  - `direction` string, nullable: long | short | both (null = use strategy default)
  - `sl_pct` number: Stop Loss %
  - `tp_pct` number: Take Profit %
  - `max_bars` integer: Max holding period (bars)
  - `market_type` 'futures' | 'spot': spot | futures
  - `symbols` string[], nullable: Specific symbols (null = use top_n)
  - `symbol` string, nullable: Single-symbol alias for `symbols`. Auto-mapped (prevents silent ignore of a singular field).
  - `top_n` integer, nullable: Number of coins (null = top 50)
  - `include_stablecoins` boolean: Include stablecoins (USDC, DAI…) in the top_n universe. Default false — ~0 volatility → empty backtests. Ignored when `symbols` is set (explicit picks always honored).
  - `start_date` string, nullable: Backtest start date (YYYY-MM-DD)
  - `end_date` string, nullable: Backtest end date (YYYY-MM-DD)
  - `cutoff_utc` string, date-time, nullable: Only candles closed by this UTC instant; null = current closed hour. Does not freeze historical source revisions.
  - `timeframe` '1H' | '2H' | '4H' | '6H' | '12H' | '1D' | '1W': Candle timeframe: 1H, 2H, 4H, 6H, 12H, 1D, 1W
  - `compounding` boolean: True = reinvest profits (compound), False = fixed position size (simple)
  - `avoid_hours` integer[], nullable: UTC hours to avoid entering trades (0-23). null = use strategy default
  - `avoid_months` integer[], nullable: Months to avoid entering trades (1-12). null = no month filter
  - `min_vol_regime` number, nullable: Minimum ATR ratio (current ATR / 14-period ATR MA) to allow entry. e.g., 0.7 = skip low-volatility periods. null = no filter
  - `btc_trend_filter` boolean: If true, only allow LONG entries when BTC is above its 50-period SMA. Improves LONG edge in bull regimes.
  - `fee_pct` number, nullable: Fee per side (0.0005 = 0.05%). None = use market_type default
  - `leverage` number: Leverage multiplier for PnL calculation

## Response `200`

Successful Response

- SimulationResponse: 시뮬레이션 결과
  - `provenance` ExecutionProvenance
    - `schema_version` '1.0.0'
    - `engine_commit` string, required
    - `started_at` string, date-time, required
    - `measured_at` string, date-time, required
    - `request` object, required: Validated request with defaults before engine normalization
    - `coins` string[], required
    - `data_snapshot_id` string, required
    - `selection` SelectedFrame[], required
      - `snapshot_id` string, required
      - `rows` integer, required
      - `columns` FrameColumn[], required
        - `name` string, required
        - `dtype` string, required
      - `row_hash` string, required
      - `pandas_version` string, required
      - `period` FramePeriod, required
        - `start` string, nullable, required
        - `end` string, nullable, required
      - `symbol` string, required
    - `runs` EngineRun[], required
      - `parameters` object, required: Bound run_fast arguments, including defaults. Fee/slippage are per-side fractions; SL/TP are fractions.
      - `strategy_parameters` object, required
      - `strategy_config` object: All public instance attributes of the strategy as constructed (superset of strategy_parameters)
      - `data` FrameSnapshot, required
        - `snapshot_id` string, required
        - `rows` integer, required
        - `columns` FrameColumn[], required
          - `name` string, required
          - `dtype` string, required
        - `row_hash` string, required
        - `pandas_version` string, required
        - `period` FramePeriod, required
          - `start` string, nullable, required
          - `end` string, nullable, required
    - `funding` FundingSnapshot[], required
      - `symbol` string, required
      - `available` boolean, required
      - `rows` integer, nullable
      - `snapshot_id` string, nullable
    - `margin` MarginSnapshot[]: Liquidation maintenance-margin inputs used (leverage>1 runs)
      - `symbol` string, required
      - `mmr` number, required
      - `source` string, required
      - `tiers_sha256` string, nullable
      - `fallback_mmr` number, required
      - `taker_fee_buffer` number, required
  - `strategy` string, required
  - `direction` string, required
  - `params` object, required
  - `market_type` string, required
  - `total_trades` integer, required
  - `wins` integer, required
  - `losses` integer, required
  - `win_rate` number, required
  - `total_return_pct` number, required
  - `profit_factor` number, required
  - `avg_win_pct` number, required
  - `avg_loss_pct` number, required
  - `max_drawdown_pct` number, required
  - `max_consecutive_losses` integer, required
  - `total_fees_pct` number, required
  - `profit_factor_ci` MetricCI: 지표 하나의 부트스트랩 신뢰구간. ★ `unit` 이 **값으로** 있는 이유 (Q-B29 A, 2026-08-29): 각 지표의 CI 는 «그 헤드라인이 실제로 쓰는 입력» 을 재표집해야 한다. profit_factor ← 거래별 gross P/L → unit="trades" sharpe_ratio ← 일별 수익률 × √365 → unit="daily_returns" 거래를 재표집해 Sharpe CI 를 내면 **화면의 그 Sharpe 가 아니다.** 그런데 틀려 보이지 않는다 — 실측(BTCUSDT rsi<30 long, 176거래): 헤드라인 −0.91, 거래 축 CI −4.78~0.29 로 **헤드라인이 구간 안에 들어간다.** 라벨도 «Sharpe CI», 숫자 모양도 맞고 점추정도 구간 안이다. 축만 다르다. ⇒ 라벨이 스스로 축을 말하게 한다. 소비자는 이 값을 «무엇을 몇 번 재표집한 CI 인가» 문구에 그대로 쓴다(룰4 스코프 라벨).
    - `unit` string, required
    - `point` number, required
    - `lo` number, required
    - `hi` number, required
    - `ci_pct` number, required
    - `n_input` integer, required
    - `n_resamples` integer, required
    - `n_used` integer, required
    - `no_loss_pct` number, nullable
  - `sharpe_ci` MetricCI: 지표 하나의 부트스트랩 신뢰구간. ★ `unit` 이 **값으로** 있는 이유 (Q-B29 A, 2026-08-29): 각 지표의 CI 는 «그 헤드라인이 실제로 쓰는 입력» 을 재표집해야 한다. profit_factor ← 거래별 gross P/L → unit="trades" sharpe_ratio ← 일별 수익률 × √365 → unit="daily_returns" 거래를 재표집해 Sharpe CI 를 내면 **화면의 그 Sharpe 가 아니다.** 그런데 틀려 보이지 않는다 — 실측(BTCUSDT rsi<30 long, 176거래): 헤드라인 −0.91, 거래 축 CI −4.78~0.29 로 **헤드라인이 구간 안에 들어간다.** 라벨도 «Sharpe CI», 숫자 모양도 맞고 점추정도 구간 안이다. 축만 다르다. ⇒ 라벨이 스스로 축을 말하게 한다. 소비자는 이 값을 «무엇을 몇 번 재표집한 CI 인가» 문구에 그대로 쓴다(룰4 스코프 라벨).
    - `unit` string, required
    - `point` number, required
    - `lo` number, required
    - `hi` number, required
    - `ci_pct` number, required
    - `n_input` integer, required
    - `n_resamples` integer, required
    - `n_used` integer, required
    - `no_loss_pct` number, nullable
  - `sharpe_window_days` integer, nullable
  - `sharpe_annualized_below_one_year` boolean, nullable
  - `mc_p_value` number, nullable
  - `mc_percentile` number, nullable
  - `total_funding_pct` number
  - `funding_uncovered` string[]
  - `funding_missing` string[]
  - `tp_count` integer, required
  - `sl_count` integer, required
  - `timeout_count` integer, required
  - `sharpe_ratio` number
  - `sortino_ratio` number
  - `calmar_ratio` number
  - `coins_used` integer, required
  - `top_n_requested` integer, nullable: The top_n this run resolved to. null for explicit symbols= requests (no top-N universe).
  - `eligible_universe` integer, nullable: Coins eligible for a top-N universe at run time (stablecoins unless include_stablecoins, non-crypto and liquidity-dead markets excluded), read in the same selection as the coins run. null for symbols= requests (and on /backtest when the strategy is invalid, is_valid=false).
  - `universe_capped` boolean, nullable: eligible_universe < top_n_requested: the request asked for more coins than exist, so the whole eligible universe ran. A cap only — coins lacking data are reported in coins_missing_data. null for symbols= requests.
  - `coins_missing_data` integer, nullable: min(top_n_requested, eligible_universe) - (selected coins with at least one candle inside start_date..end_date). Counts selected coins with no complete candle up to the cutoff AND coins whose candles all fall outside the requested window (those stay in coins_used, so this can exceed min(top_n_requested, eligible_universe) - coins_used). null for symbols= requests.
  - `data_range` string, required
  - `equity_curve` EquityPoint[], required
    - `time` string, required
    - `value` number, required
  - `btc_hold_return_pct` number
  - `btc_hold_curve` EquityPoint[]
    - `time` string, required
    - `value` number, required
  - `coin_results` CoinResult[]
    - `symbol` string, required
    - `trades` integer, required
    - `wins` integer, required
    - `losses` integer, required
    - `win_rate` number, required
    - `profit_factor` number, required
    - `total_return_pct` number, required
    - `avg_pnl_pct` number, required
    - `tp_count` integer, required
    - `sl_count` integer, required
    - `timeout_count` integer, required
  - `yearly_stats` object[]
  - `rolling_sharpe` object[]
  - `best_trade_pct` number, nullable
  - `worst_trade_pct` number, nullable
  - `exposure_time_pct` number, nullable
  - `direction_breakdown` object, nullable
  - `monthly_stats` object[]
  - `start_sensitivity` object[]
  - `regime_performance` object, nullable
  - `trades` TradeItem[]
    - `symbol` string, required
    - `direction` string, required
    - `entry_time` string, required
    - `exit_time` string, required
    - `entry_price` number
    - `exit_price` number
    - `pnl_pct` number, required
    - `pnl_usd` number
    - `exit_reason` string, required
    - `bars_held` integer, required
    - `mfe_pct` number
    - `mae_pct` number
  - `trades_total` integer
  - `trades_truncated` integer
  - `ruin_index` integer, nullable
  - `ruin_trade` RuinTrade: The trade at which capital reached zero (Q-H1218). ruin_index counts trades in realisation (exit) order; a trade table listed in entry order shows a different row at position N, so the trade is named, not only numbered.
    - `symbol` string, required
    - `entry_time` string, required
    - `exit_time` string, required
    - `table_row` integer, nullable
    - `rows_shown` integer, nullable
  - `reliability_checks` ReliabilityCheck[], required: 축별 판정 레코드 — 이 응답의 «판정 SSoT». reliability_warning 은 파생물. ⚠ `verdict` 는 세 값이다. `not_evaluated` 를 «통과»로 세면 0거래 응답이 「4개 축 통과」로 읽힌다 — 그 축들은 실행조차 안 됐다.
    - `axis_id` 'trades' | 'ruin' | 'sample' | 'leverage' | 'diversification', required
    - `verdict` 'pass' | 'fail' | 'not_evaluated', required
    - `value` number, nullable
    - `threshold` number, nullable
    - `message` string, nullable
  - `reliability_warning` string, nullable, required: 계약은 build_reliability_warning 한 곳이 소유한다(룰3).

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/pruviq/apis/pruviq-simulation-api.md) · [All operations](https://skmtc.dev/pruviq/apis/pruviq-simulation-api/llms.txt) · [OpenAPI document](https://skmtc.dev/pruviq/apis/pruviq-simulation-api/revisions/972384919a74?raw)
