QuantResearch 2.1__tar.gz → 2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QuantResearch
3
- Version: 2.1
3
+ Version: 2.3
4
4
  Summary: Technical indicators and visualization tools for quantitative research
5
5
  Home-page: https://github.com/vinayak1729-web/QuantR
6
6
  Author: Vinayak ShindeVishal Mishra
@@ -9,7 +9,7 @@ License: MIT
9
9
  Project-URL: Homepage, https://github.com/vinayak1729-web/QuantR
10
10
  Project-URL: Repository, https://github.com/vinayak1729-web/QuantR
11
11
  Project-URL: Issues, https://github.com/vinayak1729-web/QuantR/issues
12
- Project-URL: Documentation, https://github.com/vinayak1729-web/QuantR#readme
12
+ Project-URL: Documentation, https://vinayak1729-web.github.io/QuantR/
13
13
  Keywords: finance,trading,technical-analysis,indicators,quantitative,stocks,yfinance
14
14
  Classifier: Development Status :: 3 - Alpha
15
15
  Classifier: Intended Audience :: Developers
@@ -0,0 +1,2 @@
1
+ from .indicators import *
2
+ from .visualize import *
@@ -1,4 +1,3 @@
1
-
2
1
  import tkinter as tk
3
2
  from tkinter import ttk, messagebox
4
3
  from tkcalendar import DateEntry
@@ -9,71 +8,19 @@ import pandas as pd
9
8
  from datetime import datetime, timedelta
10
9
  import yfinance as yf
11
10
  import numpy as np
12
-
13
- # Core indicator functions
14
- def fetch_data(ticker, start_date, end_date):
15
- data = yf.download(tickers=ticker, start=start_date, end=end_date, auto_adjust=False)
16
- # Flatten multi-level columns if they exist
17
- if isinstance(data.columns, pd.MultiIndex):
18
- data.columns = data.columns.get_level_values(0)
19
- return data
20
-
21
- def Rsi(price, period=14):
22
- delta = price.diff()
23
- gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
24
- loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
25
- return (100 - (100 / (1 + (gain / loss))))
26
-
27
- def bb_bands(price, period=20, num_std=2):
28
- rolling_mean = price.rolling(window=period).mean()
29
- rolling_std = price.rolling(window=period).std()
30
- upper_band = rolling_mean + (rolling_std * num_std)
31
- lower_band = rolling_mean - (rolling_std * num_std)
32
- return upper_band, rolling_mean, lower_band
33
-
34
- def macd(price, short_period=12, long_period=26, signal_period=9):
35
- shortLine = price.ewm(span=short_period, adjust=False).mean()
36
- longLine = price.ewm(span=long_period, adjust=False).mean()
37
- macd_line = shortLine - longLine
38
- signalLine = macd_line.ewm(span=signal_period, adjust=False).mean()
39
- histogram = macd_line - signalLine
40
- return macd_line, signalLine, histogram
41
-
42
- def atr(data, period=14):
43
- high = data['High']
44
- low = data['Low']
45
- close = data['Close']
46
- tr1 = high - low
47
- tr2 = abs(high - close.shift())
48
- tr3 = abs(low - close.shift())
49
- tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
50
- return (tr.rolling(window=period).mean())
51
-
52
- def sma(price, period=9):
53
- return price.rolling(window=period).mean()
54
-
55
- def ema(price, period=9):
56
- return price.ewm(span=period, adjust=False).mean()
57
-
58
- def demma(price, period=9):
59
- ema_val = price.ewm(span=period, adjust=False).mean()
60
- Eema1 = ema_val.ewm(span=period, adjust=False).mean()
61
- demma_val = 2 * (ema_val) - (Eema1)
62
- return demma_val
63
-
64
- def temma(price, period=9):
65
- ema_val = price.ewm(span=period, adjust=False).mean()
66
- Eema1 = ema_val.ewm(span=period, adjust=False).mean()
67
- Eema2 = Eema1.ewm(span=period, adjust=False).mean()
68
- temma_val = 3 * ema_val - (3 * Eema1) + Eema2
69
- return temma_val
70
-
71
- def RVWAP(high, low, close, volume, period=20):
72
- tp = (high + low + close) / 3
73
- tpv = tp * volume
74
- rolling_tpv = tpv.rolling(window=period).sum()
75
- rolling_volume = volume.rolling(window=period).sum()
76
- return rolling_tpv / rolling_volume
11
+ from .indicators import Rsi ,RVWAP , macd , bb_bands , atr ,fetch_data ,sma , temma , demma , ema
12
+ # ============================================================================
13
+ # PERIOD SETTINGS FOR DIFFERENT TIME FRAMES
14
+ # ============================================================================
15
+ PERIOD_SETTINGS = {
16
+ "1M": {"rsi": 14, "bb": 20, "macd": (12, 26, 9), "atr": 14, "ma": 9, "rvwap": 20},
17
+ "3M": {"rsi": 21, "bb": 25, "macd": (12, 26, 9), "atr": 21, "ma": 20, "rvwap": 20},
18
+ "6M": {"rsi": 30, "bb": 30, "macd": (12, 26, 9), "atr": 30, "ma": 30, "rvwap": 30},
19
+ "9M": {"rsi": 40, "bb": 30, "macd": (12, 26, 9), "atr": 30, "ma": 30, "rvwap": 30},
20
+ "1Y": {"rsi": 40, "bb": 30, "macd": (12, 26, 9), "atr": 40, "ma": 50, "rvwap": 30},
21
+ "3Y": {"rsi": 60, "bb": 50, "macd": (26, 52, 18), "atr": 60, "ma": 100, "rvwap": 50},
22
+ "5Y": {"rsi": 70, "bb": 50, "macd": (26, 52, 18), "atr": 70, "ma": 200, "rvwap": 70},
23
+ }
77
24
 
