# bquant.analysis.zones.pipeline — Пайплайн анализа зон v2.1

Этот документ фиксирует ключевые изменения API, связанные с глобальным расчётом свингов. Все описания приведены на русском языке в соответствии с требованиями плана 6.2.2.

## Обзор рабочего процесса

1. `ZoneAnalysisPipeline._prepare_data()` — подготовка данных и вычисление индикаторов.
2. `ZoneAnalysisPipeline._calculate_global_swings()` — (новый шаг) глобальный расчёт свингов, если `swing_scope="global"`.
3. `ZoneAnalysisPipeline._detect_zones()` — детекция зон выбранной стратегией.
4. `ZoneAnalysisPipeline._inject_swing_context()` — (новый шаг) инъекция глобального контекста в каждую зону.
5. `ZoneAnalysisPipeline._analyze_zones()` — извлечение признаков, включая `ZoneFeaturesAnalyzer`.

Диаграмма последовательности:

```
prepare_dataframe() ──▶ _calculate_global_swings()
         │                    │
         │                    └──► SwingContext (глобальные точки)
         ▼
 detect_zones() ──▶ _inject_swing_context(zones, context)
         │
         ▼
 analyze_zones() ──▶ ZoneAnalysisResult (zones + features + metadata)
```

## `_calculate_global_swings(data: pd.DataFrame) -> SwingContext`

- Вызывается только при `config.swing_scope == "global"`.
- Получает подготовленный датафрейм (с индикаторами, ATR и т.п.).
- Находит активную стратегию свингов через `_get_active_swing_strategy()`.
- Требует, чтобы стратегия реализовала метод `calculate_global()` и вернула `SwingContext`.
- Логирует количество найденных точек, что важно для диагностики.
- При любой ошибке выбрасывает исключение, которое перехватывается на уровне `_run_without_cache()` и приводит к фолбэку в `per_zone`.

### Минимальный пример

```python
pipeline = ZoneAnalysisPipeline(config.with_swing_scope("global"), analyzer)
context = pipeline._calculate_global_swings(prepared_df)
print(len(context.swing_points))
```

## `_inject_swing_context(zones: List[ZoneInfo], swing_context: SwingContext)`

- Присваивает ссылку на глобальный контекст каждому `ZoneInfo`.
- Не изменяет порядок и не фильтрует зоны.
- Логирует количество обработанных зон (уровень DEBUG).
- После вызова метод `ZoneInfo.get_zone_swings()` возвращает глобальные точки без повторного расчёта.

### Контрольный сценарий

```python
zones = pipeline._detect_zones(prepared_df)
pipeline._inject_swing_context(zones, context)
assert all(zone.swing_context is context for zone in zones)
```

## `ZoneAnalysisBuilder.with_swing_scope(scope: Literal["per_zone", "global"])`

- Fluent-метод билдера, который управляет режимом расчёта свингов.
- Допустимые значения: `"per_zone"` (по умолчанию) и `"global"`.
- Хранит выбранное значение в `ZoneAnalysisConfig`, которое затем читает пайплайн.
- Сохраняет обратную совместимость: если метод не вызвать, поведение остаётся прежним.

### Пример использования билдера

```python
from bquant.analysis.zones import analyze_zones

result = (
    analyze_zones(df)
    .with_swing_scope("global")
    .with_strategies(swing="zigzag")
    .analyze()
    .build()
)
```

## Обработка ошибок и фолбэки

- Если стратегия не поддерживает `calculate_global`, пайплайн логирует предупреждение и переключается на `per_zone`.
- При неуспешном глобальном расчёте (исключение в стратегии) `_run_without_cache()` оставляет зоны без контекста; анализатор признаков автоматически использует локальные данные.
- В трассировке `ZoneAnalysisResult.metadata['swing_calculation_mode']` фиксируется фактический режим (глобальный или локальный).

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