Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,9 @@ venv.bak/
temp/
tmp/
.python-version

# Market data files (downloaded at runtime)
data/

# WebUI runtime output
webui/prediction_results/
102 changes: 102 additions & 0 deletions download_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""
Kronos - Downloader de dados de mercado
Baixa dados de qualquer ativo (cripto, acoes, forex) e salva em data/
para uso no Web UI.

Uso:
python download_data.py XRP-USD # XRP/USD - diario
python download_data.py BTC-USD --interval 1h
python download_data.py AAPL --interval 1d --period 2y
python download_data.py ETH-USD --interval 5m --period 60d
python download_data.py BTC-USD ETH-USD XRP-USD # multiplos de uma vez

Intervalos suportados: 1m 2m 5m 15m 30m 60m 90m 1h 1d 5d 1wk 1mo
Periodos suportados : 1d 5d 1mo 3mo 6mo 1y 2y 5y 10y ytd max
(para intervalos < 1h, o maximo e 60 dias)
"""

import argparse
import os
import sys
import pandas as pd
import yfinance as yf

DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")


def download(ticker: str, interval: str = "1d", period: str = "2y") -> None:
print(f" Baixando {ticker} intervalo={interval} periodo={period} ...")

df = yf.download(ticker, interval=interval, period=period, progress=False, auto_adjust=True)

if df.empty:
print(f" [ERRO] Nenhum dado retornado para '{ticker}'. Verifique o simbolo.")
return

# Flatten MultiIndex columns (yfinance retorna assim para tickers unicos as vezes)
if isinstance(df.columns, pd.MultiIndex):
df.columns = [col[0].lower() for col in df.columns]
else:
df.columns = [c.lower() for c in df.columns]

# Renomear colunas para o padrao Kronos
rename = {"adj close": "close"}
df = df.rename(columns=rename)

# Garantir colunas obrigatorias
required = ["open", "high", "low", "close"]
missing = [c for c in required if c not in df.columns]
if missing:
print(f" [ERRO] Colunas faltando apos download: {missing}. Colunas presentes: {list(df.columns)}")
return

# Resetar index e renomear coluna de data/hora
df = df.reset_index()
time_col = df.columns[0] # 'Datetime' ou 'Date'
df = df.rename(columns={time_col: "timestamps"})
df["timestamps"] = pd.to_datetime(df["timestamps"])

# Remover timezone para evitar problemas de serializacao
if df["timestamps"].dt.tz is not None:
df["timestamps"] = df["timestamps"].dt.tz_convert("UTC").dt.tz_localize(None)

# Manter apenas colunas uteis
keep = ["timestamps", "open", "high", "low", "close"]
if "volume" in df.columns:
keep.append("volume")
df = df[keep].dropna()

# Nome do arquivo: TICKER_interval.csv
safe_ticker = ticker.replace("/", "-").replace("=", "")
filename = f"{safe_ticker}_{interval}.csv"
out_path = os.path.join(DATA_DIR, filename)

os.makedirs(DATA_DIR, exist_ok=True)
df.to_csv(out_path, index=False)

size_kb = os.path.getsize(out_path) / 1024
print(f" [OK] {filename} ({len(df)} candles, {size_kb:.1f} KB) -> data/{filename}")


def main():
parser = argparse.ArgumentParser(
description="Baixa dados de mercado e salva em data/ para uso no Kronos Web UI."
)
parser.add_argument("tickers", nargs="+", help="Simbolos yfinance (ex: XRP-USD BTC-USD AAPL)")
parser.add_argument("--interval", default="1d",
help="Intervalo das velas (default: 1d). Ex: 5m 1h 1d")
parser.add_argument("--period", default="2y",
help="Periodo historico (default: 2y). Ex: 60d 1y 5y max")
args = parser.parse_args()

print(f"\nKronos Data Downloader")
print(f"Destino: {DATA_DIR}\n")

for ticker in args.tickers:
download(ticker.upper(), interval=args.interval, period=args.period)

print(f"\nConcluido! Reinicie o servidor ou recarregue a pagina para ver os novos arquivos.")


if __name__ == "__main__":
main()
104 changes: 104 additions & 0 deletions start.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Kronos - Startup Script
# Starts the Web UI on a random port, using Python with CUDA support

param(
[switch]$SkipInstall
)

$ErrorActionPreference = "Stop"

Write-Host "======================================" -ForegroundColor Cyan
Write-Host " Kronos - Startup" -ForegroundColor Cyan
Write-Host "======================================" -ForegroundColor Cyan
Write-Host ""

# If a venv is active, deactivate it to avoid using the wrong Python
if ($env:VIRTUAL_ENV) {
Write-Host "[INFO] Deactivating venv '$env:VIRTUAL_ENV' ..." -ForegroundColor Yellow
& deactivate 2>$null
# Manually clear venv variables from this session's PATH
$env:PATH = ($env:PATH -split ';' | Where-Object { $_ -notlike "$env:VIRTUAL_ENV*" }) -join ';'
Remove-Item Env:\VIRTUAL_ENV -ErrorAction SilentlyContinue
Remove-Item Env:\VIRTUAL_ENV_PROMPT -ErrorAction SilentlyContinue
}

# Find Python with CUDA support (search all instances in PATH)
$pythonExe = $null
$candidatos = (where.exe python 2>$null) -split "`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" -and (Test-Path $_) }

foreach ($cand in $candidatos) {
$result = & $cand -c "import torch; print(torch.cuda.is_available())" 2>$null
if ($result -eq "True") {
$pythonExe = $cand
break
}
}

if (-not $pythonExe) {
# Fallback: accept CPU if no CUDA found
$pythonExe = (Get-Command python -ErrorAction SilentlyContinue)?.Source
if (-not $pythonExe) {
Write-Host "[ERROR] Python not found in PATH. Install Python 3.10+ and try again." -ForegroundColor Red
exit 1
}
Write-Host "[WARNING] Python with CUDA not found. Using CPU: $pythonExe" -ForegroundColor Yellow
Write-Host " Para CUDA, instale: pip install torch --index-url https://download.pytorch.org/whl/cu128" -ForegroundColor Yellow
} else {
$pyVersion = & $pythonExe --version 2>&1
$gpuName = & $pythonExe -c "import torch; print(torch.cuda.get_device_name(0))" 2>&1
Write-Host "[OK] $pyVersion | CUDA - $gpuName" -ForegroundColor Green
Write-Host "[OK] Python: $pythonExe" -ForegroundColor Green
}

# Project root directory
$projectRoot = $PSScriptRoot
Set-Location $projectRoot

# Install dependencies
if (-not $SkipInstall) {
Write-Host "[...] Installing project dependencies ..." -ForegroundColor Yellow
& $pythonExe -m pip install -r (Join-Path $projectRoot "requirements.txt") --quiet

$webuiReqs = Join-Path $projectRoot "webui\requirements.txt"
if (Test-Path $webuiReqs) {
Write-Host "[...] Installing Web UI dependencies ..." -ForegroundColor Yellow
& $pythonExe -m pip install -r $webuiReqs --quiet
}
Write-Host "[OK] Dependencies installed." -ForegroundColor Green
} else {
Write-Host "[SKIP] Dependency installation skipped (-SkipInstall)." -ForegroundColor Yellow
}

# Generate random port between 10000 and 60000 (avoids well-known ports)
$port = Get-Random -Minimum 10000 -Maximum 60000
for ($i = 0; $i -lt 10; $i++) {
if (-not (Get-NetTCPConnection -LocalPort $port -ErrorAction SilentlyContinue)) { break }
$port = Get-Random -Minimum 10000 -Maximum 60000
}

Write-Host ""
Write-Host "======================================" -ForegroundColor Cyan
Write-Host " Kronos Web UI" -ForegroundColor Cyan
Write-Host " Port : $port" -ForegroundColor Cyan
Write-Host " URL : http://localhost:$port" -ForegroundColor Cyan
Write-Host "======================================" -ForegroundColor Cyan
Write-Host "Press Ctrl+C to stop the server." -ForegroundColor DarkGray
Write-Host ""

# Open browser automatically after a few seconds
Start-Job -ScriptBlock {
param($url)
Start-Sleep -Seconds 3
Start-Process $url
} -ArgumentList "http://localhost:$port" | Out-Null

# Start Flask on the random port
Set-Location (Join-Path $projectRoot "webui")
$env:FLASK_APP = "app.py"

& $pythonExe -c @"
import sys, os
sys.path.insert(0, '..')
from webui.app import app
app.run(debug=False, host='127.0.0.1', port=$port)
"@
Loading