78
25
 
79
26
 
@@ -81,28 +28,29 @@ class QuantDashboard:
81
28
  def __init__(self, root):
82
29
  self.root = root
83
30
  self.root.title("QuantResearch Dashboard")
84
-
31
+
85
32
  # Get screen dimensions
86
33
  screen_width = self.root.winfo_screenwidth()
87
34
  screen_height = self.root.winfo_screenheight()
88
-
35
+
89
36
  # Set window size to 90% of screen
90
37
  window_width = int(screen_width * 0.9)
91
38
  window_height = int(screen_height * 0.85)
92
-
39
+
93
40
  # Center the window
94
41
  x = (screen_width - window_width) // 2
95
42
  y = (screen_height - window_height) // 2
96
-
97
43
  self.root.geometry(f"{window_width}x{window_height}+{x}+{y}")
98
44
  self.root.configure(bg='#1e1e1e')
99
-
45
+
100
46
  # Data storage
101
47
  self.data = None
102
48
  self.current_ticker = None
103
49
  self.current_start = None
104
50
  self.current_end = None
105
-
51
+ self.current_timeframe = "1M" # Track selected timeframe
52
+ self.period_config = PERIOD_SETTINGS["1M"] # Store current period config
53
+
106
54
  # Indicator states
107
55
  self.ma_states = {
108
56
  'SMA': tk.BooleanVar(value=False),
@@ -110,40 +58,40 @@ class QuantDashboard:
110
58
  'DEMA': tk.BooleanVar(value=False),
111
59
  'TEMA': tk.BooleanVar(value=False)
112
60
  }
113
-
61
+
114
62
  self.indicator_states = {
115
63
  'MACD': tk.BooleanVar(value=False),
116
64
  'ATR': tk.BooleanVar(value=False),
117
- 'RSI': tk.BooleanVar(value=False)
65
+ 'RSI': tk.BooleanVar(value=False),
66
+ 'Bollinger Bands': tk.BooleanVar(value=False) # Add Bollinger Bands
118
67
  }
119
-
68
+
120
69
  self.setup_ui()
121
-
70
+
122
71
  def setup_ui(self):
123
72
  # Main container
124
73
  main_container = tk.Frame(self.root, bg='#1e1e1e')
125
74
  main_container.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
126
-
75
+
127
76
  # Configure grid weights
128
77
  main_container.grid_rowconfigure(0, weight=1)
129
78
  main_container.grid_columnconfigure(0, weight=4) # 80% for charts
130
79
  main_container.grid_columnconfigure(1, weight=1) # 20% for control panel
131
-
80
+
132
81
  # Left side - Charts area (75% width)
133
82
  left_container = tk.Frame(main_container, bg='#1e1e1e')
134
83
  left_container.grid(row=0, column=0, sticky='nsew', padx=(0, 5))
135
-
84
+
136
85
  # Time range selector at the top
137
86
  self.time_frame = tk.Frame(left_container, bg='#2d2d2d', height=50, relief=tk.RAISED, borderwidth=2)
138
87
  self.time_frame.pack(fill=tk.X, pady=(0, 5))
139
88
  self.time_frame.pack_propagate(False)
140
89
  self.setup_time_selector()
141
-
90
+
142
91
  # Chart display area
143
92
  self.chart_container = tk.Frame(left_container, bg='#2d2d2d', relief=tk.RAISED, borderwidth=2)
144
93
  self.chart_container.pack(fill=tk.BOTH, expand=True)
145
94
 
146
- # --- ADD THIS BLOCK ---
147
95
  self.welcome_label = tk.Label(
148
96
  self.chart_container,
149
97
  text="Welcome to QuantWindow",
@@ -152,143 +100,143 @@ class QuantDashboard:
152
100
  fg="#eaff00"
153
101
  )
154
102
  self.welcome_label.pack(expand=True)
155
-
103
+
156
104
  # Right side - PERMANENT Control Panel (25% width)
157
105
  self.control_frame = tk.Frame(main_container, bg='#2d2d2d', relief=tk.RAISED, borderwidth=2)
158
106
  self.control_frame.grid(row=0, column=1, sticky='nsew')
159
-
160
107
  self.setup_control_panel()
161
-
108
+
162
109
  def setup_time_selector(self):
163
110
  tk.Label(self.time_frame, text="Time Range:", font=('Arial', 9, 'bold'),
164
111
  bg='#2d2d2d', fg='#ffffff').pack(side=tk.LEFT, padx=(10, 8))
165
-
166
- periods = [('5Y', 1825), ('3Y', 1095), ('1Y', 365), ('9M', 270),
167
- ('6M', 180), ('3M', 90), ('1M', 30)]
168
-
169
- for label, days in periods:
112
+
113
+ periods = [('5Y', 1825, '5Y'), ('3Y', 1095, '3Y'), ('1Y', 365, '1Y'),
114
+ ('9M', 270, '9M'), ('6M', 180, '6M'), ('3M', 90, '3M'), ('1M', 30, '1M')]
115
+
116
+ for label, days, timeframe_key in periods:
170
117
  btn = tk.Button(self.time_frame, text=label, width=5,
171
- command=lambda d=days: self.change_time_range(d),
172
- bg='#3d3d3d', fg='#ffffff', font=('Arial', 8, 'bold'),
173
- relief=tk.RAISED, borderwidth=2, cursor='hand2',
174
- activebackground='#00ff88', activeforeground='#1e1e1e')
118
+ command=lambda d=days, tf=timeframe_key: self.change_time_range(d, tf),
119
+ bg='#3d3d3d', fg='#ffffff', font=('Arial', 8, 'bold'),
120
+ relief=tk.RAISED, borderwidth=2, cursor='hand2',
121
+ activebackground='#00ff88', activeforeground='#1e1e1e')
175
122
  btn.pack(side=tk.LEFT, padx=2)
