refactor: Modularize main application structure and separate concerns

This commit is contained in:
Bobby (aider) 2025-02-10 09:23:51 -08:00
parent 590c6fd2b7
commit 3425539781
5 changed files with 122 additions and 31 deletions

View File

@ -2,38 +2,13 @@ import warnings
from urllib3.exceptions import NotOpenSSLWarning from urllib3.exceptions import NotOpenSSLWarning
warnings.filterwarnings('ignore', category=NotOpenSSLWarning) warnings.filterwarnings('ignore', category=NotOpenSSLWarning)
from trading.journal import (create_trades_table, TradeEntry, add_trade, from trading.menu import print_main_menu, print_technical_scanner_menu
update_trade_exit, get_open_trades, get_trade_history) from trading.journal import journal_menu
from screener.scanner_controller import run_technical_scanner
from screener.canslim_controller import run_canslim_screener
from trading.main import main as trading_main
import datetime def main():
from screener.data_fetcher import validate_date_range, fetch_financial_data, get_stocks_in_time_range
from screener.t_sunnyband import run_sunny_scanner
from screener.t_atr_ema import run_atr_ema_scanner
from screener.c_canslim import check_quarterly_earnings, check_return_on_equity, check_sales_growth
from screener.a_canslim import check_annual_eps_growth
from screener.l_canslim import check_industry_leadership
from screener.i_canslim import check_institutional_sponsorship
from screener.csv_appender import append_scores_to_csv
from screener.screeners import SCREENERS
from screener.user_input import get_user_screener_selection, get_interval_choice
from indicators.three_atr_ema import ThreeATREMAIndicator
def get_float_input(prompt: str) -> float:
while True:
try:
return float(input(prompt))
except ValueError:
print("Please enter a valid number")
def get_scanner_parameters():
"""Get user input for scanner parameters"""
min_price = get_float_input("Enter minimum stock price ($): ")
max_price = get_float_input("Enter maximum stock price ($): ")
min_volume = int(input("Enter minimum volume: "))
portfolio_size = get_float_input("Enter portfolio size ($) or 0 to skip position sizing: ")
return min_price, max_price, min_volume, portfolio_size
def journal_menu():
create_trades_table() # Ensure table exists create_trades_table() # Ensure table exists
while True: while True:

View File

@ -0,0 +1,71 @@
from screener.data_fetcher import validate_date_range, fetch_financial_data, get_stocks_in_time_range
from screener.user_input import get_user_screener_selection
from screener.c_canslim import check_quarterly_earnings, check_return_on_equity, check_sales_growth
from screener.a_canslim import check_annual_eps_growth
from screener.l_canslim import check_industry_leadership
from screener.i_canslim import check_institutional_sponsorship
from screener.csv_appender import append_scores_to_csv
def run_canslim_screener():
"""Run the CANSLIM screener"""
user_start_date = input("Enter start date (YYYY-MM-DD): ")
user_end_date = input("Enter end date (YYYY-MM-DD): ")
start_date, end_date = validate_date_range(user_start_date, user_end_date, required_quarters=4)
selected_screeners = get_user_screener_selection()
symbol_list = get_stocks_in_time_range(start_date, end_date)
if not symbol_list:
print("No stocks found within the given date range.")
return
print(f"Processing {len(symbol_list)} stocks within the given date range...\n")
for symbol in symbol_list:
data = fetch_financial_data(symbol, start_date, end_date)
process_symbol(symbol, data, selected_screeners)
print("✅ Scores saved in data/metrics/stock_scores.csv\n")
def process_symbol(symbol, data, selected_screeners):
"""Process individual symbol for CANSLIM screening"""
if not
print(f"⚠️ Warning: No data returned for {symbol}. Assigning default score.\n")
scores = {screener: 0.25 for category in selected_screeners
for screener in selected_screeners[category]}
else:
scores = calculate_scores(symbol, data, selected_screeners)
scores["Total_Score"] = sum(scores.values())
append_scores_to_csv(symbol, scores)
def calculate_scores(symbol, data, selected_screeners):
"""Calculate scores for each selected screener"""
scores = {}
for category, screeners in selected_screeners.items():
for screener, threshold in screeners.items():
scores[screener] = get_screener_score(
screener, data, symbol, threshold
)
return scores
def get_screener_score(screener, data, symbol, threshold):
"""Get score for specific screener"""
if screener == "EPS_Score":
score = check_quarterly_earnings(data.get("quarterly_eps", []))
elif screener == "Annual_EPS_Score":
score = check_annual_eps_growth(data.get("annual_eps", []))
elif screener == "Sales_Score":
score = check_sales_growth(data.get("sales_growth", []))
elif screener == "ROE_Score":
score = check_return_on_equity(data.get("roe", []))
elif screener == "L_Score":
score = check_industry_leadership(symbol)
print(f"🟢 {symbol} - L_Score: {score}")
elif screener == "I_Score":
score = check_institutional_sponsorship(symbol)
print(f"🏢 {symbol} - I_Score: {score}")
else:
score = 0
return score >= threshold if isinstance(threshold, (int, float)) else score

View File

@ -0,0 +1,25 @@
from screener.t_sunnyband import run_sunny_scanner
from screener.t_atr_ema import run_atr_ema_scanner
from screener.t_atr_ema_v2 import run_atr_ema_scanner_v2
from utils.data_utils import get_float_input
def get_scanner_parameters():
"""Get user input for scanner parameters"""
min_price = get_float_input("Enter minimum stock price ($): ")
max_price = get_float_input("Enter maximum stock price ($): ")
min_volume = int(input("Enter minimum volume: "))
portfolio_size = get_float_input("Enter portfolio size ($) or 0 to skip position sizing: ")
return min_price, max_price, min_volume, portfolio_size
def run_technical_scanner(scanner_choice: str):
"""Run the selected technical scanner"""
min_price, max_price, min_volume, portfolio_size = get_scanner_parameters()
if scanner_choice == "1":
run_sunny_scanner(min_price, max_price, min_volume, portfolio_size)
elif scanner_choice == "2":
run_atr_ema_scanner(min_price, max_price, min_volume, portfolio_size)
elif scanner_choice == "3":
run_atr_ema_scanner_v2(min_price, max_price, min_volume, portfolio_size)
else:
print("Invalid scanner choice.")

13
src/trading/menu.py Normal file
View File

@ -0,0 +1,13 @@
def print_main_menu():
print("\nStock Analysis System")
print("1. Run CANSLIM Screener")
print("2. Run Technical Scanners (SunnyBands/ATR-EMA)")
print("3. Launch Trading System")
print("4. Trading Journal")
print("5. Exit")
def print_technical_scanner_menu():
print("\nTechnical Scanner Options:")
print("1. SunnyBands Scanner")
print("2. Standard ATR-EMA Scanner")
print("3. Enhanced ATR-EMA v2 Scanner")

View File

@ -5,6 +5,13 @@ from db.db_connection import create_client
from screener.user_input import get_interval_choice, get_date_range from screener.user_input import get_interval_choice, get_date_range
from trading.position_calculator import PositionCalculator from trading.position_calculator import PositionCalculator
def get_float_input(prompt: str) -> float:
while True:
try:
return float(input(prompt))
except ValueError:
print("Please enter a valid number")
def validate_signal_date(signal_date: datetime) -> datetime: def validate_signal_date(signal_date: datetime) -> datetime:
""" """
Validate and adjust signal date if needed Validate and adjust signal date if needed