Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -4,9 +4,9 @@ import yfinance as yf
|
|
4 |
from datetime import datetime, timedelta
|
5 |
import requests
|
6 |
from bs4 import BeautifulSoup
|
|
|
7 |
from pattern_finder import score_downward_trend, score_candle, calculate_risk_reward
|
8 |
import urllib3
|
9 |
-
from datetime import datetime, timedelta
|
10 |
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
11 |
|
12 |
|
@@ -26,7 +26,6 @@ def load_sp500_tickers():
|
|
26 |
return tickers
|
27 |
|
28 |
|
29 |
-
|
30 |
def load_data(ticker):
|
31 |
"""Load stock data using yfinance."""
|
32 |
end_date = datetime.today()
|
@@ -35,15 +34,16 @@ def load_data(ticker):
|
|
35 |
return data
|
36 |
|
37 |
|
38 |
-
|
39 |
def calculate_sma(data, window):
|
40 |
"""Calculate the Simple Moving Average (SMA) for a given window."""
|
41 |
return data['Close'].rolling(window=window).mean()
|
42 |
|
|
|
43 |
def calculate_ema(data, window):
|
44 |
"""Calculate the Exponential Moving Average (EMA) for a given window."""
|
45 |
return data['Close'].ewm(span=window, adjust=False).mean()
|
46 |
|
|
|
47 |
def average_downtrend(data, method, window=4):
|
48 |
"""Calculate the average difference between consecutive prices for the last 'window' candles."""
|
49 |
if len(data) < window:
|
@@ -55,42 +55,11 @@ def average_downtrend(data, method, window=4):
|
|
55 |
|
56 |
def score_candle(candle, prev_candle, trend_strength):
|
57 |
"""Score a single candle based on its characteristics and previous candle."""
|
58 |
-
|
59 |
-
|
60 |
-
low_price = candle['Low']
|
61 |
-
high_price = candle['High']
|
62 |
-
prev_close = prev_candle['Close']
|
63 |
-
|
64 |
-
# Bottom and top wick lengths
|
65 |
-
bottom_wick_length = min(open_price, close_price) - low_price
|
66 |
-
top_wick_length = high_price - max(open_price, close_price)
|
67 |
-
|
68 |
-
# Initial score based on trend strength
|
69 |
-
score = trend_strength * 2
|
70 |
-
|
71 |
-
# Doji: Open and Close are almost the same (small body)
|
72 |
-
if abs(open_price - close_price) <= 0.1 * (high_price - low_price): # Adjust tolerance if needed
|
73 |
-
score += 5 # Bonus points for doji candles
|
74 |
-
|
75 |
-
# Hammer: Small body at the top, long bottom wick (typical reversal candle)
|
76 |
-
if close_price < open_price and bottom_wick_length > 2 * (open_price - close_price):
|
77 |
-
score += 7 # Extra points for hammer-like candles
|
78 |
-
|
79 |
-
# Bottom Tailing Wick: Long bottom wick compared to the overall range
|
80 |
-
if bottom_wick_length > 0.5 * (high_price - low_price):
|
81 |
-
score += 6 # Extra points for bottom tailing wick
|
82 |
-
|
83 |
-
# Additional logic: Boost red candles with long bottom wicks following a downtrend
|
84 |
-
if close_price < open_price and bottom_wick_length > 0.5 * (open_price - close_price):
|
85 |
-
score += 3 # Boost for red candle with long bottom wick
|
86 |
-
|
87 |
-
# Penalize if the current close is higher than the previous close
|
88 |
-
if close_price > prev_close:
|
89 |
-
score -= ((close_price - prev_close) / prev_close) * 100
|
90 |
-
|
91 |
-
|
92 |
return score
|
93 |
|
|
|
94 |
def score_today_candle(data, window=4):
|
95 |
"""Score today's candle based on the downtrend from the past 'window' days."""
|
96 |
if len(data) < window + 1:
|
@@ -100,53 +69,51 @@ def score_today_candle(data, window=4):
|
|
100 |
prev_candle = data.iloc[-2]
|
101 |
|
102 |
close_price = today_candle['Close']
|
103 |
-
|
104 |
-
|
105 |
previous_data = data.iloc[-(window+1):-1]
|
106 |
-
down_High = average_downtrend(previous_data, method="High",window=window) + average_downtrend(previous_data, method="High",window=7) / 2
|
107 |
-
down_Close = average_downtrend(previous_data, method="Close",window=window) + average_downtrend(previous_data, method="Close",window=7) / 2
|
108 |
-
|
109 |
-
|
110 |
avg_downtrend = (down_High + down_Close) / 2
|
111 |
|
112 |
if avg_downtrend == 0.0:
|
113 |
return -1
|
114 |
|
115 |
-
# Calculate SMAs for the last row
|
116 |
sma_50 = calculate_sma(data, window=50).iloc[-1]
|
117 |
sma_200 = calculate_sma(data, window=200).iloc[-1]
|
118 |
sma_20 = calculate_sma(data, window=20).iloc[-1]
|
119 |
-
|
120 |
ema_10 = calculate_ema(data, window=10).iloc[-1]
|
121 |
|
122 |
if (close_price < ema_10) or (close_price < sma_20) or (close_price < sma_50) or (close_price < sma_200):
|
123 |
return -1
|
124 |
|
125 |
-
|
126 |
return score_candle(today_candle, prev_candle, abs(avg_downtrend))
|
127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
def scan_sp500(top_n=25, progress=gr.Progress()):
|
129 |
tickers = load_sp500_tickers()
|
130 |
-
scores = []
|
131 |
tickers.append("QQQ")
|
132 |
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
|
|
|
|
139 |
|
140 |
scores = sorted(scores, key=lambda x: x[1], reverse=True)
|
141 |
return scores[:top_n]
|
142 |
|
143 |
-
def next_business_day(date):
|
144 |
-
next_day = date + timedelta(days=1)
|
145 |
-
while next_day.weekday() >= 5: # 5 = Saturday, 6 = Sunday
|
146 |
-
next_day += timedelta(days=1)
|
147 |
-
return next_day
|
148 |
-
|
149 |
-
|
150 |
|
151 |
def gradio_scan_sp500(top_n, progress=gr.Progress()):
|
152 |
progress(0, desc="Downloading Data")
|
@@ -156,8 +123,7 @@ def gradio_scan_sp500(top_n, progress=gr.Progress()):
|
|
156 |
progress(0.3, desc="Running Scanner")
|
157 |
results = scan_sp500(top_n, progress)
|
158 |
|
159 |
-
|
160 |
-
last_data = load_data(results[0][0]) # Load data for the first ticker in results
|
161 |
last_date = last_data.index[-1].date()
|
162 |
next_market_day = next_business_day(last_date)
|
163 |
date_created = next_market_day.strftime("%Y-%m-%d")
|
|
|
4 |
from datetime import datetime, timedelta
|
5 |
import requests
|
6 |
from bs4 import BeautifulSoup
|
7 |
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
8 |
from pattern_finder import score_downward_trend, score_candle, calculate_risk_reward
|
9 |
import urllib3
|
|
|
10 |
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
11 |
|
12 |
|
|
|
26 |
return tickers
|
27 |
|
28 |
|
|
|
29 |
def load_data(ticker):
|
30 |
"""Load stock data using yfinance."""
|
31 |
end_date = datetime.today()
|
|
|
34 |
return data
|
35 |
|
36 |
|
|
|
37 |
def calculate_sma(data, window):
|
38 |
"""Calculate the Simple Moving Average (SMA) for a given window."""
|
39 |
return data['Close'].rolling(window=window).mean()
|
40 |
|
41 |
+
|
42 |
def calculate_ema(data, window):
|
43 |
"""Calculate the Exponential Moving Average (EMA) for a given window."""
|
44 |
return data['Close'].ewm(span=window, adjust=False).mean()
|
45 |
|
46 |
+
|
47 |
def average_downtrend(data, method, window=4):
|
48 |
"""Calculate the average difference between consecutive prices for the last 'window' candles."""
|
49 |
if len(data) < window:
|
|
|
55 |
|
56 |
def score_candle(candle, prev_candle, trend_strength):
|
57 |
"""Score a single candle based on its characteristics and previous candle."""
|
58 |
+
# [Keep the existing scoring logic]
|
59 |
+
# ...
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
return score
|
61 |
|
62 |
+
|
63 |
def score_today_candle(data, window=4):
|
64 |
"""Score today's candle based on the downtrend from the past 'window' days."""
|
65 |
if len(data) < window + 1:
|
|
|
69 |
prev_candle = data.iloc[-2]
|
70 |
|
71 |
close_price = today_candle['Close']
|
|
|
|
|
72 |
previous_data = data.iloc[-(window+1):-1]
|
73 |
+
down_High = average_downtrend(previous_data, method="High", window=window) + average_downtrend(previous_data, method="High", window=7) / 2
|
74 |
+
down_Close = average_downtrend(previous_data, method="Close", window=window) + average_downtrend(previous_data, method="Close", window=7) / 2
|
|
|
|
|
75 |
avg_downtrend = (down_High + down_Close) / 2
|
76 |
|
77 |
if avg_downtrend == 0.0:
|
78 |
return -1
|
79 |
|
|
|
80 |
sma_50 = calculate_sma(data, window=50).iloc[-1]
|
81 |
sma_200 = calculate_sma(data, window=200).iloc[-1]
|
82 |
sma_20 = calculate_sma(data, window=20).iloc[-1]
|
|
|
83 |
ema_10 = calculate_ema(data, window=10).iloc[-1]
|
84 |
|
85 |
if (close_price < ema_10) or (close_price < sma_20) or (close_price < sma_50) or (close_price < sma_200):
|
86 |
return -1
|
87 |
|
|
|
88 |
return score_candle(today_candle, prev_candle, abs(avg_downtrend))
|
89 |
|
90 |
+
|
91 |
+
def scan_ticker(ticker):
|
92 |
+
"""Load data for a ticker and calculate its score."""
|
93 |
+
data = load_data(ticker)
|
94 |
+
if not data.empty:
|
95 |
+
score = score_today_candle(data)
|
96 |
+
if score > 0:
|
97 |
+
return ticker, score
|
98 |
+
return None
|
99 |
+
|
100 |
+
|
101 |
def scan_sp500(top_n=25, progress=gr.Progress()):
|
102 |
tickers = load_sp500_tickers()
|
|
|
103 |
tickers.append("QQQ")
|
104 |
|
105 |
+
scores = []
|
106 |
+
with ThreadPoolExecutor(max_workers=10) as executor:
|
107 |
+
futures = {executor.submit(scan_ticker, ticker): ticker for ticker in tickers}
|
108 |
+
for i, future in enumerate(as_completed(futures), 1):
|
109 |
+
result = future.result()
|
110 |
+
if result:
|
111 |
+
scores.append(result)
|
112 |
+
progress(i / len(tickers), desc="Processing Tickers")
|
113 |
|
114 |
scores = sorted(scores, key=lambda x: x[1], reverse=True)
|
115 |
return scores[:top_n]
|
116 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
|
118 |
def gradio_scan_sp500(top_n, progress=gr.Progress()):
|
119 |
progress(0, desc="Downloading Data")
|
|
|
123 |
progress(0.3, desc="Running Scanner")
|
124 |
results = scan_sp500(top_n, progress)
|
125 |
|
126 |
+
last_data = load_data(results[0][0])
|
|
|
127 |
last_date = last_data.index[-1].date()
|
128 |
next_market_day = next_business_day(last_date)
|
129 |
date_created = next_market_day.strftime("%Y-%m-%d")
|