diff --git a/src/screener/t_atr_ema_v2.py b/src/screener/t_atr_ema_v2.py index a1781c9..ba144ab 100644 --- a/src/screener/t_atr_ema_v2.py +++ b/src/screener/t_atr_ema_v2.py @@ -1,34 +1,35 @@ +from screener.user_input import get_interval_choice import os -import pandas as pd from datetime import datetime, timedelta +import pandas as pd from db.db_connection import create_client -from indicators.three_atr_ema import ThreeATREMAIndicator from trading.position_calculator import PositionCalculator from screener.t_sunnyband import get_stock_data +from indicators.three_atr_ema import ThreeATREMAIndicator def run_atr_ema_target_scanner(min_price: float, max_price: float, min_volume: int, portfolio_size: float = None): print(f"\nšŸ” Scanning for stocks ${min_price:.2f}-${max_price:.2f} with min volume {min_volume:,}") - # Set time range + interval = get_interval_choice() end_date = datetime.now() - start_date = end_date - timedelta(days=90) # Expanded from 30 to 90 days - market_days = pd.bdate_range(start=start_date.date(), end=end_date.date()) - if len(market_days) < 50: - start_date = end_date - timedelta(days=50*1.5) # Ensure 50 trading days coverage - start_ts = int(start_date.timestamp() * 1000000000) - end_ts = int(end_date.timestamp() * 1000000000) - + start_date = end_date - timedelta(days=1) # Last trading day + start_ts = int(start_date.timestamp() * 1e9) # Convert to nanoseconds + end_ts = int(end_date.timestamp() * 1e9) + client = create_client() try: - # Fetch stock prices within the defined range query = f""" WITH latest_data AS ( SELECT ticker, - argMax(close, window_start) AS last_close, sum(volume) AS total_volume, - max(window_start) AS last_update + argMax(open, window_start) AS last_open, + argMax(close, window_start) AS last_close, + argMax(high, window_start) AS last_high, + argMax(low, window_start) AS last_low, + max(window_start) AS last_update, + sum(transactions) AS transaction_count FROM stock_db.stock_prices WHERE window_start BETWEEN {start_ts} AND {end_ts} GROUP BY ticker @@ -37,93 +38,73 @@ def run_atr_ema_target_scanner(min_price: float, max_price: float, min_volume: i ) SELECT ticker, - last_close, total_volume, - last_update + last_open, + last_close, + last_high, + last_low, + last_update, + transaction_count FROM latest_data ORDER BY ticker """ result = client.query(query) - stocks = [(row[0], row[1], row[2], row[3]) for row in result.result_rows] + stocks = result.result_rows if not stocks: print("āŒ No stocks found matching criteria.") return - print("\nšŸ” Verifying data availability...") - valid_query = f""" - SELECT ticker - FROM ( - SELECT ticker, count() as cnt - FROM ( - SELECT - ticker, - toDate(window_start) as date - FROM stock_db.stock_prices - WHERE window_start BETWEEN {start_ts} AND {end_ts} - GROUP BY ticker, date - ) - GROUP BY ticker - HAVING count() >= 50 - - UNION ALL - - SELECT ticker, count() as cnt - FROM stock_db.stock_prices_daily - WHERE date BETWEEN '{start_date.date()}' AND '{end_date.date()}' - GROUP BY ticker - HAVING cnt >= 50 - ) - GROUP BY ticker - HAVING sum(cnt) >= 50 - """ - valid_result = client.query(valid_query) - valid_symbols = {row[0] for row in valid_result.result_rows} - qualified_stocks = [s for s in stocks if s[0] in valid_symbols] - - # Enhanced validation check - for ticker in list(qualified_stocks): - test_df = get_stock_data(ticker[0], start_date, end_date, "1d") - if test_df.empty or len(test_df) < 50: - print(f"🚫 Removing {ticker[0]} - insufficient initial data") - qualified_stocks.remove(ticker) - - print(f"\nāœ… Found {len(qualified_stocks)} stocks with sufficient historical data") + print(f"\nāœ… Found {len(stocks)} stocks matching criteria") + + # **Correct column order as per ClickHouse output** + columns = ["ticker", "volume", "open", "close", "high", "low", "window_start", "transactions"] + df_stocks = pd.DataFrame(stocks, columns=columns) + + # **Convert timestamps from nanoseconds to readable datetime** + df_stocks["window_start"] = pd.to_datetime(df_stocks["window_start"], unit="ns") + + # Debugging: Check if columns exist + print("\nšŸ“Š Data Sample from ClickHouse Query:") + print(df_stocks.head()) indicator = ThreeATREMAIndicator() calculator = PositionCalculator(portfolio_size, risk_percentage=1.0, stop_loss_percentage=7.0) if portfolio_size else None bullish_signals = [] - for ticker, current_price, current_volume, last_update in stocks: + for _, row in df_stocks.iterrows(): + ticker = row["ticker"] + current_price = row["close"] + current_volume = row["volume"] + last_update = row["window_start"] + try: - # Validate interval and fetch data - VALID_INTERVALS = ["1d", "5m", "15m", "30m", "1h", "4h", "1w"] - - try: - df = get_stock_data(ticker, start_date, end_date, "1d") - if df.empty or len(df) < 50: - df = get_stock_data(ticker, start_date - timedelta(days=30), end_date, "1d") # Try wider range - if df.empty or len(df) < 50: - print(f"āš ļø {ticker}: No valid data in extended timeframe") - continue - except Exception as e: - print(f"āŒ Data fetch failed for {ticker}: {str(e)[:100]}") + df = get_stock_data(ticker, start_date, end_date, interval) + + # **Check if DataFrame has expected columns** + if df.empty: + print(f"āš ļø No data found for {ticker}. Skipping.") continue - results = indicator.calculate(df).dropna().tail(30) # Use only most recent valid data + missing_cols = [col for col in ["close", "open", "high", "low", "volume"] if col not in df.columns] + if missing_cols: + print(f"āš ļø {ticker} data is missing columns: {missing_cols}. Skipping.") + print(df.head()) # Debugging output + continue + + results = indicator.calculate(df) last_row = results.iloc[-1] prev_row = results.iloc[-2] - # Check if entry condition is met bullish_entry = ( - last_row['close'] < last_row['ema'] and - prev_row['close'] <= prev_row['lower_band'] and - last_row['close'] > prev_row['close'] + last_row["close"] < last_row["ema"] and + prev_row["close"] <= prev_row["lower_band"] and + last_row["close"] > prev_row["close"] ) if bullish_entry: - entry_price = last_row['close'] + entry_price = last_row["close"] target_1 = entry_price * 1.10 # 10% profit target_2 = entry_price * 1.20 # 20% profit @@ -131,25 +112,25 @@ def run_atr_ema_target_scanner(min_price: float, max_price: float, min_volume: i trail_stop = None trail_active = False - if last_row['close'] >= last_row['upper_band']: + if last_row["close"] >= last_row["upper_band"]: trail_active = True - highest_price = max(results['high'].iloc[-5:]) # Last 5 days + highest_price = max(results["high"].iloc[-5:]) # Last 5 days trail_stop = highest_price * 0.98 # 2% below high # Position sizing position = calculator.calculate_position_size(entry_price, target_2) if calculator else None - position_size = position['position_value'] if position else None + position_size = position["position_value"] if position else None # Save signal signal_data = { - 'ticker': ticker, - 'entry_price': entry_price, - 'target_1': target_1, - 'target_2': target_2, - 'volume': current_volume, - 'last_update': datetime.fromtimestamp(last_update / 1000000000), - 'trail_stop': trail_stop, - 'position_size': position_size + "ticker": ticker, + "entry_price": entry_price, + "target_1": target_1, + "target_2": target_2, + "volume": current_volume, + "last_update": last_update, + "trail_stop": trail_stop, + "position_size": position_size } bullish_signals.append(signal_data) @@ -165,13 +146,13 @@ def run_atr_ema_target_scanner(min_price: float, max_price: float, min_volume: i # Save results if bullish_signals: - output_dir = 'reports' + output_dir = "reports" os.makedirs(output_dir, exist_ok=True) - output_file = f'{output_dir}/atr_ema_targets_{datetime.now().strftime("%Y%m%d_%H%M")}.csv' + output_file = f"{output_dir}/atr_ema_targets_{datetime.now().strftime('%Y%m%d_%H%M')}.csv" pd.DataFrame(bullish_signals).to_csv(output_file, index=False) print(f"\nšŸ“ Saved bullish signals to {output_file}") else: print("āŒ No bullish signals found.") except Exception as e: - print(f"āŒ Error during scan: {e}") + print(f"āŒ Error during scan: {e}") \ No newline at end of file