Coverage for src / monte_neo / cli / menu / data.py: 0%
78 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
1"""Data download workflow."""
3from __future__ import annotations
5from datetime import datetime, timedelta
6from typing import TYPE_CHECKING
8import questionary
9from rich.console import Console
10from rich.panel import Panel
12from monte_neo.cli.menu.symbol_selector import SymbolSelector
13from monte_neo.cli.styles import CUSTOM_STYLE
14from monte_neo.data.downloader import BinanceDownloader
15from monte_neo.utils.logger import get_logger
17if TYPE_CHECKING:
18 from monte_neo.cli.menu.main import InteractiveMenu
20logger = get_logger(__name__)
21console = Console()
24def download_data_workflow(menu: InteractiveMenu) -> None:
25 """Download market data workflow."""
26 console.print("\n[bold cyan]📊 Download Market Data[/]\n")
28 # Select symbol
29 if not menu._cached_symbols:
30 try:
31 menu.progress.start(100, "Fetching available symbols from Binance...")
32 downloader = BinanceDownloader()
33 symbols = downloader.get_available_symbols()
35 # Prioritize USDT pairs
36 usdt_pairs = [s for s in symbols if s.endswith("USDT")]
37 other_pairs = [s for s in symbols if not s.endswith("USDT")]
38 menu._cached_symbols = sorted(usdt_pairs) + sorted(other_pairs)
40 menu.progress.update(100, 100, "Done")
41 menu.progress.stop()
42 except Exception as e:
43 logger.error(f"Error fetching symbols: {e}")
44 menu.progress.stop()
45 console.print(f"[red]Error fetching symbols: {e}. Using default.[/]")
46 menu._cached_symbols = [menu._selected_symbol]
48 # Use the new multi-column searchable selector
49 selector = SymbolSelector(menu._cached_symbols, style=CUSTOM_STYLE)
50 symbol = selector.ask()
52 if not symbol:
53 return
55 # Select timeframe
56 timeframe = questionary.select(
57 "Timeframe:",
58 choices=["1m", "5m", "15m", "30m", "1h", "4h", "1d"],
59 default=menu._selected_timeframe,
60 style=CUSTOM_STYLE,
61 ).ask()
63 if not timeframe:
64 return
66 _process_download(menu, symbol, timeframe)
69def _process_download(menu: InteractiveMenu, symbol: str, timeframe: str) -> None:
70 # Check if exists
71 info = menu.storage.get_info(symbol, timeframe)
72 if info:
73 console.print(f"\n[yellow]⚠ Data for {symbol} {timeframe} already exists.[/]")
75 start = info.get('start_date')
76 end = info.get('end_date')
78 # Format dates if they exist
79 start_str = str(start).split(".")[0] if start else "Unknown"
80 end_str = str(end).split(".")[0] if end else "Unknown"
81 range_str = f"{start_str} ➜ {end_str}"
83 console.print(Panel(
84 f"Rows: {info.get('rows', 0):,}\n"
85 f"Range: {range_str}\n"
86 f"Size: {info.get('size_mb', 0):.2f} MB",
87 title="Existing Data",
88 border_style="yellow"
89 ))
91 if not questionary.confirm(
92 "Overwrite existing data?", default=False, style=CUSTOM_STYLE
93 ).ask():
94 return
96 # Select period
97 choices = [
98 {"name": "30 days", "value": 30},
99 {"name": "90 days", "value": 90},
100 {"name": "180 days", "value": 180},
101 {"name": "365 days (1 year)", "value": 365},
102 {"name": "730 days (2 years)", "value": 730},
103 {"name": "1095 days (3 years)", "value": 1095},
104 {"name": "1825 days (5 years)", "value": 1825},
105 {"name": "3650 days (10 years)", "value": 3650},
106 {"name": "Custom days...", "value": "custom"},
107 ]
109 days_val = questionary.select(
110 "Historical period:",
111 choices=choices,
112 style=CUSTOM_STYLE,
113 ).ask()
115 if not days_val:
116 return
118 if days_val == "custom":
119 days_str = questionary.text("Enter number of days:").ask()
120 try:
121 days = int(days_str)
122 except (ValueError, TypeError):
123 console.print("[red]Invalid number of days.[/]")
124 return
125 else:
126 days = days_val
128 menu._selected_symbol = symbol
129 menu._selected_timeframe = timeframe
131 try:
132 downloader = BinanceDownloader()
133 end_date = datetime.now()
134 start_date = end_date - timedelta(days=days)
136 menu.progress.start(100, f"Downloading {symbol}...")
137 data = downloader.download(
138 symbol, timeframe, start_date, end_date, menu.progress.update
139 )
140 menu.progress.update(100, 100, "Done")
141 menu.progress.stop()
143 menu.storage.save(data, symbol, timeframe)
145 console.print(f"[green]✓ Downloaded {len(data)} candles[/]")
146 console.print(f"[dim]Saved to: data/raw/{symbol}_{timeframe}.parquet[/]\n")
148 except Exception as e:
149 menu.progress.stop()
150 console.print(f"[red]✗ Download failed: {e}[/]\n")
151 logger.error(f"Download failed for {symbol}: {e}")