176
-
123
+
177
124
  def setup_control_panel(self):
178
125
  # Main frame with fixed structure
179
126
  control_main = tk.Frame(self.control_frame, bg='#2d2d2d')
180
127
  control_main.pack(fill=tk.BOTH, expand=True)
181
-
128
+
182
129
  # Title (fixed at top)
183
130
  title_label = tk.Label(control_main, text="Control Panel", font=('Arial', 14, 'bold'),
184
- bg='#2d2d2d', fg='#00ff88')
131
+ bg='#2d2d2d', fg='#00ff88')
185
132
  title_label.pack(pady=(10, 15), anchor='n')
186
-
133
+
187
134
  # Scrollable content area
188
135
  canvas = tk.Canvas(control_main, bg='#2d2d2d', highlightthickness=0)
189
136
  scrollbar = tk.Scrollbar(control_main, orient="vertical", command=canvas.yview)
190
137
  scrollable_frame = tk.Frame(canvas, bg='#2d2d2d')
191
-
138
+
192
139
  def on_frame_configure(event=None):
193
140
  canvas.configure(scrollregion=canvas.bbox("all"))
194
-
141
+
195
142
  def on_canvas_configure(event):
196
- # Update the scrollable frame width to match canvas width
197
143
  canvas.itemconfig(canvas_window, width=event.width)
198
-
144
+
199
145
  scrollable_frame.bind("<Configure>", on_frame_configure)
200
146
  canvas.bind("<Configure>", on_canvas_configure)
201
-
147
+
202
148
  canvas_window = canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
203
149
  canvas.configure(yscrollcommand=scrollbar.set)
204
-
150
+
205
151
  # Mouse wheel scrolling
206
152
  def _on_mousewheel(event):
207
153
  canvas.yview_scroll(int(-1*(event.delta/120)), "units")
208
-
154
+
209
155
  canvas.bind_all("<MouseWheel>", _on_mousewheel)
210
-
156
+
211
157
  # Ticker input
212
158
  ticker_frame = tk.Frame(scrollable_frame, bg='#2d2d2d')
213
159
  ticker_frame.pack(pady=8, padx=15, fill=tk.X)
214
-
160
+
215
161
  tk.Label(ticker_frame, text="Company Ticker:", font=('Arial', 9, 'bold'),
216
162
  bg='#2d2d2d', fg='#ffffff').pack(anchor=tk.W)
217
-
163
+
218
164
  self.ticker_var = tk.StringVar()
219
165
  ticker_entry = tk.Entry(ticker_frame, textvariable=self.ticker_var, font=('Arial', 10),
220
- bg='#3d3d3d', fg='#ffffff', insertbackground='#ffffff',
221
- relief=tk.SOLID, borderwidth=1)
166
+ bg='#3d3d3d', fg='#ffffff', insertbackground='#ffffff',
167
+ relief=tk.SOLID, borderwidth=1)
222
168
  ticker_entry.pack(fill=tk.X, pady=(5, 0), ipady=4)
223
169
  ticker_entry.bind('<Return>', lambda e: self.fetch_and_plot())
224
-
170
+
225
171
  # Date inputs
226
172
  date_frame = tk.Frame(scrollable_frame, bg='#2d2d2d')
227
173
  date_frame.pack(pady=8, padx=15, fill=tk.X)
228
-
174
+
229
175
  tk.Label(date_frame, text="Start Date:", font=('Arial', 9, 'bold'),
230
176
  bg='#2d2d2d', fg='#ffffff').pack(anchor=tk.W)
177
+
231
178
  self.start_date = DateEntry(date_frame, width=23, background='#3d3d3d',
232
- foreground='white', borderwidth=2, date_pattern='yyyy-mm-dd')
179
+ foreground='white', borderwidth=2, date_pattern='yyyy-mm-dd')
233
180
  self.start_date.pack(fill=tk.X, pady=(5, 8))
234
- self.start_date.set_date(datetime.now() - timedelta(days=365))
235
-
181
+ self.start_date.set_date(datetime.now() - timedelta(days=30))
182
+
236
183
  tk.Label(date_frame, text="End Date:", font=('Arial', 9, 'bold'),
237
184
  bg='#2d2d2d', fg='#ffffff').pack(anchor=tk.W)
185
+
238
186
  self.end_date = DateEntry(date_frame, width=23, background='#3d3d3d',
239
- foreground='white', borderwidth=2, date_pattern='yyyy-mm-dd')
187
+ foreground='white', borderwidth=2, date_pattern='yyyy-mm-dd')
240
188
  self.end_date.pack(fill=tk.X, pady=(5, 0))
241
-
189
+
242
190
  # Fetch button
