Growlex / app.py
Kirill Pigorev
Clean deploy to Hugging Face
fda99bb
Raw
History Blame Contribute Delete
32.1 kB
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)