from flask import Flask, render_template, request, jsonify, send_file import yfinance as yf import pandas as pd import numpy as np import io import traceback import torch from datetime import datetime import copy from tvDatafeed import TvDatafeed, Interval import os import shutil import requests from model import Kronos, KronosTokenizer, KronosPredictor app = Flask(__name__) # Скопируем логотип в static при запуске logo_src = "/Users/kirillpigorev/.gemini/antigravity-ide/brain/0793dfd5-f02b-4fe6-a14e-0608fc119c2f/media__1781258918887.png" logo_dest = "static/logo.png" if os.path.exists(logo_src): os.makedirs("static", exist_ok=True) try: shutil.copy(logo_src, logo_dest) except Exception as e: print("Не удалось скопировать логотип:", e) # Глобальная инициализация модели (выполняется 1 раз при запуске сервера) print("Загрузка модели Kronos... (пожалуйста, подождите)") tokenizer = KronosTokenizer.from_pretrained("NeoQuasar/Kronos-Tokenizer-base") model = Kronos.from_pretrained("NeoQuasar/Kronos-base") predictor = KronosPredictor(model, tokenizer, max_context=256) print("Модель успешно загружена! Сервер готов к работе.") PROGRESS = {} @app.route('/') def index(): return render_template('index.html') @app.route('/api/search_ticker') def search_ticker(): query = request.args.get('q', '') if not query: return jsonify([]) try: headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Origin': 'https://www.tradingview.com', 'Referer': 'https://www.tradingview.com/' } url = f"https://symbol-search.tradingview.com/symbol_search/v3/?text={query}&hl=0&exchange=&lang=en&domain=production" resp = requests.get(url, headers=headers, timeout=5) if resp.status_code == 200: quotes = resp.json() # TradingView API may return a dict {"symbols": [...]} or a list directly if isinstance(quotes, dict): quotes = quotes.get('symbols', quotes.get('data', quotes.get('matches', []))) results = [] if isinstance(quotes, list): for q in quotes: if isinstance(q, dict) and 'symbol' in q: results.append({ 'symbol': q['symbol'], 'name': q.get('description', q.get('symbol', '')), 'exchange': q.get('exchange', ''), 'type': q.get('type', '') }) # Умная сортировка по релевантности для TradingView def get_score(item): sym = item['symbol'].upper() q_str = query.upper() score = 0 if sym == q_str: score += 1000 elif sym.startswith(q_str): score += 500 # Приоритет популярным биржам if item['exchange'] in ['BINANCE', 'OANDA', 'NASDAQ', 'NYSE', 'TVC']: score += 200 return score results.sort(key=get_score, reverse=True) return jsonify(results[:20]) return jsonify([]) except Exception as e: print("Ошибка поиска тикера:", e) return jsonify([]) def prep_df(d): df_prep = d.copy() if df_prep.empty: return df_prep # Приводим все колонки к нижнему регистру для совместимости TV и YF df_prep.columns = [str(c).lower() for c in df_prep.columns] # Берем только нужные 5 df_prep = df_prep[['open', 'high', 'low', 'close', 'volume']] df_prep['amount'] = df_prep['volume'] # Строгая валидация (никаких ffill/fillna) if df_prep.isnull().values.any(): raise ValueError("Данные содержат пропуски (NaN)") return df_prep def tv_to_yf(tv_ticker): tv_ticker = str(tv_ticker).upper().strip() if ':' in tv_ticker: tv_ticker = tv_ticker.split(':')[1] mapping = { 'XAUUSD': 'GC=F', 'GOLD': 'GC=F', 'XAGUSD': 'SI=F', 'SILVER': 'SI=F', 'USOIL': 'CL=F', 'WTI': 'CL=F', 'UKOIL': 'BZ=F', 'BRENT': 'BZ=F', 'SPX': '^GSPC', 'SPX500': '^GSPC', 'SPX500USD': '^GSPC', 'SP500': '^GSPC', 'SP500USD': '^GSPC', 'SP500-USD': '^GSPC', 'US500': '^GSPC', 'GSPC': '^GSPC', 'NDX': '^NDX', 'NAS100USD': '^NDX', 'US100': '^NDX', 'DJI': '^DJI', 'US30': '^DJI', 'VIX': '^VIX', 'DXY': 'DX-Y.NYB', } if tv_ticker in mapping: return mapping[tv_ticker] if tv_ticker.endswith('USDT'): return tv_ticker[:-4] + '-USD' if tv_ticker.endswith('-USD'): return tv_ticker if tv_ticker.endswith('USD'): if tv_ticker in ['EURUSD', 'GBPUSD', 'AUDUSD', 'NZDUSD']: return tv_ticker + '=X' return tv_ticker[:-3] + '-USD' if tv_ticker in ['USDJPY', 'USDCHF', 'USDCAD']: return tv_ticker + '=X' return tv_ticker # Инициализация tvDatafeed (гостевой режим) tv = TvDatafeed() @app.route('/api/progress') def progress_api(): task_id = request.args.get('task_id') return jsonify({'progress': PROGRESS.get(task_id, 0)}) @app.route('/api/forecast') def forecast_api(): raw_ticker = request.args.get('ticker', 'CLSK') tf = request.args.get('tf', '4h') anchor_type = request.args.get('anchor', 'max_vol') task_id = request.args.get('task_id', '') target_date_str = request.args.get('target_date', '') target_datetime = None if target_date_str: try: # Если передана только дата (10 символов), добавляем конец дня if len(target_date_str) == 10: target_datetime = pd.to_datetime(target_date_str + " 23:59:59") else: # Иначе парсим точное переданное время target_datetime = pd.to_datetime(target_date_str) except Exception as e: print("Target date parse error:", e) ticker_symbol = raw_ticker # Извлечение биржи, если она есть (например BINANCE:BTCUSDT) exchange = "" if ':' in ticker_symbol: exchange, ticker_symbol = ticker_symbol.split(':', 1) # Если биржа не указана (пользователь ввел просто AAPL), найдем ее через поиск TV if not exchange: try: url = f"https://symbol-search.tradingview.com/symbol_search/v3/?text={ticker_symbol}&hl=0&exchange=&lang=en&domain=production" res = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}, timeout=5) if res.status_code == 200: quotes = res.json() if isinstance(quotes, dict): quotes = quotes.get('symbols', quotes.get('data', quotes.get('matches', []))) if isinstance(quotes, list) and len(quotes) > 0: for q in quotes: if isinstance(q, dict) and q.get('symbol', '').upper() == ticker_symbol.upper(): exchange = q.get('exchange', '') break if not exchange and isinstance(quotes[0], dict): exchange = quotes[0].get('exchange', '') except Exception as e: print("Auto-exchange lookup failed:", e) if not exchange: exchange = "BINANCE" # Fallback print(f"[{exchange}:{ticker_symbol}] Получен запрос на прогноз (Таймфрейм: {tf})...") if task_id: PROGRESS[task_id] = 10 try: def fetch_tv(symbol, exch, interval_enum, bars): d = tv.get_hist(symbol, exch, interval=interval_enum, n_bars=bars) if d is None or d.empty: d = tv.get_hist(symbol, 'OANDA', interval=interval_enum, n_bars=bars) if d is None or d.empty: d = tv.get_hist(symbol, 'TVC', interval=interval_enum, n_bars=bars) if d is None or d.empty: d = tv.get_hist(symbol, 'CRYPTO', interval=interval_enum, n_bars=bars) if d is None or d.empty: d = tv.get_hist(symbol, 'NASDAQ', interval=interval_enum, n_bars=bars) if d is None or d.empty: d = tv.get_hist(symbol, 'NYSE', interval=interval_enum, n_bars=bars) return d def fetch_yf(symbol, yf_interval, yf_period): yf_ticker = tv_to_yf(symbol) ticker = yf.Ticker(yf_ticker) d = ticker.history(period=yf_period, interval=yf_interval) return d def get_validated_data(symbol, exch, tv_interval, yf_interval, yf_period, bars=1000): if target_datetime is not None: # Увеличиваем лимит свечей, если запрашиваем глубокую историю bars = 5000 result_df = None # 1. Пробуем TradingView try: tv_data = fetch_tv(symbol, exch, tv_interval, bars) if tv_data is not None and not tv_data.empty: crypto_exchanges = ['CRYPTO', 'BINANCE', 'COINBASE', 'KUCOIN', 'BYBIT', 'OKX'] if exch.upper() in crypto_exchanges: tv_data.index = tv_data.index.tz_localize('UTC').tz_convert('Europe/Berlin').tz_localize(None) else: tv_data.index = tv_data.index.tz_localize('America/New_York').tz_convert('Europe/Berlin').tz_localize(None) result_df = prep_df(tv_data) except Exception as e: print(f"TV fetch/validate error: {e}") # 2. Если TV не дал данных или в них дыры, пробуем Yahoo Finance if result_df is None: try: print(f"Fallback to YF for {symbol}...") yf_data = fetch_yf(symbol, yf_interval, yf_period) if yf_data is not None and not yf_data.empty: # Приводим YF-данные (tz-aware) к CET yf_data.index = yf_data.index.tz_convert('Europe/Berlin').tz_localize(None) result_df = prep_df(yf_data) except Exception as e: print(f"YF fetch/validate error: {e}") if result_df is None: raise ValueError(f"Недостаточно чистых данных для {symbol} (все источники вернули пустые данные или пропуски NaN)") # Обрезка истории, если задан target_date if target_datetime is not None: result_df = result_df[result_df.index <= target_datetime] if result_df.empty: raise ValueError(f"Нет данных для {symbol} до указанной даты {target_date_str}") return result_df if tf == '5m': data = get_validated_data(ticker_symbol, exchange, Interval.in_5_minute, "5m", "60d") timedelta_step = pd.Timedelta(minutes=5) freq_str = '5min' elif tf == '15m': data = get_validated_data(ticker_symbol, exchange, Interval.in_15_minute, "15m", "60d") timedelta_step = pd.Timedelta(minutes=15) freq_str = '15min' elif tf == '1h': data = get_validated_data(ticker_symbol, exchange, Interval.in_1_hour, "1h", "3mo") timedelta_step = pd.Timedelta(hours=1) freq_str = '1h' elif tf == '4h': data = get_validated_data(ticker_symbol, exchange, Interval.in_4_hour, "1h", "6mo") if not data.empty and data.index.freqstr != '4h': data = data.resample('4h').agg({ 'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum' }).dropna() timedelta_step = pd.Timedelta(hours=4) freq_str = '4h' elif tf == '1d': data = get_validated_data(ticker_symbol, exchange, Interval.in_daily, "1d", "2y") timedelta_step = pd.Timedelta(days=1) freq_str = '1d' elif tf == '1w': data = get_validated_data(ticker_symbol, exchange, Interval.in_weekly, "1wk", "max") timedelta_step = pd.Timedelta(weeks=1) freq_str = 'W' elif tf.startswith('mtf'): # MTF configs configs = { 'mtf_3': { 'base_tf': '1h', 'step': pd.Timedelta(hours=1), 'freq': '1h', 'layers': [ {'tf': '1d', 'period': '2y', 'interval': '1d', 'weight': 0.2, 'step': pd.Timedelta(days=1), 'freq': '1d'}, {'tf': '4h', 'period': '6mo', 'interval': '1h', 'resample': '4h', 'weight': 0.3, 'step': pd.Timedelta(hours=4), 'freq': '4h'}, {'tf': '1h', 'period': '3mo', 'interval': '1h', 'weight': 0.5, 'step': pd.Timedelta(hours=1), 'freq': '1h'} ] }, 'mtf_2': { 'base_tf': '15m', 'step': pd.Timedelta(minutes=15), 'freq': '15min', 'layers': [ {'tf': '1d', 'period': '2y', 'interval': '1d', 'weight': 0.1, 'step': pd.Timedelta(days=1), 'freq': '1d'}, {'tf': '4h', 'period': '6mo', 'interval': '1h', 'resample': '4h', 'weight': 0.2, 'step': pd.Timedelta(hours=4), 'freq': '4h'}, {'tf': '1h', 'period': '3mo', 'interval': '1h', 'weight': 0.3, 'step': pd.Timedelta(hours=1), 'freq': '1h'}, {'tf': '15m', 'period': '60d', 'interval': '15m', 'weight': 0.4, 'step': pd.Timedelta(minutes=15), 'freq': '15min'} ] }, 'mtf_1': { 'base_tf': '5m', 'step': pd.Timedelta(minutes=5), 'freq': '5min', 'layers': [ {'tf': '1d', 'period': '2y', 'interval': '1d', 'weight': 0.05, 'step': pd.Timedelta(days=1), 'freq': '1d'}, {'tf': '4h', 'period': '6mo', 'interval': '1h', 'resample': '4h', 'weight': 0.1, 'step': pd.Timedelta(hours=4), 'freq': '4h'}, {'tf': '1h', 'period': '3mo', 'interval': '1h', 'weight': 0.15, 'step': pd.Timedelta(hours=1), 'freq': '1h'}, {'tf': '15m', 'period': '60d', 'interval': '15m', 'weight': 0.3, 'step': pd.Timedelta(minutes=15), 'freq': '15min'}, {'tf': '5m', 'period': '60d', 'interval': '5m', 'weight': 0.4, 'step': pd.Timedelta(minutes=5), 'freq': '5min'} ] }, 'mtf_4': { 'base_tf': '4h', 'step': pd.Timedelta(hours=4), 'freq': '4h', 'layers': [ {'tf': '1w', 'period': 'max', 'interval': '1wk', 'resample': '1w', 'weight': 0.2, 'step': pd.Timedelta(weeks=1), 'freq': 'W'}, {'tf': '1d', 'period': '2y', 'interval': '1d', 'weight': 0.3, 'step': pd.Timedelta(days=1), 'freq': '1d'}, {'tf': '4h', 'period': '6mo', 'interval': '1h', 'resample': '4h', 'weight': 0.5, 'step': pd.Timedelta(hours=4), 'freq': '4h'} ] }, 'mtf_5': { 'base_tf': '1d', 'step': pd.Timedelta(days=1), 'freq': '1d', 'layers': [ {'tf': '1M', 'period': 'max', 'interval': '1mo', 'resample': '1M', 'weight': 0.2, 'step': pd.Timedelta(days=30), 'freq': 'ME'}, {'tf': '1w', 'period': 'max', 'interval': '1wk', 'resample': '1w', 'weight': 0.3, 'step': pd.Timedelta(weeks=1), 'freq': 'W'}, {'tf': '1d', 'period': '2y', 'interval': '1d', 'weight': 0.5, 'step': pd.Timedelta(days=1), 'freq': '1d'} ] } } cfg = configs[tf] pred_len = 24 # Map string interval to Interval enum and YF parameters tv_interval_map = { '1M': (Interval.in_monthly, "1mo", "max"), '1w': (Interval.in_weekly, "1wk", "max"), '1h': (Interval.in_1_hour, "1h", "3mo"), '4h': (Interval.in_4_hour, "1h", "6mo"), '1d': (Interval.in_daily, "1d", "2y"), '15m': (Interval.in_15_minute, "15m", "1mo"), '5m': (Interval.in_5_minute, "5m", "1mo") } super_median = None base_df = None base_pred = None for i, layer in enumerate(cfg['layers']): interval_enum, yf_int, yf_per = tv_interval_map.get(layer['tf'], (Interval.in_1_hour, "1h", "3mo")) try: d = get_validated_data(ticker_symbol, exchange, interval_enum, yf_int, yf_per, bars=2000) except ValueError as ve: return jsonify({'error': str(ve)}), 400 if layer.get('resample'): d = d.resample(layer['resample']).agg({'open': 'first', 'high': 'max', 'low': 'min', 'close': 'last', 'volume': 'sum'}).dropna() d['amount'] = d['volume'] if layer['tf'] == cfg['base_tf']: base_df = d # Защита от переполнения контекста модели (макс. 256) if len(d) < 256 and not d.empty: pad_size = 256 - len(d) pad_df = pd.concat([d.iloc[0:1]] * pad_size) pad_df['volume'] = 0 # нулевой объем для фейковых данных pad_dates = pd.date_range(end=d.index[0] - layer['step'], periods=pad_size, freq=layer['freq']) pad_df.index = pad_dates d_input = pd.concat([pad_df, d]).iloc[-256:] else: d_input = d.iloc[-256:] y_ts = pd.Series(pd.date_range(start=d_input.index[-1] + layer['step'], periods=pred_len, freq=layer['freq'])) if task_id: # Устанавливаем промежуточный прогресс перед началом расчета слоя PROGRESS[task_id] = 10 + int(80 * i / len(cfg['layers'])) pred = predictor.predict(df=d_input, x_timestamp=pd.Series(d_input.index), y_timestamp=y_ts, pred_len=pred_len, sample_count=30, return_samples=True) if task_id: PROGRESS[task_id] = 10 + int(80 * (i + 1) / len(cfg['layers'])) if torch.backends.mps.is_available(): torch.mps.empty_cache() elif torch.cuda.is_available(): torch.cuda.empty_cache() if layer['tf'] == cfg['base_tf']: base_pred = pred layer['df'] = d layer['pred'] = pred layer['y_ts'] = y_ts # Synthesize start_price = base_df['close'].iloc[-1] start_time = base_df.index[-1] target_dt_index = pd.date_range(start=start_time + cfg['step'], periods=pred_len, freq=cfg['freq']) target_dates = base_pred['y_ts'] = pd.Series(target_dt_index) final_median = np.zeros(pred_len) for layer in cfg['layers']: median_vals = layer['pred']['median']['close'].values if layer['tf'] == cfg['base_tf']: interp_vals = median_vals else: s = pd.Series(median_vals, index=layer['y_ts'].values) s.loc[start_time] = start_price s.sort_index(inplace=True) # resample and interpolate to base frequency interp_vals = s.resample(cfg['freq']).interpolate(method='time').reindex(target_dt_index).ffill().bfill().values if np.isnan(interp_vals).any(): interp_vals = np.nan_to_num(interp_vals, nan=s.iloc[-1]) final_median += interp_vals * layer['weight'] shift = final_median - base_pred['median']['close'].values samples_close = base_pred['raw_preds'][:, :, 3] shifted_samples = samples_close + shift # Тотальная защита от NaN if np.isnan(shifted_samples).any(): shifted_samples = np.nan_to_num(shifted_samples, nan=base_pred['median']['close'].values[-1]) if np.isnan(shift).any(): shift = np.nan_to_num(shift, nan=0.0) p05_arr = np.percentile(shifted_samples, 5, axis=0) p15_arr = np.percentile(shifted_samples, 15, axis=0) p85_arr = np.percentile(shifted_samples, 85, axis=0) p95_arr = np.percentile(shifted_samples, 95, axis=0) df = base_df pred_median_df = base_pred['median'].copy().ffill().bfill() pred_median_df['open'] += shift pred_median_df['high'] += shift pred_median_df['low'] += shift pred_median_df['close'] += shift else: return jsonify({'error': 'Неизвестный таймфрейм'}), 400 if not tf.startswith('mtf'): if data.empty: return jsonify({'error': f'Failed to load data for ticker {ticker_symbol}'}), 400 df = data.copy() # Защита от переполнения контекста модели (макс. 256) if len(df) < 256 and not df.empty: pad_size = 256 - len(df) pad_df = pd.concat([df.iloc[0:1]] * pad_size) pad_df['volume'] = 0 pad_dates = pd.date_range(end=df.index[0] - timedelta_step, periods=pad_size, freq=freq_str) pad_df.index = pad_dates df = pd.concat([pad_df, df]).iloc[-256:] else: df = df.iloc[-256:] x_timestamp = pd.Series(df.index) pred_len = 24 last_time = x_timestamp.iloc[-1] future_dates = pd.date_range(start=last_time + timedelta_step, periods=pred_len, freq=freq_str) y_timestamp = pd.Series(future_dates) if task_id: PROGRESS[task_id] = 50 prediction = predictor.predict( df=df, x_timestamp=x_timestamp, y_timestamp=y_timestamp, pred_len=pred_len, sample_count=30, return_samples=True ) if task_id: PROGRESS[task_id] = 90 pred_median_df = prediction['median'] samples_close = prediction['raw_preds'][:, :, 3] p05_arr = np.percentile(samples_close, 5, axis=0) p15_arr = np.percentile(samples_close, 15, axis=0) p85_arr = np.percentile(samples_close, 85, axis=0) p95_arr = np.percentile(samples_close, 95, axis=0) # Расчет Anchored VWAP и полос стандартного отклонения df['vwap'] = np.nan df['vwap_std'] = np.nan last_cum_vol = 0 last_cum_pv = 0 last_cum_pv2 = 0 if anchor_type != 'none': if anchor_type == 'high': anchor_idx = df['high'].idxmax() elif anchor_type == 'low': anchor_idx = df['low'].idxmin() elif anchor_type == 'gap': gap_series = (df['open'] - df['close'].shift(1)).abs() / df['close'].shift(1) anchor_idx = gap_series.fillna(0).idxmax() else: # 'max_vol' anchor_idx = df['volume'].idxmax() df_anchored = df.loc[anchor_idx:].copy() cum_vol = df_anchored['volume'].cumsum() cum_pv = (df_anchored['close'] * df_anchored['volume']).cumsum() cum_pv2 = ((df_anchored['close']**2) * df_anchored['volume']).cumsum() vwap_series = cum_pv / cum_vol vwap_var_series = (cum_pv2 / cum_vol) - (vwap_series**2) vwap_var_series = vwap_var_series.clip(lower=0) # Защита от отрицательных значений vwap_std_series = np.sqrt(vwap_var_series) df.loc[anchor_idx:, 'vwap'] = vwap_series df.loc[anchor_idx:, 'vwap_std'] = vwap_std_series last_cum_vol = cum_vol.iloc[-1] if not cum_vol.empty else 0 last_cum_pv = cum_pv.iloc[-1] if not cum_pv.empty else 0 last_cum_pv2 = cum_pv2.iloc[-1] if not cum_pv2.empty else 0 hist_list = [] hist_vwap_list = [] hist_vwap_up1_list = [] hist_vwap_up2_list = [] hist_vwap_dn1_list = [] hist_vwap_dn2_list = [] pred_vwap_list = [] pred_vwap_up1_list = [] pred_vwap_up2_list = [] pred_vwap_dn1_list = [] pred_vwap_dn2_list = [] for i, row in df.iterrows(): hist_list.append({ 'time': int(i.timestamp()), 'open': float(row['open']), 'high': float(row['high']), 'low': float(row['low']), 'close': float(row['close']) }) if not np.isnan(row['vwap']): t = int(i.timestamp()) v = float(row['vwap']) s = float(row['vwap_std']) hist_vwap_list.append({'time': t, 'value': v}) hist_vwap_up1_list.append({'time': t, 'value': v + s}) hist_vwap_up2_list.append({'time': t, 'value': v + 2*s}) hist_vwap_dn1_list.append({'time': t, 'value': v - s}) hist_vwap_dn2_list.append({'time': t, 'value': v - 2*s}) if hist_vwap_list: pred_vwap_list.append(hist_vwap_list[-1]) pred_vwap_up1_list.append(hist_vwap_up1_list[-1]) pred_vwap_up2_list.append(hist_vwap_up2_list[-1]) pred_vwap_dn1_list.append(hist_vwap_dn1_list[-1]) pred_vwap_dn2_list.append(hist_vwap_dn2_list[-1]) pred_list = [] p05_list = [] p15_list = [] p85_list = [] p95_list = [] for idx, (i, row) in enumerate(pred_median_df.iterrows()): if tf.startswith('mtf'): time_val = int(target_dates.iloc[idx].timestamp()) else: time_val = int(i.timestamp()) pred_close = float(row['close']) pred_vol = float(row.get('volume', 0.0)) # Forecast VWAP if last_cum_vol > 0 and pred_vol > 0: last_cum_pv += pred_close * pred_vol last_cum_pv2 += pred_vol * (pred_close**2) last_cum_vol += pred_vol cur_vwap = last_cum_pv / last_cum_vol cur_var = max(0.0, (last_cum_pv2 / last_cum_vol) - cur_vwap**2) cur_std = float(np.sqrt(cur_var)) vwap_list_point = {'time': time_val, 'value': float(cur_vwap)} vwap_up1_point = {'time': time_val, 'value': float(cur_vwap + cur_std)} vwap_up2_point = {'time': time_val, 'value': float(cur_vwap + 2*cur_std)} vwap_dn1_point = {'time': time_val, 'value': float(cur_vwap - cur_std)} vwap_dn2_point = {'time': time_val, 'value': float(cur_vwap - 2*cur_std)} pred_vwap_list.append(vwap_list_point) pred_vwap_up1_list.append(vwap_up1_point) pred_vwap_up2_list.append(vwap_up2_point) pred_vwap_dn1_list.append(vwap_dn1_point) pred_vwap_dn2_list.append(vwap_dn2_point) pred_list.append({ 'time': time_val, 'open': float(row['open']), 'high': float(row['high']), 'low': float(row['low']), 'close': pred_close }) p05_list.append({'time': time_val, 'value': float(p05_arr[idx])}) p15_list.append({'time': time_val, 'value': float(p15_arr[idx])}) p85_list.append({'time': time_val, 'value': float(p85_arr[idx])}) p95_list.append({'time': time_val, 'value': float(p95_arr[idx])}) last_hist = hist_list[-1] # Пересчитываем OHLC для прогноза, чтобы свечи шли слитно и имели адекватные тени for row in pred_list: row['open'] = float(row['open']) row['close'] = float(row['close']) row['high'] = float(row['high']) row['low'] = float(row['low']) pred_list = [p for p in pred_list if p['time'] > last_hist['time']] pred_list.insert(0, last_hist) for lst in [p05_list, p15_list, p85_list, p95_list]: new_lst = [p for p in lst if p['time'] > last_hist['time']] new_lst.insert(0, {'time': last_hist['time'], 'value': last_hist['close']}) lst.clear() lst.extend(new_lst) print(f"[{ticker_symbol}] Прогноз успешно сгенерирован!") return jsonify({ 'success': True, 'history': hist_list, 'prediction': pred_list, 'p05': p05_list, 'p15': p15_list, 'p85': p85_list, 'p95': p95_list, 'hist_vwap': hist_vwap_list, 'hist_vwap_up1': hist_vwap_up1_list, 'hist_vwap_up2': hist_vwap_up2_list, 'hist_vwap_dn1': hist_vwap_dn1_list, 'hist_vwap_dn2': hist_vwap_dn2_list, 'pred_vwap': pred_vwap_list, 'pred_vwap_up1': pred_vwap_up1_list, 'pred_vwap_up2': pred_vwap_up2_list, 'pred_vwap_dn1': pred_vwap_dn1_list, 'pred_vwap_dn2': pred_vwap_dn2_list }) except Exception as e: import traceback traceback.print_exc() print(f"[{ticker_symbol}] Ошибка: {e}") return jsonify({'error': str(e)}), 500 @app.route('/loading_meme') def loading_meme(): return send_file("/Users/kirillpigorev/.gemini/antigravity-ide/brain/0793dfd5-f02b-4fe6-a14e-0608fc119c2f/trading_doge_pixel_1781280876615.png") if __name__ == '__main__': app.run(host='0.0.0.0', port=5001, debug=True)