refactor: Modularize main application structure and separate concerns
This commit is contained in:
parent
590c6fd2b7
commit
3425539781
37
src/main.py
37
src/main.py
@ -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:
|
||||||
|
|||||||
71
src/screener/canslim_controller.py
Normal file
71
src/screener/canslim_controller.py
Normal 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
|
||||||
25
src/screener/scanner_controller.py
Normal file
25
src/screener/scanner_controller.py
Normal 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
13
src/trading/menu.py
Normal 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")
|
||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user