kaganseyda commited on
Commit
19bb366
·
verified ·
1 Parent(s): 24309a6

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +336 -0
app.py ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import yfinance as yf
3
+ import numpy as np
4
+ import pandas as pd
5
+ import json
6
+ from scipy import signal
7
+ from scipy.fft import fft, fftfreq
8
+ from scipy.stats import norm, gaussian_kde
9
+ import warnings
10
+ from datetime import datetime, timedelta
11
+ from skopt import gp_minimize
12
+ from skopt.space import Real, Integer
13
+ from skopt.utils import use_named_args
14
+
15
+ warnings.filterwarnings('ignore')
16
+
17
+ # ==============================================================================
18
+ # 1. SİNYAL ÜRETİCİ (İŞÇİ)
19
+ # Sadece verilen parametrelere göre sinyal üreten saf DSP modülü.
20
+ # ==============================================================================
21
+ class SpectralStockAnalyzer:
22
+ def __init__(self, data, interval='1d'):
23
+ self.data = data
24
+ self.interval = interval
25
+ self.filtered_prices = None
26
+ self.momentum_signal = None
27
+ self.volatility = self.calculate_volatility()
28
+ self.adx = self.calculate_adx()
29
+
30
+ def calculate_volatility(self, window=20):
31
+ returns = self.data['Close'].pct_change()
32
+ annualization_factor = 52 if self.interval == '1wk' else 252
33
+ vol = returns.rolling(window=window).std() * np.sqrt(annualization_factor)
34
+ return vol.fillna(method='bfill').fillna(method='ffill')
35
+
36
+ def calculate_adx(self, lookback=14):
37
+ high, low, close = self.data['High'], self.data['Low'], self.data['Close']
38
+ tr = pd.concat([high - low, abs(high - close.shift()), abs(low - close.shift())], axis=1).max(axis=1)
39
+ up_move, down_move = high - high.shift(), low.shift() - low
40
+ plus_dm = pd.Series(np.where((up_move > down_move) & (up_move > 0), up_move, 0), index=self.data.index)
41
+ minus_dm = pd.Series(np.where((down_move > up_move) & (down_move > 0), down_move, 0), index=self.data.index)
42
+
43
+ atr = tr.rolling(window=lookback).mean()
44
+ plus_di = 100 * (plus_dm.ewm(alpha=1/lookback, min_periods=lookback).mean() / atr)
45
+ minus_di = 100 * (minus_dm.ewm(alpha=1/lookback, min_periods=lookback).mean() / atr)
46
+
47
+ dx = 100 * (abs(plus_di - minus_di) / (plus_di + minus_di).replace(0, 1))
48
+ adx = dx.ewm(alpha=1/lookback, min_periods=lookback).mean()
49
+ return adx.fillna(20)
50
+
51
+ def process_signal_chain(self, cutoff_freq, filter_order, momentum_thresh, price_thresh):
52
+ # 1. Filtreleme
53
+ prices = self.data['Close'].values
54
+ nyquist = 0.5
55
+ normalized_cutoff = max(0.01, min(cutoff_freq / nyquist, 0.99))
56
+ b, a = signal.butter(int(filter_order), normalized_cutoff, btype='low')
57
+ self.filtered_prices = signal.filtfilt(b, a, prices)
58
+
59
+ # 2. Momentum Sinyali
60
+ window_size=30
61
+ momentum_values = []
62
+ safe_window_size = min(window_size, len(self.filtered_prices) - 1)
63
+ for i in range(safe_window_size, len(self.filtered_prices)):
64
+ window_data = self.filtered_prices[i - safe_window_size:i]
65
+ window_spectrum = fft(window_data)
66
+ magnitude = np.abs(window_spectrum)
67
+ dominant_idx = np.argmax(magnitude[1:]) + 1 if len(magnitude) > 1 else 0
68
+ phase = np.angle(window_spectrum[dominant_idx])
69
+ momentum = np.cos(phase)
70
+ momentum_values.append(momentum)
71
+ self.momentum_signal = np.concatenate([np.zeros(safe_window_size), np.array(momentum_values)])
72
+
73
+ # 3. Alım Sinyallerini Tespit Et
74
+ signals = []
75
+ for i in range(5, len(self.momentum_signal)):
76
+ if i >= len(self.filtered_prices) or i >= len(self.data): continue
77
+
78
+ momentum_condition = (self.momentum_signal[i - 1] < momentum_thresh and
79
+ self.momentum_signal[i] > self.momentum_signal[i - 1])
80
+ recent_change = (self.filtered_prices[i] - self.filtered_prices[i - 5]) / self.filtered_prices[i - 5]
81
+ price_condition = recent_change < price_thresh
82
+
83
+ if momentum_condition and price_condition:
84
+ signal_date = self.data.index[i]
85
+ signals.append({
86
+ 'date': signal_date,
87
+ 'price': self.data['Close'].loc[signal_date],
88
+ 'context': {
89
+ 'volatility': self.volatility.loc[signal_date],
90
+ 'adx': self.adx.loc[signal_date],
91
+ 'momentum': self.momentum_signal[i]
92
+ }
93
+ })
94
+ return signals
95
+
96
+ # ==============================================================================
97
+ # 2. ÖĞRENME MOTORU (ÖĞRETMEN)
98
+ # Sinyalleri doğrular, başarılı olanların paternini öğrenir.
99
+ # ==============================================================================
100
+ class SignalFeedbackEngine:
101
+ def __init__(self, full_data, interval='1d'):
102
+ self.full_data = full_data
103
+ self.interval = interval
104
+ self.param_space = [
105
+ Real(0.01, 0.25, name='cutoff_freq'), Integer(3, 6, name='filter_order'),
106
+ Real(-0.9, -0.1, name='momentum_thresh'), Real(-0.15, -0.02, name='price_thresh')]
107
+ self.successful_signal_profile = None
108
+
109
+ def validate_signals(self, signals, forward_periods=20, success_threshold=0.01):
110
+ validated = []
111
+ for signal in signals:
112
+ future_data = self.full_data.loc[signal['date']:]
113
+ if len(future_data) > forward_periods:
114
+ end_price = future_data['Close'].iloc[forward_periods]
115
+ ret = (end_price - signal['price']) / signal['price']
116
+ signal['outcome'] = {
117
+ 'return': ret,
118
+ 'is_success': ret > success_threshold
119
+ }
120
+ validated.append(signal)
121
+ return validated
122
+
123
+ def objective_function(self):
124
+ @use_named_args(self.param_space)
125
+ def evaluate_params(**params):
126
+ analyzer = SpectralStockAnalyzer(self.full_data, self.interval)
127
+ signals = analyzer.process_signal_chain(**params)
128
+
129
+ if len(signals) < 5: return 10 # Çok az sinyal varsa cezalandır
130
+
131
+ validated_signals = self.validate_signals(signals)
132
+ if not validated_signals: return 10
133
+
134
+ successes = [s for s in validated_signals if s['outcome']['is_success']]
135
+
136
+ success_rate = len(successes) / len(validated_signals)
137
+ if success_rate < 0.5: return 5 # Başarı oranı düşükse cezalandır
138
+
139
+ # Başarılı sinyallerin ortalama getirisini ve riskini hesaba katan bir skor
140
+ avg_success_return = np.mean([s['outcome']['return'] for s in successes]) if successes else 0
141
+
142
+ # Kalite Skoru: Başarı oranı * Ortalama Getiri
143
+ quality_score = success_rate * avg_success_return
144
+
145
+ return -quality_score # Optimizasyon minimize ettiği için negatifini alıyoruz
146
+
147
+ return evaluate_params
148
+
149
+ def find_optimal_parameters(self):
150
+ print("Finding optimal parameters based on historical signal quality...")
151
+ objective = self.objective_function()
152
+ result = gp_minimize(func=objective, dimensions=self.param_space, n_calls=40, random_state=42, n_jobs=-1)
153
+
154
+ best_params = {dim.name: val for dim, val in zip(self.param_space, result.x)}
155
+ print(f"Optimal parameters found with Quality Score: {-result.fun:.4f}")
156
+ return best_params
157
+
158
+ def learn_successful_signal_profile(self, optimal_params):
159
+ print("Learning the DNA of successful signals...")
160
+ analyzer = SpectralStockAnalyzer(self.full_data, self.interval)
161
+ signals = analyzer.process_signal_chain(**optimal_params)
162
+ validated_signals = self.validate_signals(signals)
163
+
164
+ successes = [s for s in validated_signals if s['outcome']['is_success']]
165
+ if len(successes) < 5:
166
+ print("Not enough successful signals to build a reliable profile.")
167
+ self.successful_signal_profile = None
168
+ return
169
+
170
+ # Başarılı sinyallerin bağlam (context) verilerini topla
171
+ profile_data = {
172
+ 'volatility': [s['context']['volatility'] for s in successes],
173
+ 'adx': [s['context']['adx'] for s in successes]
174
+ }
175
+
176
+ # Olasılık yoğunluk fonksiyonları ile paterni öğren
177
+ self.successful_signal_profile = {
178
+ 'volatility_kde': gaussian_kde(profile_data['volatility']),
179
+ 'adx_kde': gaussian_kde(profile_data['adx']),
180
+ 'volatility_range': (np.min(profile_data['volatility']), np.max(profile_data['volatility'])),
181
+ 'adx_range': (np.min(profile_data['adx']), np.max(profile_data['adx']))
182
+ }
183
+ print("Successful signal profile created.")
184
+
185
+ def get_confidence_score(self, current_context):
186
+ if not self.successful_signal_profile: return 0.5 # Profil yoksa nötr skor
187
+
188
+ # Mevcut durum, başarılı paternin ne kadar "içinde"?
189
+ # Yoğunluk fonksiyonundan olasılık alıyoruz
190
+ vol_score = self.successful_signal_profile['volatility_kde'].evaluate([current_context['volatility']])[0]
191
+ adx_score = self.successful_signal_profile['adx_kde'].evaluate([current_context['adx']])[0]
192
+
193
+ # Skorları normalize etmek için maksimum yoğunluk değerlerini kullanabiliriz
194
+ # Basitlik için, şimdilik aralık kontrolü yapalım
195
+ norm_vol_score = np.interp(vol_score, [0, self.successful_signal_profile['volatility_kde'].pdf(self.successful_signal_profile['volatility_range']).max()], [0, 1])
196
+ norm_adx_score = np.interp(adx_score, [0, self.successful_signal_profile['adx_kde'].pdf(self.successful_signal_profile['adx_range']).max()], [0, 1])
197
+
198
+ # İki skorun ortalamasını alarak nihai güven skorunu oluştur
199
+ confidence = (norm_vol_score + norm_adx_score) / 2
200
+ return confidence
201
+
202
+ # ==============================================================================
203
+ # 3. ANA UYGULAMA VE ARAYÜZ
204
+ # ==============================================================================
205
+ def run_intelligent_analysis(stock_symbol, analysis_period, interval_choice):
206
+ if not stock_symbol: return "Please enter a stock symbol!", "...", None
207
+
208
+ interval_map = {"Günlük (Daily)": "1d", "Haftalık (Weekly)": "1wk"}
209
+ interval = interval_map[interval_choice]
210
+
211
+ try:
212
+ ticker = yf.Ticker(stock_symbol.strip().upper())
213
+ full_data = ticker.history(period=analysis_period, interval=interval)
214
+ if len(full_data) < 150: return f"Insufficient data ({len(full_data)} points).", "...", None
215
+
216
+ status_msg = f"Data fetched: {len(full_data)} points ({interval_choice})\n"
217
+
218
+ # 1. Öğrenme
219
+ engine = SignalFeedbackEngine(full_data, interval)
220
+ optimal_params = engine.find_optimal_parameters()
221
+ engine.learn_successful_signal_profile(optimal_params)
222
+ status_msg += "Learning from historical signals complete.\n"
223
+
224
+ # 2. Canlı Analiz
225
+ analyzer = SpectralStockAnalyzer(full_data, interval)
226
+ all_signals = analyzer.process_signal_chain(**optimal_params)
227
+
228
+ current_signal = None
229
+ # Son 5 gün içinde sinyal var mı diye kontrol et
230
+ if all_signals and (full_data.index[-1] - all_signals[-1]['date']).days < 5:
231
+ current_signal = all_signals[-1]
232
+ status_msg += "Potential signal detected in the current period.\n"
233
+ else:
234
+ status_msg += "No active signal in the current period.\n"
235
+
236
+ # 3. Raporlama
237
+ # ... Rapor ve JSON oluşturma ...
238
+ results = f"## INTELLIGENT ANALYSIS REPORT: {stock_symbol.upper()}\n"
239
+ results += "### Step 1: Learning from Past Performance\n"
240
+ results += f"The model analyzed past data to find parameters that maximize **Signal Quality Score** (Success Rate × Avg. Return).\n"
241
+ results += "**Optimal Parameters Found:**\n"
242
+ for key, val in optimal_params.items():
243
+ results += f"- **{key.replace('_', ' ').title()}:** {val:.4f}\n"
244
+
245
+ if engine.successful_signal_profile:
246
+ vol_range = engine.successful_signal_profile['volatility_range']
247
+ adx_range = engine.successful_signal_profile['adx_range']
248
+ results += "\n**Learned Profile of a 'Good' Signal (DNA):**\n"
249
+ results += f"- **Optimal Volatility Range:** {vol_range[0]:.2f} - {vol_range[1]:.2f}\n"
250
+ results += f"- **Optimal ADX (Trend Strength) Range:** {adx_range[0]:.1f} - {adx_range[1]:.1f}\n"
251
+
252
+ results += "\n### Step 2: Current Market Analysis\n"
253
+ if current_signal:
254
+ confidence = engine.get_confidence_score(current_signal['context'])
255
+ current_signal['confidence_score'] = confidence
256
+
257
+ color = "green" if confidence > 0.7 else "orange" if confidence > 0.5 else "red"
258
+
259
+ results += f"**POTENTIAL BUY SIGNAL DETECTED** on {current_signal['date'].strftime('%Y-%m-%d')}\n"
260
+ results += f"> **Confidence Score:** <span style='color:{color}; font-weight:bold;'>{confidence:.1%}</span>\n"
261
+ results += f"> This score indicates how closely the current market conditions match the DNA of historical successful signals.\n\n"
262
+ results += "**Current Market Context:**\n"
263
+ results += f"- **Volatility:** {current_signal['context']['volatility']:.2f} (Historical sweet spot: {vol_range[0]:.2f}-{vol_range[1]:.2f})\n"
264
+ results += f"- **ADX:** {current_signal['context']['adx']:.1f} (Historical sweet spot: {adx_range[0]:.1f}-{adx_range[1]:.1f})\n"
265
+ else:
266
+ results += "**No active buy signal detected in the last 5 periods.** The model is waiting for market conditions to align with its learned success profile.\n"
267
+
268
+ # JSON oluşturma
269
+ json_output = {
270
+ "symbol": stock_symbol.upper(),
271
+ "analysis_timestamp": datetime.now().isoformat(),
272
+ "optimal_parameters": optimal_params,
273
+ "learned_profile": {
274
+ 'volatility_range': engine.successful_signal_profile['volatility_range'] if engine.successful_signal_profile else None,
275
+ 'adx_range': engine.successful_signal_profile['adx_range'] if engine.successful_signal_profile else None
276
+ },
277
+ "current_signal": current_signal,
278
+ "historical_signals_with_optimal_params": all_signals
279
+ }
280
+
281
+ # Datetime ve numpy türlerini serileştirilebilir hale getir
282
+ def json_converter(o):
283
+ if isinstance(o, (datetime, pd.Timestamp)): return o.isoformat()
284
+ if isinstance(o, (np.integer, np.int64)): return int(o)
285
+ if isinstance(o, (np.floating, np.float64)): return float(o)
286
+ if isinstance(o, np.ndarray): return o.tolist()
287
+ if np.isnan(o): return None
288
+ return o
289
+
290
+ return status_msg, results, json.dumps(json_output, default=json_converter, indent=2)
291
+
292
+ except Exception as e:
293
+ import traceback
294
+ return f"An error occurred: {e}\n{traceback.format_exc()}", "...", None
295
+
296
+
297
+ def create_interface():
298
+ with gr.Blocks(theme=gr.themes.Soft(), title="Intelligent Spectral Analysis") as demo:
299
+ gr.HTML("""<div style="text-align: center; background: linear-gradient(90deg, #1A2980 0%, #26D0CE 100%); color: white; padding: 25px; border-radius: 8px;">
300
+ <h1>Intelligent Spectral Analyzer</h1>
301
+ <p>Self-Learning Quantitative Model with Signal Feedback & Pattern Recognition</p>
302
+ </div>""")
303
+
304
+ with gr.Row():
305
+ with gr.Column(scale=1):
306
+ stock_input = gr.Textbox(label="Stock Symbol", placeholder="e.g., NVDA, GOOGL, TSLA")
307
+ period_input = gr.Dropdown(label="Analysis Period", choices=["1y", "2y", "5y", "10y"], value="5y")
308
+ interval_input = gr.Dropdown(label="Analysis Interval", choices=["Günlük (Daily)", "Haftalık (Weekly)"], value="Günlük (Daily)")
309
+ analyze_btn = gr.Button("START INTELLIGENT ANALYSIS", variant="primary")
310
+ status_output = gr.Textbox(label="Process Log", interactive=False, lines=15)
311
+
312
+ with gr.Column(scale=2):
313
+ results_output = gr.Markdown(value="Analysis report will appear here...")
314
+ with gr.Accordion("Show Raw JSON Output for Integration", open=False):
315
+ json_output_display = gr.JSON(label="JSON Data")
316
+
317
+ analyze_btn.click(
318
+ fn=run_intelligent_analysis,
319
+ inputs=[stock_input, period_input, interval_input],
320
+ outputs=[status_output, results_output, json_output_display]
321
+ )
322
+
323
+ gr.HTML("""<div style="margin-top: 20px; padding: 15px; background-color: #f2f2f2; border-radius: 8px;">
324
+ <h4>How It Works (Self-Learning Logic):</h4>
325
+ <ol>
326
+ <li><strong>Historical Simulation:</strong> The model runs its DSP strategy on past data with many different parameter sets.</li>
327
+ <li><strong>Signal Validation:</strong> It checks the outcome of every historical signal it generated. Signals that led to profit are marked 'Successful'.</li>
328
+ <li><strong>Pattern Recognition (Learning):</strong> It analyzes all 'Successful' signals to find their common characteristics (the "DNA"). What was the market volatility and trend strength like when they occurred?</li>
329
+ <li><strong>Intelligent Prediction:</strong> It uses the parameters that produced the best historical signals to analyze the current market. If a new signal appears, it's compared against the learned 'DNA' to generate a final <strong>Confidence Score</strong>.</li>
330
+ </ol>
331
+ </div>""")
332
+ return demo
333
+
334
+ if __name__ == "__main__":
335
+ demo = create_interface()
336
+ demo.launch(share=True)