Улучши код. Сейчас цены берутся из base price и потом рандомно составляются для каждого часа. Нужно чтобы была возможность задавать историю цен с привязкой к дате (по дням), а это переводилось в часовой график (часовые значения разумеется рандомные). Даты для разных тикеров разные

from fastapi import APIRouter, HTTPException, Query
from enum import Enum
from typing import Literal, Optional, Dict, Any, List
from datetime import datetime, timedelta
import random
import math
from psycopg import connect
from psycopg.rows import dict_row
from msdsdb import pg
import pandas as pd

router = APIRouter()
import os

class TradingMode(str, Enum):
    TQBR = "TQBR"
    TQBC = "TQBC"


sample_anomalies = [
    {
        "ticker": "AAPL",
        "name": "Apple Inc.",
        "anomaly": "grow",
        "mode": "TQBR",
        "price_history": [150, 152, 155, 154, 158],
    },
    {
        "ticker": "TSLA",
        "name": "Tesla Inc.",
        "anomaly": "decline",
        "mode": "TQBR",
        "price_history": [800, 790, 780, 770, 760],
    },
    {
        "ticker": "NVDA",
        "name": "NVIDIA Corp.",
        "anomaly": "grow",
        "mode": "TQBC",
        "price_history": [420, 430, 440, 455, 460],
    },
]


@router.get("/anomalies")
async def get_anomalies(mode: Optional[TradingMode] = Query(None, description="Filter by trading mode")):
    """Return a list of stocks that show anomalous price behaviour."""
    if mode:
        return [a for a in sample_anomalies if a["mode"] == mode]
    return sample_anomalies


# ----------------------------------------------------------------------------
# Fake stats helpers & dataset
# ----------------------------------------------------------------------------

def generate_realistic_candles(
    base_price: float, 
    hours: int, 
    volatility: float = 0.02,  # 2% волатильность
    trend: float = 0.0001,      # небольшой тренд
    volume_base: int = 2000000
) -> List[Dict[str, Any]]:
    """
    Генерирует реалистичные свечные данные с правильными OHLC соотношениями.
    """
    now = datetime.utcnow()
    candles = []
    
    # Начальная цена
    current_price = base_price
    
    for i in range(hours):
        timestamp = now - timedelta(hours=hours-i-1)
        
        # Применяем тренд и добавляем случайное блуждание
        drift = trend * base_price
        random_walk = random.gauss(0, volatility * base_price)
        
        # Open - это close предыдущей свечи (или текущая цена для первой)
        open_price = current_price
        
        # Генерируем внутрисвечное движение
        # Используем нормальное распределение для более реалистичного движения
        intracandle_moves = []
        for _ in range(20):  # 20 тиков внутри свечи
            move = random.gauss(drift/20, volatility * base_price / math.sqrt(20))
            intracandle_moves.append(current_price + move)
        
        # High и Low - экстремумы внутри свечи
        high = max(intracandle_moves + [open_price])
        low = min(intracandle_moves + [open_price])
        
        # Close - последнее значение с учетом тренда
        close_price = open_price + drift + random_walk
        
        # Убеждаемся, что high >= max(open, close) и low <= min(open, close)
        high = max(high, open_price, close_price)
        low = min(low, open_price, close_price)
        
        # Объем зависит от волатильности (больше движение = больше объем)
        price_change = abs(close_price - open_price) / open_price
        volume_multiplier = 1 + price_change * 10  # больше изменение = больше объем
        volume = int(volume_base * volume_multiplier * random.uniform(0.7, 1.3))
        
        # Добавляем внутридневные паттерны для объема
        hour_of_day = timestamp.hour
        if hour_of_day in [9, 10, 15, 16]:  # часы открытия/закрытия рынка
            volume = int(volume * 1.5)
        elif hour_of_day in [12, 13]:  # обеденное время
            volume = int(volume * 0.7)
        
        candles.append({
            "timestamp": int(timestamp.timestamp() * 1000),
            "open": round(open_price, 2),
            "high": round(high, 2),
            "low": round(low, 2),
            "close": round(close_price, 2),
            "volume": volume
        })
        
        # Обновляем текущую цену для следующей свечи
        current_price = close_price
    
    return candles


