Dmitry Beresnev commited on
Commit
7c9bc72
Β·
1 Parent(s): 7406726

add async trading grid calculator

Browse files
.gitignore CHANGED
@@ -9,6 +9,7 @@ uv.lock
9
  # Ignore egg-info directories
10
  src/your_project_name.egg-info/
11
  src/news_sentoment_analyzer.egg-info/
 
12
  # Ignore Python bytecode files
13
  *.pyc
14
  *.pyo
 
9
  # Ignore egg-info directories
10
  src/your_project_name.egg-info/
11
  src/news_sentoment_analyzer.egg-info/
12
+ src/financial_news_bot.egg-info/
13
  # Ignore Python bytecode files
14
  *.pyc
15
  *.pyo
src/services/async_trading_grid_calculator.py ADDED
@@ -0,0 +1,513 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import aiohttp
3
+ import yfinance as yf
4
+ import numpy as np
5
+ import pandas as pd
6
+ import warnings
7
+ from typing import Dict, List, Tuple, Optional, Union
8
+ from datetime import datetime, timedelta
9
+ import matplotlib
10
+ matplotlib.use('Agg')
11
+ import matplotlib.pyplot as plt
12
+ import seaborn as sns
13
+ import io
14
+ import base64
15
+ from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
16
+ import logging
17
+
18
+ warnings.filterwarnings('ignore')
19
+ logging.basicConfig(level=logging.INFO)
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class AsyncTradingGridGenerator:
24
+ """
25
+ Asynchronous trading grid generator for Telegram bots
26
+ """
27
+ def __init__(self):
28
+ self.strategies = {
29
+ "conservative": {
30
+ "growth": 1.15,
31
+ "drop_pct": 0.2,
32
+ "levels": 5,
33
+ "risk_factor": 0.8,
34
+ "description": "πŸ›‘οΈ Conservative strategy with minimal risk",
35
+ "emoji": "🐌"
36
+ },
37
+ "medium": {
38
+ "growth": 1.3,
39
+ "drop_pct": 0.35,
40
+ "levels": 8,
41
+ "risk_factor": 1.0,
42
+ "description": "βš–οΈ Balanced strategy with medium aggression",
43
+ "emoji": "🎯"
44
+ },
45
+ "aggressive": {
46
+ "growth": 1.5,
47
+ "drop_pct": 0.5,
48
+ "levels": 12,
49
+ "risk_factor": 1.3,
50
+ "description": "πŸš€ Aggressive strategy with high profitability",
51
+ "emoji": "πŸ”₯"
52
+ },
53
+ "ultra_aggressive": {
54
+ "growth": 1.8,
55
+ "drop_pct": 0.7,
56
+ "levels": 15,
57
+ "risk_factor": 1.6,
58
+ "description": "⚑ Maximum aggressive strategy",
59
+ "emoji": "πŸ’₯"
60
+ }
61
+ }
62
+ self.executor = ProcessPoolExecutor(max_workers=4)
63
+ self.chart_executor = ThreadPoolExecutor(max_workers=2)
64
+
65
+ @staticmethod
66
+ def fetch_data(ticker: str, period: str = "1y") -> pd.DataFrame:
67
+ try:
68
+ stock = yf.Ticker(ticker)
69
+ data = stock.history(period=period)
70
+ if data.empty:
71
+ raise ValueError(f"Data for ticker {ticker} not found")
72
+ # Add technical indicators
73
+ data['SMA_20'] = data['Close'].rolling(20).mean()
74
+ data['SMA_50'] = data['Close'].rolling(50).mean()
75
+ data['Volatility'] = data['Close'].pct_change().rolling(20).std() * np.sqrt(252)
76
+ return data
77
+ except Exception as e:
78
+ logger.error(f"Error getting data for {ticker}: {e}")
79
+ raise ValueError(f"Data retrieval error: {e}")
80
+
81
+ async def get_stock_data_async(self, ticker: str, period: str = "1y") -> pd.DataFrame:
82
+ """Asynchronous stock data retrieval"""
83
+ loop = asyncio.get_event_loop()
84
+ return await loop.run_in_executor(
85
+ self.executor, AsyncTradingGridGenerator.fetch_data, ticker, period
86
+ )
87
+
88
+ def calculate_technical_indicators(self, data: pd.DataFrame) -> Dict[str, float]:
89
+ """Technical indicators calculation"""
90
+ current_price = data['Close'].iloc[-1]
91
+ # ATR (Average True Range)
92
+ high_low = data['High'] - data['Low']
93
+ high_close = np.abs(data['High'] - data['Close'].shift())
94
+ low_close = np.abs(data['Low'] - data['Close'].shift())
95
+ tr = np.maximum.reduce([high_low, high_close, low_close])
96
+ tr_series = pd.Series(tr, index=data.index)
97
+ atr = tr_series.rolling(14).mean().iloc[-1]
98
+ # Support and resistance
99
+ support = data['Low'].rolling(20).min().iloc[-1]
100
+ resistance = data['High'].rolling(20).max().iloc[-1]
101
+ # Volatility
102
+ volatility = data['Volatility'].iloc[-1] if not pd.isna(data['Volatility'].iloc[-1]) else 0.25
103
+ # RSI
104
+ delta = data['Close'].diff()
105
+ gain = (delta.where(delta > 0, 0)).rolling(14).mean()
106
+ loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
107
+ rs = gain / loss
108
+ rsi = 100 - (100 / (1 + rs))
109
+ return {
110
+ 'current_price': current_price,
111
+ 'atr': atr,
112
+ 'support': support,
113
+ 'resistance': resistance,
114
+ 'volatility': volatility,
115
+ 'rsi': rsi.iloc[-1] if not pd.isna(rsi.iloc[-1]) else 50,
116
+ 'sma_20': data['SMA_20'].iloc[-1] if not pd.isna(data['SMA_20'].iloc[-1]) else current_price,
117
+ 'sma_50': data['SMA_50'].iloc[-1] if not pd.isna(data['SMA_50'].iloc[-1]) else current_price
118
+ }
119
+
120
+ def fibonacci_levels(self, current_price: float, low: float, high: float) -> List[float]:
121
+ """Fibonacci levels calculation"""
122
+ ratios = [0.236, 0.382, 0.5, 0.618, 0.786, 0.886]
123
+ levels = []
124
+ for ratio in ratios:
125
+ level = high - (high - low) * ratio
126
+ if level < current_price:
127
+ levels.append(level)
128
+ return sorted(levels, reverse=True)
129
+
130
+ def geometric_levels(self, current_price: float, drop_pct: float, levels: int) -> List[float]:
131
+ """Geometric levels"""
132
+ min_price = current_price * (1 - drop_pct)
133
+ step = (current_price - min_price) / levels
134
+ return [current_price - step * (i + 1) for i in range(levels)]
135
+
136
+ def volatility_adjusted_levels(self, current_price: float, atr: float,
137
+ volatility: float, levels: int) -> List[float]:
138
+ """Volatility-based levels"""
139
+ vol_step = atr * (1 + volatility)
140
+ return [current_price - vol_step * (i + 1) for i in range(levels)]
141
+
142
+ def combine_and_optimize_levels(self, fib_levels: List[float],
143
+ geom_levels: List[float],
144
+ vol_levels: List[float],
145
+ current_price: float,
146
+ min_distance: float = 0.02) -> List[float]:
147
+ """Combining and optimizing levels"""
148
+ all_levels = fib_levels + geom_levels + vol_levels
149
+ # Remove duplicates and sort
150
+ unique_levels = list(set([round(level, 2) for level in all_levels
151
+ if level < current_price and level > 0]))
152
+ unique_levels.sort(reverse=True)
153
+ # Remove levels that are too close
154
+ optimized_levels = []
155
+ last_level = current_price
156
+ for level in unique_levels:
157
+ distance = abs(last_level - level) / current_price
158
+ if distance >= min_distance:
159
+ optimized_levels.append(level)
160
+ last_level = level
161
+ return optimized_levels
162
+
163
+ def calculate_position_sizes(self, levels: List[float], current_price: float,
164
+ capital: float, growth: float,
165
+ risk_factor: float) -> List[float]:
166
+ """Position sizes calculation"""
167
+ n = len(levels)
168
+ if n == 0:
169
+ return []
170
+ # Weights increase with price decline
171
+ weights = []
172
+ for i, level in enumerate(levels):
173
+ drop_pct = (current_price - level) / current_price
174
+ weight = (growth ** i) * (1 + drop_pct * risk_factor)
175
+ weights.append(weight)
176
+ # Weight normalization
177
+ total_weight = sum(weights)
178
+ position_sizes = [(weight / total_weight) * capital for weight in weights]
179
+ return position_sizes
180
+
181
+ def calculate_grid_metrics(self, df: pd.DataFrame, current_price: float) -> Dict[str, float]:
182
+ """Grid metrics calculation"""
183
+ if df.empty:
184
+ return {}
185
+ total_capital = df['OrderSize'].sum()
186
+ max_drawdown = df['%Drop'].max()
187
+ avg_order_size = df['OrderSize'].mean()
188
+ # Potential profit when returning to current price
189
+ potential_profit = 0
190
+ for _, row in df.iterrows():
191
+ shares = row['OrderSize'] / row['Price']
192
+ profit = shares * (current_price - row['Price'])
193
+ potential_profit += profit
194
+ return {
195
+ 'total_capital': total_capital,
196
+ 'max_drawdown': max_drawdown,
197
+ 'avg_order_size': avg_order_size,
198
+ 'potential_profit': potential_profit,
199
+ 'profit_margin': (potential_profit / total_capital) * 100 if total_capital > 0 else 0,
200
+ 'number_of_orders': len(df)
201
+ }
202
+
203
+ async def generate_grid_async(self, ticker: str, capital: float = 10000,
204
+ strategy: str = "medium") -> Tuple[Dict, pd.DataFrame, Dict]:
205
+ """Asynchronous grid generation"""
206
+ if strategy not in self.strategies:
207
+ raise ValueError(f"Unknown strategy: {strategy}")
208
+ # Get data asynchronously
209
+ data = await self.get_stock_data_async(ticker)
210
+ indicators = self.calculate_technical_indicators(data)
211
+ strategy_params = self.strategies[strategy]
212
+ current_price = indicators['current_price']
213
+ # Generate levels
214
+ fib_levels = self.fibonacci_levels(
215
+ current_price,
216
+ indicators['support'],
217
+ indicators['resistance']
218
+ )
219
+ geom_levels = self.geometric_levels(
220
+ current_price,
221
+ strategy_params['drop_pct'],
222
+ strategy_params['levels']
223
+ )
224
+ vol_levels = self.volatility_adjusted_levels(
225
+ current_price,
226
+ indicators['atr'],
227
+ indicators['volatility'],
228
+ strategy_params['levels'] // 2
229
+ )
230
+ # Combine levels
231
+ combined_levels = self.combine_and_optimize_levels(
232
+ fib_levels, geom_levels, vol_levels, current_price
233
+ )
234
+ if not combined_levels:
235
+ raise ValueError("Failed to generate grid levels")
236
+ # Calculate position sizes
237
+ position_sizes = self.calculate_position_sizes(
238
+ combined_levels,
239
+ current_price,
240
+ capital,
241
+ strategy_params['growth'],
242
+ strategy_params['risk_factor']
243
+ )
244
+ # Create DataFrame
245
+ df = pd.DataFrame({
246
+ 'Level': range(1, len(combined_levels) + 1),
247
+ 'Price': combined_levels,
248
+ 'OrderSize': position_sizes,
249
+ 'Shares': [size / price for size, price in zip(position_sizes, combined_levels)],
250
+ '%Drop': [(current_price - price) / current_price * 100 for price in combined_levels],
251
+ 'Distance_ATR': [(current_price - price) / indicators['atr'] for price in combined_levels]
252
+ })
253
+ # Add level type column
254
+ df['Type'] = 'Combined'
255
+ for i, price in enumerate(combined_levels):
256
+ if price in fib_levels:
257
+ df.loc[i, 'Type'] = 'Fibonacci'
258
+ elif abs(price - min(geom_levels, key=lambda x: abs(x - price))) < 0.01:
259
+ df.loc[i, 'Type'] = 'Geometric'
260
+ # Calculate metrics
261
+ metrics = self.calculate_grid_metrics(df, current_price)
262
+ return indicators, df, metrics
263
+
264
+ def format_telegram_message(self, ticker: str, indicators: Dict, df: pd.DataFrame,
265
+ metrics: Dict, strategy: str) -> str:
266
+ """Telegram message formatting"""
267
+ strategy_info = self.strategies[strategy]
268
+ # Determine trend
269
+ trend_emoji = "πŸ“ˆ" if indicators['current_price'] > indicators['sma_20'] else "πŸ“‰"
270
+ rsi_status = "πŸ”΄ Oversold" if indicators['rsi'] < 30 else "🟒 Overbought" if indicators[
271
+ 'rsi'] > 70 else "🟑 Neutral"
272
+ message = f"""🎯 <b>TRADING GRID {ticker.upper()}</b>
273
+ {strategy_info['emoji']} <b>Strategy:</b> {strategy.upper()}
274
+ {strategy_info['description']}
275
+ πŸ“Š <b>CURRENT INDICATORS:</b>
276
+ πŸ’° Price: <code>${indicators['current_price']:.2f}</code> {trend_emoji}
277
+ πŸ“Š RSI: <code>{indicators['rsi']:.1f}</code> {rsi_status}
278
+ ⚑ Volatility: <code>{indicators['volatility']:.1%}</code>
279
+ 🎯 ATR: <code>${indicators['atr']:.2f}</code>
280
+ πŸ”» Support: <code>${indicators['support']:.2f}</code>
281
+ πŸ”Ί Resistance: <code>${indicators['resistance']:.2f}</code>
282
+ πŸ“‹ <b>GRID METRICS:</b>
283
+ πŸ’΅ Capital: <code>${metrics['total_capital']:.2f}</code>
284
+ πŸ“‰ Max drawdown: <code>{metrics['max_drawdown']:.2f}%</code>
285
+ 🎯 Orders: <code>{metrics['number_of_orders']}</code>
286
+ πŸ’° Potential: <code>${metrics['potential_profit']:.2f}</code>
287
+ πŸ“ˆ Margin: <code>{metrics['profit_margin']:.2f}%</code>
288
+ 🎯 <b>TOP-{min(5, len(df))} LEVELS:</b>
289
+ ```
290
+ β„– Price Size Drop Type
291
+ """
292
+ # Add top-5 levels
293
+ for i in range(min(5, len(df))):
294
+ row = df.iloc[i]
295
+ type_emoji = "πŸŒ€" if row['Type'] == 'Fibonacci' else "πŸ“" if row['Type'] == 'Geometric' else "⚑"
296
+ message += f"{row['Level']:2.0f} ${row['Price']:6.2f} ${row['OrderSize']:7.0f} {row['%Drop']:6.2f}% {type_emoji}\n"
297
+ message += "```"
298
+ if len(df) > 5:
299
+ message += f"\nπŸ“ <i>Showing {min(5, len(df))} of {len(df)} levels</i>"
300
+ return message
301
+
302
+ def format_comparison_message(self, ticker: str, comparison_data: List[Dict]) -> str:
303
+ """Strategy comparison message formatting for Telegram"""
304
+ message = f"""πŸ“Š <b>STRATEGY COMPARISON {ticker.upper()}</b>
305
+ """
306
+ for data in comparison_data:
307
+ strategy = data['strategy']
308
+ strategy_info = self.strategies[strategy]
309
+ message += f"""{strategy_info['emoji']} <b>{strategy.upper()}</b>
310
+ 🎯 Orders: <code>{data['orders']}</code>
311
+ πŸ“‰ Drawdown: <code>{data['max_drawdown']:.2f}%</code>
312
+ πŸ’° Profit: <code>${data['potential_profit']:.0f}</code>
313
+ πŸ“ˆ Margin: <code>{data['profit_margin']:.1f}%</code>
314
+ """
315
+ # Recommendation
316
+ best_strategy = max(comparison_data, key=lambda x: x['profit_margin'])
317
+ message += f"πŸ’‘ <b>Recommendation:</b> {self.strategies[best_strategy['strategy']]['emoji']} {best_strategy['strategy'].upper()}"
318
+ return message
319
+
320
+ def create_chart(self, ticker, indicators, df, strategy):
321
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(12, 10))
322
+ fig.suptitle(f'{ticker.upper()} - {strategy}', fontsize=14, fontweight='bold')
323
+ current_price = indicators['current_price']
324
+ # 1. Order distribution
325
+ colors = ['#FF6B6B' if t == 'Fibonacci' else '#4ECDC4' if t == 'Geometric' else '#45B7D1'
326
+ for t in df['Type']]
327
+ ax1.barh(df['Level'], df['OrderSize'], color=colors, alpha=0.8)
328
+ ax1.set_xlabel('Order Size ($)')
329
+ ax1.set_ylabel('Level')
330
+ ax1.set_title('πŸ“Š Order Distribution')
331
+ ax1.grid(True, alpha=0.3)
332
+ # 2. Level prices
333
+ ax2.scatter(df['Price'], df['%Drop'], c=colors, s=df['OrderSize'] / 30, alpha=0.8)
334
+ ax2.axvline(x=current_price, color='red', linestyle='--', alpha=0.7,
335
+ label=f'${current_price:.2f}')
336
+ ax2.set_xlabel('Price ($)')
337
+ ax2.set_ylabel('% Drop')
338
+ ax2.set_title('🎯 Levels and Drops')
339
+ ax2.legend()
340
+ ax2.grid(True, alpha=0.3)
341
+ # 3. Cumulative capital
342
+ cumulative = df['OrderSize'].cumsum()
343
+ ax3.plot(df['Level'], cumulative, marker='o', linewidth=2, color='#FF6B6B')
344
+ ax3.fill_between(df['Level'], cumulative, alpha=0.3, color='#FF6B6B')
345
+ ax3.set_xlabel('Level')
346
+ ax3.set_ylabel('Capital ($)')
347
+ ax3.set_title('πŸ’° Cumulative Capital')
348
+ ax3.grid(True, alpha=0.3)
349
+ # 4. Level types
350
+ type_counts = df['Type'].value_counts()
351
+ colors_pie = ['#FF6B6B', '#4ECDC4', '#45B7D1']
352
+ ax4.pie(type_counts.values, labels=type_counts.index, autopct='%1.1f%%',
353
+ startangle=90, colors=colors_pie)
354
+ ax4.set_title('πŸ“ˆ Level Types')
355
+ plt.tight_layout()
356
+ # Save to bytes
357
+ buf = io.BytesIO()
358
+ plt.savefig(buf, format='png', dpi=150, bbox_inches='tight')
359
+ buf.seek(0)
360
+ chart_bytes = buf.getvalue()
361
+ buf.close()
362
+ plt.close()
363
+ return chart_bytes
364
+
365
+ async def create_grid_chart_async(self, ticker: str, indicators: Dict,
366
+ df: pd.DataFrame, strategy: str) -> bytes:
367
+ """Asynchronous grid chart creation"""
368
+ loop = asyncio.get_event_loop()
369
+ return await loop.run_in_executor(
370
+ self.chart_executor, self.create_chart, ticker, indicators, df, strategy
371
+ )
372
+
373
+
374
+ # --- Telegram Bot Integration Functions ---
375
+
376
+ async def generate_grid_message(ticker: str, capital: float = 10000,
377
+ strategy: str = "medium") -> Tuple[str, Optional[bytes]]:
378
+ """
379
+ Grid message and chart generation for Telegram bot
380
+
381
+ Returns:
382
+ Tuple[str, Optional[bytes]]: (message, chart in bytes)
383
+ """
384
+ generator = AsyncTradingGridGenerator()
385
+ try:
386
+ indicators, df, metrics = await generator.generate_grid_async(ticker, capital, strategy)
387
+ message = generator.format_telegram_message(ticker, indicators, df, metrics, strategy)
388
+ chart = await generator.create_grid_chart_async(ticker, indicators, df, strategy)
389
+ return message, chart
390
+ except Exception as e:
391
+ error_message = f"❌ <b>Grid generation error</b>\n\nπŸ” Ticker: <code>{ticker}</code>\nπŸ’« Error: <i>{str(e)}</i>"
392
+ logger.error(f"Grid generation error for {ticker}: {e}", exc_info=True)
393
+ return error_message, None
394
+
395
+
396
+ async def compare_strategies_message(ticker: str, capital: float = 10000,
397
+ strategies: List[str] = None) -> str:
398
+ """
399
+ Strategy comparison for Telegram bot
400
+
401
+ Returns:
402
+ str: Formatted message for Telegram
403
+ """
404
+ if strategies is None:
405
+ strategies = ["conservative", "medium", "aggressive"]
406
+ generator = AsyncTradingGridGenerator()
407
+ comparison_data = []
408
+ try:
409
+ for strategy in strategies:
410
+ try:
411
+ indicators, df, metrics = await generator.generate_grid_async(ticker, capital, strategy)
412
+ comparison_data.append({
413
+ 'strategy': strategy,
414
+ 'orders': metrics['number_of_orders'],
415
+ 'max_drawdown': metrics['max_drawdown'],
416
+ 'potential_profit': metrics['potential_profit'],
417
+ 'profit_margin': metrics['profit_margin'],
418
+ })
419
+ except Exception as e:
420
+ logger.error(f"Error for strategy {strategy}: {e}")
421
+ continue
422
+ if comparison_data:
423
+ return generator.format_comparison_message(ticker, comparison_data)
424
+ else:
425
+ return f"❌ <b>Error</b>\n\nCould not retrieve data for {ticker.upper()}"
426
+ except Exception as e:
427
+ logger.error(f"Strategy comparison error for {ticker}: {e}")
428
+ return f"❌ <b>Comparison error</b>\n\nπŸ” Ticker: <code>{ticker}</code>\nπŸ’« Error: <i>{str(e)}</i>"
429
+
430
+
431
+ async def get_available_strategies() -> Dict[str, Dict]:
432
+ """Get list of available strategies"""
433
+ generator = AsyncTradingGridGenerator()
434
+ return generator.strategies
435
+
436
+
437
+ async def validate_ticker(ticker: str) -> bool:
438
+ """Ticker validation"""
439
+ generator = AsyncTradingGridGenerator()
440
+ try:
441
+ await generator.get_stock_data_async(ticker, period="5d")
442
+ return True
443
+ except:
444
+ return False
445
+
446
+
447
+ # --- Example usage for Telegram bot ---
448
+ class TelegramGridBot:
449
+ """
450
+ Example class for Telegram bot integration
451
+ """
452
+
453
+ def __init__(self):
454
+ self.generator = AsyncTradingGridGenerator()
455
+
456
+ async def handle_grid_command(self, ticker: str, capital: str = "10000",
457
+ strategy: str = "medium") -> Tuple[str, Optional[bytes]]:
458
+ """Grid generation command handler"""
459
+ try:
460
+ capital_float = float(capital)
461
+ if capital_float <= 0:
462
+ return "❌ Capital must be a positive number", None
463
+
464
+ return await generate_grid_message(ticker.upper(), capital_float, strategy.lower())
465
+ except ValueError:
466
+ return "❌ Invalid capital format. Enter a number.", None
467
+ except Exception as e:
468
+ return f"❌ An error occurred: {str(e)}", None
469
+
470
+ async def handle_compare_command(self, ticker: str, capital: str = "10000") -> str:
471
+ """Strategy comparison command handler"""
472
+ try:
473
+ capital_float = float(capital)
474
+ if capital_float <= 0:
475
+ return "❌ Capital must be a positive number"
476
+ return await compare_strategies_message(ticker.upper(), capital_float)
477
+ except ValueError:
478
+ return "❌ Invalid capital format. Enter a number."
479
+ except Exception as e:
480
+ return f"❌ An error occurred: {str(e)}"
481
+
482
+ async def handle_strategies_command(self) -> str:
483
+ """Available strategies list"""
484
+ strategies = await get_available_strategies()
485
+ message = "πŸ“‹ <b>AVAILABLE STRATEGIES:</b>\n\n"
486
+ for name, info in strategies.items():
487
+ message += f"""{info['emoji']} <b>{name.upper()}</b>
488
+ {info['description']}
489
+ πŸ“ˆ Growth: <code>{info['growth']}</code>
490
+ πŸ“‰ Max drop: <code>{info['drop_pct']:.0%}</code>
491
+ 🎯 Levels: <code>{info['levels']}</code>
492
+ """
493
+ return message
494
+
495
+
496
+ # --- Example execution ---
497
+ async def main():
498
+ """Usage example"""
499
+ ticker = "NVDA"
500
+ # Grid generation
501
+ message, chart = await generate_grid_message(ticker, 10000, "aggressive")
502
+ with open('chart.png', 'wb') as f:
503
+ f.write(chart)
504
+ print("Message:", message)
505
+ print("Chart generated:", chart is not None)
506
+
507
+ # Strategy comparison
508
+ comparison = await compare_strategies_message(ticker, 10000)
509
+ print("\nComparison:", comparison)
510
+
511
+
512
+ if __name__ == "__main__":
513
+ asyncio.run(main())