Локальная аналитика для RL-агентов

Локальная аналитика для RL-агентов

Обзор

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

Проблемы старой архитектуры

  1. Глобальные снапшоты — аналитика рассчитывалась один раз для всех агентов
  2. Неактуальность данных — метрики не обновлялись в процессе симуляции
  3. Конфликты между тестами — параллельные тесты могли влиять друг на друга
  4. Низкая производительность — избыточные вычисления и блокировки

Новая архитектура

Архитектура локальной аналитики

graph TB
    subgraph "Локальная аналитика"
        ALA1[AgentLocalAnalytics 1]
        ALA2[AgentLocalAnalytics 2]
        ALA3[AgentLocalAnalytics N]
    end
    
    subgraph "Пул аналитики"
        POOL[AnalyticsPool]
    end
    
    subgraph "Данные"
        QUOTES1[Quotes Agent 1]
        QUOTES2[Quotes Agent 2]
        QUOTES3[Quotes Agent N]
    end
    
    subgraph "Результаты"
        ANALYTICS1[Analytics Agent 1]
        ANALYTICS2[Analytics Agent 2]
        ANALYTICS3[Analytics Agent N]
    end
    
    QUOTES1 --> ALA1
    QUOTES2 --> ALA2
    QUOTES3 --> ALA3
    ALA1 --> ANALYTICS1
    ALA2 --> ANALYTICS2
    ALA3 --> ANALYTICS3
    ALA1 --> POOL
    ALA2 --> POOL
    ALA3 --> POOL

1. AgentLocalAnalytics

Каждый агент имеет свою локальную аналитику:

type AgentLocalAnalytics struct {
    agentID     string
    testID      string
    quotes      []model.Quote
    maxWindow   int
    mu          sync.RWMutex
    log         *slog.Logger
    lastHash    string
    lastAnalytics SymbolAnalytics
}

Особенности:

  • Изоляция — каждый агент имеет свою аналитику
  • Скользящее окно — поддерживается фиксированный размер буфера котировок
  • Кэширование — аналитика пересчитывается только при изменении данных
  • Потокобезопасность — использование RWMutex для конкурентного доступа

2. AnalyticsPool

Пул для управления локальной аналитикой агентов:

type AnalyticsPool struct {
    agents map[string]*AgentLocalAnalytics
    mu     sync.RWMutex
    log    *slog.Logger
}

Функции:

  • Создание и управление локальной аналитикой агентов
  • Очистка ресурсов по завершении тестов
  • Статистика использования

Использование

В симуляторе

func simulateRLAgent(...) {
    // Создаём локальную аналитику для агента
    agentAnalytics := analytics.NewAgentLocalAnalytics(
        agent.GetID(),
        testID,
        100, // окно 100 котировок
        log,
    )

    for i, quote := range data {
        // Добавляем котировку в локальную аналитику
        agentAnalytics.AddQuote(quote)
        
        // Получаем актуальную аналитику
        analytics := agentAnalytics.GetAnalytics()
        agent.SetAnalytics(&analytics)
        
        // Принимаем решение и обновляем модель
        action := agent.DecideAction()
        agent.Update(action, analytics.Volatility1h, reward, penalty, analytics, exchange, pair)
    }
}

В генетическом алгоритме

func createFitnessEvaluator(...) {
    return func(agent *rl_agent.RLAgent) (decimalutils.Decimal, error) {
        // Создаём локальную аналитику для каждого агента
        agentAnalytics := analytics.NewAgentLocalAnalytics(
            agent.GetID(),
            uniqueTestID,
            100,
            log,
        )
        
        // Симуляция с локальной аналитикой
        result, err := simulateRLAgent(...)
        return fitness, nil
    }
}

Преимущества

  1. Актуальность — аналитика всегда соответствует текущему состоянию
  2. Изоляция — каждый агент работает с собственной аналитикой
  3. Производительность — нет глобальных блокировок, эффективное кэширование
  4. Масштабируемость — легко добавлять новые агенты и тесты
  5. Параллелизм — тесты могут выполняться параллельно без конфликтов

Оптимизации

Кэширование по хешу

func (ala *AgentLocalAnalytics) calculateQuotesHash() string {
    // Используем последние 20 котировок для хеша
    hash := sha256.New()
    for i := start; i < len(ala.quotes); i++ {
        quote := ala.quotes[i]
        hash.Write([]byte(quote.Price.String()))
        hash.Write([]byte(quote.Timestamp.Format(time.RFC3339)))
    }
    return hex.EncodeToString(hash.Sum(nil))
}

Скользящее окно

func (ala *AgentLocalAnalytics) AddQuote(quote model.Quote) {
    ala.quotes = append(ala.quotes, quote)
    if len(ala.quotes) > ala.maxWindow {
        ala.quotes = ala.quotes[1:] // удаляем старые котировки
    }
}

Миграция

Для перехода на новую архитектуру:

  1. Заменить вызовы anal.GetAnalyticsSnapshot() на создание AgentLocalAnalytics
  2. Добавить AddQuote() в цикл симуляции
  3. Использовать GetAnalytics() вместо глобального снапшота
  4. Удалить вызовы ProcessQuotes() и ResetForTest()

Мониторинг

// Статистика пула
stats := analyticsPool.GetStats()
log.Info("Статистика аналитики",
    slog.Int("total_agents", stats["total_agents"]),
    slog.Int("ready_agents", stats["ready_agents"]),
    slog.Int("total_quotes", stats["total_quotes"]),
)