stock_profiles = {
    "AAPL": {
        "base_price": 150,
        "volatility": 0.015,  # 1.5% - относительно стабильная
        "trend": 0.0002,      # небольшой восходящий тренд
        "volume_base": 3000000
    },
    "TSLA": {
        "base_price": 780,
        "volatility": 0.035,  # 3.5% - высокая волатильность
        "trend": -0.0001,     # небольшой нисходящий тренд
        "volume_base": 5000000
    },
    "NVDA": {
        "base_price": 450,
        "volatility": 0.025,  # 2.5% - средняя волатильность
        "trend": 0.0003,      # сильный восходящий тренд
        "volume_base": 4000000
    }
}

sample_stats: Dict[str, Dict[str, Dict[str, Any]]] = {}

# Генерируем данные для каждой акции и периода
for ticker, profile in stock_profiles.items():
    sample_stats[ticker] = {}
    
    # 3 дня
    sample_stats[ticker]["3d"] = {
        "sentiment": {
            "good": {"count": random.randint(10, 20), "importance_avg": random.uniform(0.7, 0.9)},
            "neutral": {"count": random.randint(5, 15), "importance_avg": random.uniform(0.4, 0.6)},
            "bad": {"count": random.randint(1, 5), "importance_avg": random.uniform(0.5, 0.7)},
        },
        "summary": [f"{ticker} показывает краткосрочную динамику"],
        "price": generate_realistic_candles(
            profile["base_price"],
            hours=72,
            volatility=profile["volatility"],
            trend=profile["trend"],
            volume_base=profile["volume_base"]
        )
    }
    
    # 1 неделя
    sample_stats[ticker]["1w"] = {
        "sentiment": {
            "good": {"count": random.randint(20, 40), "importance_avg": random.uniform(0.65, 0.85)},
            "neutral": {"count": random.randint(10, 25), "importance_avg": random.uniform(0.4, 0.6)},
            "bad": {"count": random.randint(3, 10), "importance_avg": random.uniform(0.5, 0.7)},
        },
        "summary": [f"{ticker} недельный обзор: волатильность в пределах нормы"],
        "price": generate_realistic_candles(
            profile["base_price"] * 0.98,  # немного ниже для недели
            hours=168,
            volatility=profile["volatility"] * 0.9,  # чуть меньше волатильность на длинном периоде
            trend=profile["trend"],
            volume_base=profile["volume_base"]
        )
    }
    
    # 1 месяц
    sample_stats[ticker]["1m"] = {
        "sentiment": {
            "good": {"count": random.randint(60, 100), "importance_avg": random.uniform(0.6, 0.8)},
            "neutral": {"count": random.randint(30, 60), "importance_avg": random.uniform(0.4, 0.6)},
            "bad": {"count": random.randint(10, 30), "importance_avg": random.uniform(0.5, 0.7)},
        },
        "summary": [f"{ticker} месячный отчет: долгосрочные перспективы"],
        "price": generate_realistic_candles(
            profile["base_price"] * 0.95,  # еще ниже для месяца
            hours=720,
            volatility=profile["volatility"] * 0.8,  # еще меньше волатильность
            trend=profile["trend"],
            volume_base=profile["volume_base"]
        )
    }

@router.get("/{ticker}/stats")
async def get_stats(
    ticker: str,
    period: Literal["3d", "1w", "1m"] = Query("3d", description="Time period: 3d, 1w, 1m"),
):
    """Return sentiment statistics, news summary and price data for a ticker symbol."""
    ticker = ticker.upper()
    if ticker not in sample_stats:
        raise HTTPException(status_code=404, detail=f"Statistics not found for ticker {ticker}")
    if period not in sample_stats[ticker]:
        raise HTTPException(status_code=404, detail=f"Statistics not found for period {period}")
    return sample_stats[ticker][period]