Arkm20 commited on
Commit
d81c653
1 Parent(s): cdbaf0c

Main Commit

Browse files
Files changed (3) hide show
  1. app.py +122 -0
  2. pattern_finder.py +176 -0
  3. requirements.txt +7 -0
app.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ 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
+
13
+ def load_sp500_tickers():
14
+ """Load S&P 500 tickers from Wikipedia."""
15
+ url = "https://en.wikipedia.org/wiki/List_of_S%26P_500_companies"
16
+ response = requests.get(url, verify=False)
17
+ soup = BeautifulSoup(response.content, 'html.parser')
18
+ table = soup.find('table', {'id': 'constituents'})
19
+ tickers = []
20
+ if table:
21
+ for row in table.find_all('tr')[1:]:
22
+ cells = row.find_all('td')
23
+ if cells:
24
+ ticker = cells[0].text.strip()
25
+ tickers.append(ticker)
26
+ return tickers
27
+
28
+
29
+
30
+ def load_data(ticker):
31
+ """Load stock data using yfinance."""
32
+ end_date = datetime.today()
33
+ start_date = end_date - timedelta(days=365) # Get 1 year of data
34
+ data = yf.download(ticker, start=start_date, end=end_date)
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:
50
+ return 0.0
51
+ price_diffs = data[method].diff().iloc[-window:]
52
+ avg_diff = price_diffs.mean()
53
+ return avg_diff if avg_diff < 0 else 0.0
54
+
55
+
56
+ def score_today_candle(data, window=4):
57
+ if len(data) < window + 1:
58
+ return 0
59
+
60
+ trend_score = score_downward_trend(data.iloc[-window:], window=window)
61
+ candle_score = score_candle(data.iloc[-1], data, len(data) - 1)
62
+ risk_reward = calculate_risk_reward(data, len(data) - 1)
63
+
64
+ # Combine scores (you can adjust the weights as needed)
65
+ total_score = trend_score + candle_score + (risk_reward * 10)
66
+
67
+ return total_score
68
+
69
+ def scan_sp500(top_n=25, progress=gr.Progress()):
70
+ tickers = load_sp500_tickers()
71
+ scores = []
72
+ tickers.append("QQQ")
73
+
74
+ for i, ticker in enumerate(progress.tqdm(tickers)):
75
+ data = load_data(ticker)
76
+ if not data.empty:
77
+ score = score_today_candle(data)
78
+ if score > 0:
79
+ scores.append((ticker, score))
80
+
81
+ scores = sorted(scores, key=lambda x: x[1], reverse=True)
82
+ return scores[:top_n]
83
+
84
+ def next_business_day(date):
85
+ next_day = date + timedelta(days=1)
86
+ while next_day.weekday() >= 5: # 5 = Saturday, 6 = Sunday
87
+ next_day += timedelta(days=1)
88
+ return next_day
89
+
90
+
91
+
92
+ def gradio_scan_sp500(top_n, progress=gr.Progress()):
93
+ progress(0, desc="Downloading Data")
94
+ tickers = load_sp500_tickers()
95
+ tickers.append("QQQ")
96
+
97
+ progress(0.3, desc="Running Scanner")
98
+ results = scan_sp500(top_n, progress)
99
+
100
+ # Get the last date of the data and find the next business day
101
+ last_data = load_data(results[0][0]) # Load data for the first ticker in results
102
+ last_date = last_data.index[-1].date()
103
+ next_market_day = next_business_day(last_date)
104
+ date_created = next_market_day.strftime("%Y-%m-%d")
105
+
106
+ output = f"Scan Results for Market Open on: {date_created}\n\n"
107
+ output += "Top {} stocks based on pattern finder score:\n\n".format(top_n)
108
+ for ticker, score in results:
109
+ output += "{}: Total Score = {:.2f}\n".format(ticker, score)
110
+ return output
111
+
112
+ iface = gr.Interface(
113
+ fn=gradio_scan_sp500,
114
+ inputs=gr.Slider(minimum=1, maximum=100, step=1, label="Number of top stocks to display", value=25),
115
+ outputs="text",
116
+ title="S&P 500 Stock Scanner",
117
+ description="Scan S&P 500 stocks and display top N stocks based on today's candle score.",
118
+ allow_flagging="never",
119
+ )
120
+
121
+ if __name__ == "__main__":
122
+ iface.launch()
pattern_finder.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+
4
+ def load_data(ticker):
5
+ try:
6
+ # Load data from CSV
7
+ data = pd.read_csv(f'tickers/{ticker}.csv', index_col="Date", parse_dates=['Date'])
8
+ data.sort_index(inplace=True) # Ensure data is sorted by date
9
+ return data
10
+ except FileNotFoundError:
11
+ print(f"Data for {ticker} not found.")
12
+ return None
13
+
14
+ def sma(data, period):
15
+ return data['Close'].rolling(window=period).mean()
16
+
17
+ def ema(data, period):
18
+ return data['Close'].ewm(span=period, adjust=False).mean()
19
+
20
+ def score_downward_trend(data, window=4):
21
+ """
22
+ Score the downward trend based on price action and volume.
23
+ """
24
+ if len(data) < window:
25
+ return 0 # Not enough data
26
+
27
+ score = 0
28
+ for j in range(1, window):
29
+ if data['High'].iloc[j] < data['High'].iloc[j-1]:
30
+ score += 1 # Increment score for each lower high
31
+ if data['Close'].iloc[j] < data['Close'].iloc[j-1]:
32
+ score += 1 # Increment score for each lower close
33
+ if data['Volume'].iloc[j] > data['Volume'].iloc[j-1]:
34
+ score += 0.5 # Increment score for increasing volume during downtrend
35
+
36
+ return score
37
+
38
+ def score_candle(candle, data, i):
39
+ """
40
+ Score the candle based on its pattern, volume, and position relative to moving averages.
41
+ """
42
+ open_price, close_price, low_price, high_price = candle['Open'], candle['Close'], candle['Low'], candle['High']
43
+ prev_candle = data.iloc[i-1]
44
+
45
+ score = 0
46
+ body = abs(close_price - open_price)
47
+ bottom_wick_length = min(open_price, close_price) - low_price
48
+ top_wick_length = high_price - max(open_price, close_price)
49
+
50
+ # Add 16 points if candle is green and there's a significant gap
51
+ if close_price > open_price and low_price > prev_candle['High']:
52
+ score += 16
53
+
54
+ # Rest of the existing scoring logic
55
+ if bottom_wick_length > 2 * body:
56
+ score += 10
57
+ if abs(open_price - close_price) < (0.1 * (high_price - low_price)):
58
+ score += 15
59
+ if bottom_wick_length > top_wick_length:
60
+ score += 8
61
+
62
+ # Volume analysis
63
+ if candle['Volume'] > data['Volume'].rolling(window=20).mean().iloc[i]:
64
+ score += 5
65
+
66
+ # Moving average analysis
67
+ ema_20 = ema(data.iloc[:i+1], 20).iloc[-1]
68
+ sma_50 = sma(data.iloc[:i+1], 50).iloc[-1]
69
+ sma_200 = sma(data.iloc[:i+1], 200).iloc[-1]
70
+
71
+ if close_price > ema_20 and open_price < ema_20:
72
+ score += 5
73
+ if close_price > sma_50:
74
+ score += 3
75
+ if close_price > sma_200:
76
+ score += 2
77
+
78
+ # Momentum indicator
79
+ rsi = calculate_rsi(data.iloc[:i+1], period=14).iloc[-1]
80
+ if rsi < 30:
81
+ score += 5
82
+
83
+ penalty = 0
84
+ conditions_met = 0
85
+
86
+ if candle['High'] > prev_candle['High']:
87
+ conditions_met += 1
88
+ if candle['Low'] > prev_candle['High']:
89
+ conditions_met += 1
90
+ if candle['Close'] > max(prev_candle['Close'], prev_candle['Open']):
91
+ conditions_met += 1
92
+ if candle['Open'] > max(prev_candle['Open'], prev_candle['Close']):
93
+ conditions_met += 1
94
+
95
+ current_avg = (candle['Open'] + candle['Close'] + candle['High'] + candle['Low']) / 4
96
+ prev_avg = (prev_candle['Open'] + prev_candle['Close'] + prev_candle['High'] + prev_candle['Low']) / 4
97
+ if current_avg > prev_avg:
98
+ conditions_met += 1
99
+
100
+ if conditions_met == 3:
101
+ penalty = -10
102
+ elif conditions_met == 4:
103
+ penalty = -12
104
+ elif conditions_met == 5:
105
+ penalty = -17
106
+
107
+ score += penalty
108
+
109
+ return score
110
+
111
+ def calculate_rsi(data, period=14):
112
+ delta = data['Close'].diff()
113
+ gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
114
+ loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
115
+ rs = gain / loss
116
+ return 100 - (100 / (1 + rs))
117
+
118
+ def calculate_risk_reward(data, entry_index, stop_loss_percent=0.02, target_percent=0.06):
119
+ entry_price = data['Close'].iloc[entry_index]
120
+ stop_loss = entry_price * (1 - stop_loss_percent)
121
+ target = entry_price * (1 + target_percent)
122
+ risk = entry_price - stop_loss
123
+ reward = target - entry_price
124
+ return reward / risk
125
+
126
+ def find_reversal_patterns(data, window=4, candle_score_threshold=20, trend_score_threshold=5, risk_reward_threshold=2):
127
+ patterns = []
128
+
129
+ for i in range(window, len(data)):
130
+ trend_score = score_downward_trend(data.iloc[i-window:i], window=window)
131
+ if trend_score >= trend_score_threshold:
132
+ candle_score = score_candle(data.iloc[i], data, i)
133
+ if candle_score >= candle_score_threshold:
134
+ risk_reward = calculate_risk_reward(data, i)
135
+ if risk_reward >= risk_reward_threshold:
136
+ format_date = data.index[i].strftime('%Y-%m-%d')
137
+ patterns.append((format_date, trend_score, candle_score, risk_reward))
138
+
139
+ return patterns
140
+
141
+ def back_reversal_finder(data, window=4, candle_score_threshold=20, trend_score_threshold=4.5, risk_reward_threshold=1.5):
142
+ patterns = []
143
+
144
+ for i in range(window, len(data)):
145
+ trend_score = score_downward_trend(data.iloc[i-window:i], window=window)
146
+ if trend_score >= trend_score_threshold:
147
+ candle_score = score_candle(data.iloc[i], data, i)
148
+ if candle_score >= candle_score_threshold:
149
+ risk_reward = calculate_risk_reward(data, i)
150
+ if risk_reward >= risk_reward_threshold:
151
+ format_date = data.index[i].strftime('%Y-%m-%d')
152
+ patterns.append(format_date)
153
+
154
+ return patterns
155
+
156
+ def check_for_reversal_patterns(ticker, window=4, candle_score_threshold=20, trend_score_threshold=5, risk_reward_threshold=2):
157
+ data = load_data(ticker)
158
+ if data is None:
159
+ return
160
+
161
+ patterns = find_reversal_patterns(data, window=window, candle_score_threshold=candle_score_threshold,
162
+ trend_score_threshold=trend_score_threshold, risk_reward_threshold=risk_reward_threshold)
163
+
164
+ if patterns:
165
+ print(f"{ticker}: Potential reversal patterns found:")
166
+ for date, trend_score, candle_score, risk_reward in patterns:
167
+ print(f"Date: {date}, Trend Score: {trend_score:.2f}, Candle Score: {candle_score:.2f}, Risk-Reward: {risk_reward:.2f}")
168
+ else:
169
+ print(f"{ticker}: No clear reversal patterns detected.")
170
+
171
+ def main():
172
+ ticker = input("Enter Ticker: ").upper()
173
+ check_for_reversal_patterns(ticker)
174
+
175
+ if __name__ == '__main__':
176
+ main()
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ pandas
2
+ numpy
3
+ yfinance
4
+ gradio
5
+ requests
6
+ beautifulsoup4
7
+ urllib3