From f62cd472927065f8b7aba94653582b83d770de36 Mon Sep 17 00:00:00 2001 From: "Bobby Abellana (aider)" Date: Thu, 6 Feb 2025 18:56:38 -0800 Subject: [PATCH] feat: Add institutional sponsorship check for CANSLIM strategy --- src/screener/i_canslim.py | 88 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/screener/i_canslim.py diff --git a/src/screener/i_canslim.py b/src/screener/i_canslim.py new file mode 100644 index 0000000..942db56 --- /dev/null +++ b/src/screener/i_canslim.py @@ -0,0 +1,88 @@ +from db.db_connection import create_client + +def check_institutional_sponsorship(symbol): + """ + Determines if a stock has strong institutional sponsorship. + + Criteria: + - Should have at least several institutional sponsors + - Look for increasing institutional ownership + - Focus on higher-quality institutions + - Avoid stocks with too much institutional ownership (>80-90%) + + Args: + symbol (str): Stock ticker symbol. + + Returns: + float: 1 (Pass), 0 (Fail), 0.25 (Insufficient Data) + """ + client = create_client() + + # Get current institutional ownership data + query = """ + SELECT + COUNT(DISTINCT holder_name) as num_institutions, + SUM(percentage_held) as total_ownership, + MAX(date_reported) as latest_date + FROM stock_db.yf_institutional_holders + WHERE ticker = '{}' + GROUP BY ticker + """.format(symbol) + + result = client.query(query) + + if not result.result_rows: + return 0.25 # Insufficient data + + num_institutions, total_ownership, _ = result.result_rows[0] + + # Get ownership trend (comparing with 3 months ago) + trend_query = """ + WITH current AS ( + SELECT SUM(percentage_held) as current_ownership, + MAX(date_reported) as latest_date + FROM stock_db.yf_institutional_holders + WHERE ticker = '{}' + ), + previous AS ( + SELECT SUM(percentage_held) as previous_ownership + FROM stock_db.yf_institutional_holders + WHERE ticker = '{}' + AND date_reported <= (SELECT latest_date - INTERVAL 3 MONTH FROM current) + ) + SELECT current_ownership, previous_ownership + FROM current, previous + """.format(symbol, symbol) + + trend_result = client.query(trend_query) + + # Evaluate criteria + if num_institutions is None or total_ownership is None: + return 0.25 + + # Convert total_ownership to percentage (0-100 scale) + total_ownership = float(total_ownership) + + # Criteria thresholds + MIN_INSTITUTIONS = 10 + MIN_OWNERSHIP = 20 # percentage + MAX_OWNERSHIP = 85 # percentage + + # Basic institutional presence check + has_sufficient_institutions = num_institutions >= MIN_INSTITUTIONS + has_healthy_ownership = MIN_OWNERSHIP <= total_ownership <= MAX_OWNERSHIP + + # Check ownership trend + ownership_increasing = False + if trend_result.result_rows: + current_ownership, previous_ownership = trend_result.result_rows[0] + if current_ownership and previous_ownership: + ownership_increasing = current_ownership > previous_ownership + + # Final evaluation + if has_sufficient_institutions and has_healthy_ownership and ownership_increasing: + return 1 + elif not has_sufficient_institutions or total_ownership < MIN_OWNERSHIP: + return 0 + else: + return 0.25 # Borderline cases