243
191
  fetch_btn = tk.Button(scrollable_frame, text="📊 Fetch Data", command=self.fetch_and_plot,
244
- bg='#00ff88', fg='#1e1e1e', font=('Arial', 10, 'bold'),
245
- relief=tk.RAISED, borderwidth=3, cursor='hand2',
246
- activebackground='#00dd77')
192
+ bg='#00ff88', fg='#1e1e1e', font=('Arial', 10, 'bold'),
193
+ relief=tk.RAISED, borderwidth=3, cursor='hand2',
194
+ activebackground='#00dd77')
247
195
  fetch_btn.pack(pady=12, padx=15, fill=tk.X, ipady=5)
248
-
196
+
249
197
  # Separator
250
198
  tk.Frame(scrollable_frame, height=2, bg='#00ff88').pack(fill=tk.X, padx=15, pady=10)
251
-
199
+
252
200
  # Moving Averages section
253
201
  ma_label = tk.Label(scrollable_frame, text="📈 Moving Averages", font=('Arial', 11, 'bold'),
254
- bg='#2d2d2d', fg='#00ff88')
202
+ bg='#2d2d2d', fg='#00ff88')
255
203
  ma_label.pack(pady=(8, 8))
256
-
204
+
257
205
  ma_frame = tk.Frame(scrollable_frame, bg='#2d2d2d')
258
206
  ma_frame.pack(pady=5, padx=15, fill=tk.X)
259
-
207
+
260
208
  for ma_name in ['SMA', 'EMA', 'DEMA', 'TEMA']:
261
209
  self.create_toggle_button(ma_frame, ma_name, self.ma_states[ma_name], is_ma=True)
262
-
210
+
263
211
  # Separator
264
212
  tk.Frame(scrollable_frame, height=2, bg='#00ff88').pack(fill=tk.X, padx=15, pady=10)
265
-
213
+
266
214
  # Technical Indicators section
267
215
  ind_label = tk.Label(scrollable_frame, text="📊 Technical Indicators", font=('Arial', 11, 'bold'),
268
- bg='#2d2d2d', fg='#00ff88')
216
+ bg='#2d2d2d', fg='#00ff88')
269
217
  ind_label.pack(pady=(8, 8))
270
-
218
+
271
219
  ind_frame = tk.Frame(scrollable_frame, bg='#2d2d2d')
272
220
  ind_frame.pack(pady=5, padx=15, fill=tk.X)
273
-
274
- for ind_name in ['MACD', 'ATR', 'RSI']:
221
+
222
+ for ind_name in ['RSI', 'MACD', 'ATR', 'Bollinger Bands']:
275
223
  self.create_toggle_button(ind_frame, ind_name, self.indicator_states[ind_name], is_ma=False)
276
-
224
+
277
225
  # Add some bottom padding
278
226
  tk.Frame(scrollable_frame, height=20, bg='#2d2d2d').pack()
279
-
227
+
280
228
  # Pack canvas and scrollbar
281
229
  canvas.pack(side="left", fill="both", expand=True)
282
230
  scrollbar.pack(side="right", fill="y")
283
-
231
+
284
232
  def create_toggle_button(self, parent, name, var, is_ma=True):
285
233
  frame = tk.Frame(parent, bg='#2d2d2d')
286
234
  frame.pack(pady=6, fill=tk.X)
287
-
235
+
288
236
  label = tk.Label(frame, text=name, font=('Arial', 9, 'bold'),
289
- bg='#2d2d2d', fg='#ffffff', width=8, anchor=tk.W)
237
+ bg='#2d2d2d', fg='#ffffff', width=12, anchor=tk.W)
290
238
  label.pack(side=tk.LEFT, padx=(0, 8))
291
-
239
+
292
240
  btn = tk.Button(frame, text="OFF", width=8,
293
241
  command=lambda: self.toggle_indicator(name, var, btn, is_ma),
294
242
  bg='#ff4444', fg='white', font=('Arial', 8, 'bold'),
@@ -296,284 +244,329 @@ class QuantDashboard:
296
244
  activebackground='#ff6666')
297
245
  btn.pack(side=tk.RIGHT)
298
246
  btn.var = var
299
-
247
+
300
248
  def toggle_indicator(self, name, var, btn, is_ma):
301
249
  var.set(not var.get())
302
250
  if var.get():
303
251
  btn.config(text="ON", bg='#00ff88', fg='#1e1e1e', activebackground='#00dd77')
304
252
  else:
305
253
  btn.config(text="OFF", bg='#ff4444', fg='white', activebackground='#ff6666')
306
-
254
+
307
255
  if self.data is not None:
308
256
  self.update_plot()
309
-
310
- def change_time_range(self, days):
257
+
258
+ def change_time_range(self, days, timeframe_key):
311
259
  end_date = datetime.now()
312
260
  start_date = end_date - timedelta(days=days)
313
-
314
261
  self.start_date.set_date(start_date)
315
262
  self.end_date.set_date(end_date)
316
263
 
264
+ # Update timeframe and period config
265
+ self.current_timeframe = timeframe_key
266
+ self.period_config = PERIOD_SETTINGS.get(timeframe_key, PERIOD_SETTINGS["1M"])
267
+
317
268
  if self.current_ticker:
318
269
  self.fetch_and_plot()
319
-
270
+
320
271
  def fetch_and_plot(self):
321
272
  ticker = self.ticker_var.get().strip().upper()
322
273
  if not ticker:
323
274
  messagebox.showwarning("Input Error", "Please enter a ticker symbol")
324
275
  return
325
-
276
+
326
277
  start = self.start_date.get_date().strftime('%Y-%m-%d')
327
278
  end = self.end_date.get_date().strftime('%Y-%m-%d')
328
-
279
+
329
280
  try:
