Dmitry Beresnev
commited on
Commit
Β·
7c9bc72
1
Parent(s):
7406726
add async trading grid calculator
Browse files- .gitignore +1 -0
- src/services/async_trading_grid_calculator.py +513 -0
.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())
|