# ── TURBINE · VOLUME leg (v2 supervised) ────────────────────────────
# Funding-fade volume rotation, fast force-close. 7 slots, 10-min hard timeout.
#
# Ported from senpi-skills/turbine (producer v3.2.2, config v3.3) to the v2
# supervised external-scanner contract. The producer daemon is gone: the runtime
# supervises ../scanners/scan.py (SHARED with the runners leg) and calls
# scan(inputs, ctx) every `interval_seconds`. Both turbine legs run the SAME
# rotation alpha — only the sizing (inputs) and DSL exit differ. See scan.py.
name: turbine-volume
version: 2.0.0
group: turbine                      # agent-facing handle (BOTH legs share it)
description: >
  TURBINE VOLUME — Funding-fade rotation across BTC/ETH/SOL/HYPE + xyz
  (BRENTOIL/GOLD/SPX), probabilistic xyz-weighted pool pick. Pure high-notional
  volume printing: 7 slots @ $700 margin, 5x, force-closed by a 10-min DSL hard
  timeout with no Phase 2. Shares the rotation scanner with the RUNNERS leg on a
  separate wallet; the wallet boundary selects the DSL exit.

strategy:
  wallet: "${TURBINE_VOLUME_WALLET}"
  slots: 7                          # source slots.volume 7
  margin_per_slot: 100              # FALLBACK sizing — used only if a signal omits marginPct. scan.py emits per-signal marginPct (% of withdrawable, from inputs below), which WINS. TEST 100; prod source margin.volume 700 (config v3.3)
  default_leverage: 5               # source leverage.volume 5 (clamped per-asset in scan.py)
  trading_risk: aggressive          # source trading_risk aggressive
  enabled: true

scanners:
  - name: position_tracker          # built-in: feeds DSL exit engine
    type: position_tracker
    interval_seconds: 10            # source interval 10s

  - name: turbine_volume_signals    # supervised external scanner (SHARED scan.py)
    type: external_scanner
    path: ./scanners                # shared rotation scanner (one dir, two recipes)
    entrypoint: scan.py
    interval_seconds: 60            # source daemon interval_seconds 60
    timeout_seconds: 45             # source tick_timeout 45
    default_signal_validity_seconds: 120   # 2x tick; pass-through gate re-scores next tick
    state_history_max_count: 200    # holds rotation index + held/last-closed/dedup records
    inputs:
      # ── rotation knobs (config v3.3) ──
      xyzWeight: 0.80               # source xyzWeight 0.80
      spreadMainBps: 3             # source spread.mainBps 3
      spreadXyzBps: 10             # source spread.xyzBps 10
      # ── per-leg sizing (volume) ──
      leverage: 5                  # source leverage.volume 5
      marginPct: 20                # per-signal % of withdrawable; scan.py emits it TOP-LEVEL → runtime honors it as the EFFECTIVE sizing (overrides margin_per_slot). 20 is an example default; prod source 700 USD intent must be sized as a percent
      maxSlots: 7                  # source slots.volume 7 (per-tick emission ceiling)
    signal_data_schema:
      thesis: { type: string }
      fundingRegime: { type: string, required: false }
      fundingAnnualizedPct: { type: number, required: false }
      spreadBps: { type: number, required: false }
      isXyz: { type: boolean, required: false }

actions:
  - name: position_tracker_action   # rule-based lifecycle → DSL
    action_type: POSITION_TRACKER
    decision_mode: rule
    scanners: [position_tracker]

  - name: turbine_volume_entry      # OPEN_POSITION, rule mode (source LLM gate was pure pass-through)
    action_type: OPEN_POSITION
    decision_mode: rule
    scanners: [turbine_volume_signals]
    params:
      order_type: FEE_OPTIMIZED_LIMIT
      fee_optimized_limit_options:
        ensure_execution_as_taker: false   # source volume: maker-only (free volume)
        execution_timeout_seconds: 60      # source exit timeout 60 (entry was 180; v2 owns order lifecycle)
    context:
      - type: signal
        scanner: turbine_volume_signals

# ── EXIT (DSL — pure rotation, force-close on the clock; NO Phase 2) ──
exit:
  engine: dsl
  interval_seconds: 30              # source exit interval_seconds 30
  order_type: FEE_OPTIMIZED_LIMIT
  fee_optimized_limit_options:
    ensure_execution_as_taker: false   # source volume exit taker false (free volume)
    execution_timeout_seconds: 60
  dsl_preset:
    hard_timeout:
      enabled: true
      interval_in_minutes: 10       # source hard_timeout 10min — force every position off the clock
    weak_peak_cut:
      enabled: false                # source disabled on volume
      interval_in_minutes: 60
      min_value: 1.0
    dead_weight_cut:
      enabled: false                # source disabled on volume
      interval_in_minutes: 60
    phase1:
      enabled: true
      max_loss_pct: 50.0            # source phase1 max_loss 50
      retrace_threshold: 30         # source phase1 retrace 30
      consecutive_breaches_required: 1
    phase2:
      enabled: false                # source: NO phase2 on volume — disabled, never advances
      # The v2 DSL parser requires a non-empty tiers array even when phase2 is
      # disabled (parsePreset: "must have tiers or phase2.tiers"). The source used
      # `tiers: []`, which v2 rejects. One inert placeholder tier satisfies the
      # parser; `enabled: false` keeps phase2 off (config.test.ts confirms a
      # disabled phase2 with tiers present yields phase2Enabled=false). This is the
      # only threshold that does not map 1:1 to the v2 schema for the volume leg.
      tiers:
        - { trigger_pct: 100, lock_hw_pct: 0 }

# ── RISK guard rails (from runtime-volume.yaml; minutes → seconds) ──
risk:
  data_retention_seconds: 259200    # 72h (was data_retention_hours 72)
  guard_rails:
    daily_loss_limit_pct: 50
    max_entries_per_day: 1500
    bypass_max_entries_per_day_on_profit: true
    max_consecutive_losses: 30
    cooldown_seconds: 60            # was cooldown_minutes 1 (schema min 60s)
    drawdown_halt_pct: 50
    drawdown_reset_on_day_rollover: true
    # per_asset_cooldown OMITTED — source set per_asset_cooldown_minutes: 0 (disabled).
    # v2 per_asset_cooldown_seconds has a min of 300; 0 is not expressible, and omitting
    # the key == disabled, which is the faithful representation of the source's intent.

notifications:
  telegram_chat_id: "${TELEGRAM_CHAT_ID}"
  dsl_lifecycle: false
  dsl_notify_sl_updates: false
  action_lifecycle: false