330
281
  print(f"Fetching data for {ticker} from {start} to {end}...")
331
-
332
- # Show loading message
282
+
333
283
  # Remove welcome screen on first action
334
284
  for widget in self.chart_container.winfo_children():
335
285
  widget.destroy()
336
286
 
337
- # Ensure welcome label is gone
338
287
  self.welcome_label = None
339
-
288
+
340
289
  loading_label = tk.Label(self.chart_container, text=f"Loading {ticker} data...",
341
290
  font=('Arial', 16, 'bold'), bg='#2d2d2d', fg='#00ff88')
342
291
  loading_label.pack(expand=True)
292
+
343
293
  self.root.update()
344
-
294
+
345
295
  # Fetch data
346
296
  self.data = fetch_data(ticker, start, end)
347
-
297
+
348
298
  if self.data is None or self.data.empty:
349
299
  messagebox.showerror("Data Error", f"No data found for {ticker}")
350
300
  loading_label.destroy()
351
301
  return
352
-
302
+
353
303
  self.current_ticker = ticker
354
304
  self.current_start = start
355
305
  self.current_end = end
356
-
306
+
357
307
  print(f"Data fetched successfully! Shape: {self.data.shape}")
358
308
  print(f"Columns: {self.data.columns.tolist()}")
359
309
  print(f"Date range: {self.data.index[0]} to {self.data.index[-1]}")
360
-
310
+ print(f"Current Period Config: {self.period_config}")
311
+
361
312
  self.update_plot()
362
-
313
+
363
314
  except Exception as e:
364
315
  messagebox.showerror("Error", f"Error fetching data: {str(e)}")
365
316
  print(f"Error details: {e}")
366
317
  import traceback
367
318
  traceback.print_exc()
368
-
319
+
369
320
  def update_plot(self):
370
321
  if self.data is None or self.data.empty:
371
322
  return
372
-
323
+
373
324
  # Clear existing charts
374
325
  for widget in self.chart_container.winfo_children():
375
326
  widget.destroy()
376
-
327
+
377
328
  # Count active bottom indicators
378
- active_indicators = [name for name, var in self.indicator_states.items() if var.get()]
329
+ active_indicators = [name for name, var in self.indicator_states.items()
330
+ if var.get() and name != 'Bollinger Bands']
331
+
379
332
  num_indicators = len(active_indicators)
380
-
333
+
381
334
  # Create figure with dynamic subplot layout
382
335
  if num_indicators == 0:
383
336
  fig = Figure(figsize=(12, 7), facecolor='#2d2d2d', dpi=100)
384
337
  gs = fig.add_gridspec(1, 1)
385
338
  axes = [fig.add_subplot(gs[0, 0])]
339
+
386
340
  elif num_indicators == 1:
387
341
  fig = Figure(figsize=(12, 7), facecolor='#2d2d2d', dpi=100)
388
342
  gs = fig.add_gridspec(2, 1, height_ratios=[3, 1], hspace=0.3)
389
343
  axes = [fig.add_subplot(gs[0, 0]), fig.add_subplot(gs[1, 0])]
344
+
390
345
  elif num_indicators == 2:
391
346
  fig = Figure(figsize=(12, 7), facecolor='#2d2d2d', dpi=100)
392
347
  gs = fig.add_gridspec(2, 2, height_ratios=[3, 1], hspace=0.3, wspace=0.3)
393
348
  axes = [fig.add_subplot(gs[0, :]), fig.add_subplot(gs[1, 0]), fig.add_subplot(gs[1, 1])]
394
- else: # 3 indicators
349
+
350
+ else: # 3 or more indicators
395
351
  fig = Figure(figsize=(12, 7), facecolor='#2d2d2d', dpi=100)
396
352
  gs = fig.add_gridspec(2, 3, height_ratios=[3, 1], hspace=0.3, wspace=0.3)
397
- axes = [fig.add_subplot(gs[0, :]), fig.add_subplot(gs[1, 0]),
353
+ axes = [fig.add_subplot(gs[0, :]), fig.add_subplot(gs[1, 0]),
398
354
  fig.add_subplot(gs[1, 1]), fig.add_subplot(gs[1, 2])]
399
-
355
+
400
356
  # Main price chart with candlesticks
401
357
  ax_main = axes[0]
358
+
402
359
  self.plot_candlesticks(ax_main)
403
-
360
+
361
+ # Add Bollinger Bands to main chart if enabled
362
+ if self.indicator_states['Bollinger Bands'].get():
363
+ self.add_bollinger_bands(ax_main)
364
+
404
365
  # Add moving averages to main chart
405
366
  self.add_moving_averages(ax_main)
406
-
407
- ax_main.set_title(f'{self.current_ticker} - Price Chart',
408
- color='#ffffff', fontsize=12, fontweight='bold', pad=10)
367
+
368
+ ax_main.set_title(f'{self.current_ticker} - Price Chart | Period Config: {self.period_config}',
369
+ color='#ffffff', fontsize=12, fontweight='bold', pad=10)
409
370
  ax_main.set_ylabel('Price ($)', color='#ffffff', fontsize=9)
410
371
  ax_main.tick_params(colors='#ffffff', labelsize=7)
411
372
  ax_main.grid(True, alpha=0.2, color='#ffffff', linestyle='--')
412
-
373
+
413
374
  # Only show legend if there are items to display
414
375
  handles, labels = ax_main.get_legend_handles_labels()
415
376
  if handles:
416
- ax_main.legend(loc='upper left', facecolor='#2d2d2d',
417
- edgecolor='#00ff88', labelcolor='#ffffff', fontsize=7)
418
-
377
+ ax_main.legend(loc='upper left', facecolor='#2d2d2d',
378
+ edgecolor='#00ff88', labelcolor='#ffffff', fontsize=7)
419
379
  ax_main.set_facecolor('#1e1e1e')
420
-
380
+
421
381
  # Plot active indicators
422
382
  for idx, ind_name in enumerate(active_indicators):
423
383
  ax_ind = axes[idx + 1]
424
384
  ax_ind.set_facecolor('#1e1e1e')
425
-
385
+
426
386
  if ind_name == 'RSI':
427
387
  self.plot_rsi_indicator(ax_ind)
428
388
  elif ind_name == 'MACD':
429
389
  self.plot_macd_indicator(ax_ind)
430
390
  elif ind_name == 'ATR':
431
391
  self.plot_atr_indicator(ax_ind)
432
-
392
+
433
393
  fig.tight_layout(pad=1.5)
434
-
394
+
435
395
  # Embed figure
436
396
  canvas = FigureCanvasTkAgg(fig, self.chart_container)
437
397
  canvas.draw()
438
398
  canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
439
-
399
+
440
400
  def plot_candlesticks(self, ax):
441
401
  """Plot candlestick chart"""
442
402
  data_clean = self.data[['Open', 'High', 'Low', 'Close']].dropna()
443
-
403
+
444
404
  if len(data_clean) == 0:
445
405
  return
446
-
406
+
447
407
  open_prices = data_clean['Open'].values
448
408
  close_prices = data_clean['Close'].values
449
409
  high_prices = data_clean['High'].values
450
410
  low_prices = data_clean['Low'].values
451
-
411
+
452
412
  x = range(len(open_prices))
453
413
  width = 0.6
454
-
414
+
455
415
  for i in x:
456
- color = "#00ff00" if close_prices[i] >= open_prices[i] else "#ff0000ff"
457
-
416
+ color = "#00ff00" if close_prices[i] >= open_prices[i] else "#ff0000"
417
+
458
418
  # Draw wick
459
- ax.plot([i, i], [low_prices[i], high_prices[i]],
419
+ ax.plot([i, i], [low_prices[i], high_prices[i]],
460
420
  color='#ffffff', linewidth=0.8, alpha=0.8)
461
-
421
+
462
422
  # Draw body
463
423
  body_height = abs(close_prices[i] - open_prices[i])
464
424
  body_bottom = min(open_prices[i], close_prices[i])
465
-
425
+
466
426
  rect = plt.Rectangle((i - width/2, body_bottom), width, body_height,
467
- facecolor=color, edgecolor='#ffffff',
468
- linewidth=0.5, alpha=0.9)
427
+ facecolor=color, edgecolor='#ffffff',
428
+ linewidth=0.5, alpha=0.9)
469
429
  ax.add_patch(rect)
470
-
430
+
471
431
  # Format x-axis with dates
472
432
  step = max(1, len(data_clean) // 10)
473
433
  tick_positions = list(range(0, len(data_clean), step))
474
434
  tick_labels = [data_clean.index[i].strftime('%Y-%m-%d') for i in tick_positions]
435
+
475
436
  ax.set_xticks(tick_positions)
476
437
  ax.set_xticklabels(tick_labels, rotation=45, ha='right')
477
-
438
+
439
+ def add_bollinger_bands(self, ax):
440
+ """Add Bollinger Bands to the price chart"""
441
+ bb_period = self.period_config.get('bb', 20)
442
+ bb_upper, bb_mid, bb_lower = bb_bands(self.data['Close'], period=bb_period)
443
+
444
+ x = range(len(self.data))
445
+
446
+ ax.plot(x, bb_upper.values, color='#FFD700', label=f'BB Upper ({bb_period})',
447
+ linestyle='--', linewidth=1.5, alpha=0.7)
448
+ ax.plot(x, bb_mid.values, color='#00FFFF', label=f'BB Middle ({bb_period})',
449
+ linestyle='-.', linewidth=1, alpha=0.6)
450
+ ax.plot(x, bb_lower.values, color='#FFD700', label=f'BB Lower ({bb_period})',
451
+ linestyle='--', linewidth=1.5, alpha=0.7)
452
+
453
+ # Fill between upper and lower bands
454
+ ax.fill_between(x, bb_upper.values, bb_lower.values, alpha=0.1, color='#FFD700')
455
+
478
456
  def add_moving_averages(self, ax):
479
457
  """Add moving averages to the chart"""
480
458
  x = range(len(self.data))
481
-
459
+ ma_period = self.period_config.get('ma', 9)
460
+
482
461
  if self.ma_states['SMA'].get():
483
- sma_val = sma(self.data['Close'], period=20)
484
- ax.plot(x, sma_val.values, label='SMA(20)',
462
+ sma_val = sma(self.data['Close'], period=ma_period)
463
+ ax.plot(x, sma_val.values, label=f'SMA({ma_period})',
485
464
  color='#00aaff', linewidth=1.5, linestyle='--', alpha=0.8)
486
-
465
+
487
466
  if self.ma_states['EMA'].get():
488
- ema_val = ema(self.data['Close'], period=20)
489
- ax.plot(x, ema_val.values, label='EMA(20)',
467
+ ema_val = ema(self.data['Close'], period=ma_period)
468
+ ax.plot(x, ema_val.values, label=f'EMA({ma_period})',
490
469
  color='#ff9500', linewidth=1.5, linestyle='--', alpha=0.8)
491
-
470
+
492
471
  if self.ma_states['DEMA'].get():
493
- dema_val = demma(self.data['Close'], period=20)
494
- ax.plot(x, dema_val.values, label='DEMA(20)',
472
+ dema_val = demma(self.data['Close'], period=ma_period)
473
+ ax.plot(x, dema_val.values, label=f'DEMA({ma_period})',
495
474
  color='#af52de', linewidth=1.5, linestyle='-.', alpha=0.8)
496
-
475
+
497
476
  if self.ma_states['TEMA'].get():
498
- tema_val = temma(self.data['Close'], period=20)
499
- ax.plot(x, tema_val.values, label='TEMA(20)',
477
+ tema_val = temma(self.data['Close'], period=ma_period)
478
+ ax.plot(x, tema_val.values, label=f'TEMA({ma_period})',
500
479
  color='#ffcc00', linewidth=1.5, linestyle='-.', alpha=0.8)
501
-
480
+
502
481
  def plot_rsi_indicator(self, ax):
503
482
  """Plot RSI indicator"""
504
- rsi_val = Rsi(self.data['Close'], period=14)
483
+ rsi_period = self.period_config.get('rsi', 14)
484
+ rsi_val = Rsi(self.data['Close'], period=rsi_period)
485
+
505
486
  x = range(len(rsi_val))
506
-
487
+
507
488
  ax.plot(x, rsi_val.values, color='#af52de', linewidth=2)
508
489
  ax.axhline(y=70, color='#ff4444', linestyle='--', linewidth=1.5, alpha=0.7)
509
490
  ax.axhline(y=30, color='#00ff88', linestyle='--', linewidth=1.5, alpha=0.7)
491
+
510
492
  ax.fill_between(x, 70, 100, alpha=0.1, color='#ff4444')
511
493
  ax.fill_between(x, 0, 30, alpha=0.1, color='#00ff88')
512
-
494
+
513
495
  ax.set_ylim(0, 100)
514
- ax.set_title('RSI (14)', color='#ffffff', fontsize=10, fontweight='bold')
496
+ ax.set_title(f'RSI ({rsi_period})', color='#ffffff', fontsize=10, fontweight='bold')
515
497
  ax.set_ylabel('RSI', color='#ffffff', fontsize=8)
516
498
  ax.tick_params(colors='#ffffff', labelsize=6)
517
499
  ax.grid(True, alpha=0.2, color='#ffffff', linestyle='--')
518
-
500
+
519
501
  # Format x-axis
520
502
  step = max(1, len(self.data) // 5)
521
503
  tick_positions = list(range(0, len(self.data), step))
522
504
  tick_labels = [self.data.index[i].strftime('%m-%d') for i in tick_positions]
505
+
523
506
  ax.set_xticks(tick_positions)
524
507
  ax.set_xticklabels(tick_labels, rotation=45, ha='right')
525
-
508
+
526
509
  def plot_macd_indicator(self, ax):
527
510
  """Plot MACD indicator"""
528
- macd_line, signal_line, hist = macd(self.data['Close'])
511
+ macd_config = self.period_config.get('macd', (12, 26, 9))
512
+ short_period, long_period, signal_period = macd_config
513
+
514
+ macd_line, signal_line, hist = macd(self.data['Close'],
515
+ short_period=short_period,
516
+ long_period=long_period,
517
+ signal_period=signal_period)
518
+
529
519
  x = range(len(macd_line))
530
-
520
+
531
521
  ax.plot(x, macd_line.values, label='MACD', color='#00aaff', linewidth=1.5)
532
522
  ax.plot(x, signal_line.values, label='Signal', color='#ff9500', linewidth=1.5)
533
-
523
+
534
524
  colors = ['#00ff88' if val >= 0 else '#ff4444' for val in hist.values]
535
525
  ax.bar(x, hist.values, color=colors, alpha=0.6, width=0.8)
536
-
526
+
537
527
  ax.axhline(y=0, color='#ffffff', linestyle='-', linewidth=0.8, alpha=0.5)
538
- ax.set_title('MACD', color='#ffffff', fontsize=10, fontweight='bold')
528
+
529
+ ax.set_title(f'MACD ({short_period}/{long_period}/{signal_period})',
530
+ color='#ffffff', fontsize=10, fontweight='bold')
539
531
  ax.set_ylabel('Value', color='#ffffff', fontsize=8)
540
532
  ax.tick_params(colors='#ffffff', labelsize=6)
541
533
  ax.grid(True, alpha=0.2, color='#ffffff', linestyle='--')
542
534
  ax.legend(loc='upper left', fontsize=6, facecolor='#2d2d2d',
543
535
  edgecolor='#00ff88', labelcolor='#ffffff')
544
-
536
+
545
537
  # Format x-axis
546
538
  step = max(1, len(self.data) // 5)
547
539
  tick_positions = list(range(0, len(self.data), step))
548
540
  tick_labels = [self.data.index[i].strftime('%m-%d') for i in tick_positions]
541
+
549
542
  ax.set_xticks(tick_positions)
550
543
  ax.set_xticklabels(tick_labels, rotation=45, ha='right')
551
-
544
+
552
545
  def plot_atr_indicator(self, ax):
553
546
  """Plot ATR indicator"""
554
- atr_val = atr(self.data, period=14)
547
+ atr_period = self.period_config.get('atr', 14)
548
+ atr_val = atr(self.data, period=atr_period)
549
+
555
550
  x = range(len(atr_val))
556
-
551
+
557
552
  ax.plot(x, atr_val.values, color='#ff9500', linewidth=2)
558
553
  ax.fill_between(x, 0, atr_val.values, alpha=0.2, color='#ff9500')
559
-
560
- ax.set_title('ATR (14)', color='#ffffff', fontsize=10, fontweight='bold')
554
+
555
+ ax.set_title(f'ATR ({atr_period})', color='#ffffff', fontsize=10, fontweight='bold')
561
556
  ax.set_ylabel('ATR', color='#ffffff', fontsize=8)
562
557
  ax.tick_params(colors='#ffffff', labelsize=6)
563
558
  ax.grid(True, alpha=0.2, color='#ffffff', linestyle='--')
564
-
559
+
565
560
  # Format x-axis
566
561
  step = max(1, len(self.data) // 5)
567
562
  tick_positions = list(range(0, len(self.data), step))
568
563
  tick_labels = [self.data.index[i].strftime('%m-%d') for i in tick_positions]
564
+
569
565
  ax.set_xticks(tick_positions)
570
566
  ax.set_xticklabels(tick_labels, rotation=45, ha='right')
571
567
 
572
-
573
568
  def launch_dashboard():
574
569
  """Main function to launch the QuantResearch Dashboard"""
575
570
  root = tk.Tk()
576
571
  app = QuantDashboard(root)
577
572
  root.mainloop()
578
-
579
- launch_dashboard()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: QuantResearch
3
- Version: 2.1
3
+ Version: 2.3
4
4
  Summary: Technical indicators and visualization tools for quantitative research
5
5
  Home-page: https://github.com/vinayak1729-web/QuantR
6
6
  Author: Vinayak ShindeVishal Mishra
@@ -9,7 +9,7 @@ License: MIT
9
9
  Project-URL: Homepage, https://github.com/vinayak1729-web/QuantR
10
10
  Project-URL: Repository, https://github.com/vinayak1729-web/QuantR
11
11
  Project-URL: Issues, https://github.com/vinayak1729-web/QuantR/issues
12
- Project-URL: Documentation, https://github.com/vinayak1729-web/QuantR#readme
12
+ Project-URL: Documentation, https://vinayak1729-web.github.io/QuantR/
13
13
  Keywords: finance,trading,technical-analysis,indicators,quantitative,stocks,yfinance
14
14
  Classifier: Development Status :: 3 - Alpha
15
15
  Classifier: Intended Audience :: Developers
@@ -1,2 +1,3 @@
1
1
  QuantResearch
2
2
  dist
3
+ static
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "QuantResearch"
7
- version = "2.1"
7
+ version = "2.3"
8
8
  description = "Technical indicators and visualization tools for quantitative research"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.7"
@@ -57,7 +57,7 @@ dev = [
57
57
  Homepage = "https://github.com/vinayak1729-web/QuantR"
58
58
  Repository = "https://github.com/vinayak1729-web/QuantR"
59
59
  Issues = "https://github.com/vinayak1729-web/QuantR/issues"
60
- Documentation = "https://github.com/vinayak1729-web/QuantR#readme"
60
+ Documentation = "https://vinayak1729-web.github.io/QuantR/"
61
61
 
62
62
  [tool.setuptools.packages.find]
63
63
  where = ["."]
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="QuantResearch",
8
- version="0.0.2",
8
+ version="2.3",
9
9
  author="Vinayak Shinde" "Vishal Mishra",
10
10
  author_email="vinayak.r.shinde.1729@gmail.com",
11
11
  description="Technical indicators and visualization tools for quantitative research",
@@ -1,76 +0,0 @@
1
- from .indicators import (
2
- fetch_data,
3
- Rsi,
4
- bb_bands,
5
- macd,
6
- atr,
7
- sma,
8
- ema,
9
- demma,
10
- temma,
11
- RVWAP
12
- )
13
-
14
- from .visualize import (
15
- plot_candlestick,
16
- plot_macd,
17
- plot_bollinger,
18
- plot_rsi,
19
- plot_moving_averages
20
- )
21
-
22
- # Import dashboard functionality
23
- try:
24
- from .dashboard import launch_dashboard
25
- __all__ = [
26
- # Data fetching
27
- 'fetch_data',
28
-
29
- # Indicators
30
- 'Rsi',
31
- 'bb_bands',
32
- 'macd',
33
- 'atr',
34
- 'sma',
35
- 'ema',
36
- 'demma',
37
- 'temma',
38
- 'RVWAP',
39
-
40
- # Visualization
41
- 'plot_candlestick',
42
- 'plot_macd',
43
- 'plot_bollinger',
44
- 'plot_rsi',
45
- 'plot_moving_averages',
46
-
47
- # Dashboard
48
- 'launch_dashboard'
49
- ]
50
- except ImportError as e:
51
- # Dashboard dependencies (tkinter, tkcalendar) may not be installed
52
- print(f"Warning: Dashboard not available. Install with: pip install tkcalendar")
53
- __all__ = [
54
- # Data fetching
55
- 'fetch_data',
56
-
57
- # Indicators
58
- 'Rsi',
59
- 'bb_bands',
60
- 'macd',
61
- 'atr',
62
- 'sma',
63
- 'ema',
64
- 'demma',
65
- 'temma',
66
- 'RVWAP',
67
-
68
- # Visualization
69
- 'plot_candlestick',
70
- 'plot_macd',
71
- 'plot_bollinger',
72
- 'plot_rsi',
73
- 'plot_moving_averages'
74
- ]
75
-
76
- __version__ = '2.0.0'
File without changes
File without changes
File without changes