Skip to content

Commit cf772dd

Browse files
committed
first task ready
1 parent 4eea215 commit cf772dd

14 files changed

Lines changed: 4491 additions & 218 deletions

.kiro/specs/telegram-spread-management/tasks.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,68 +117,68 @@
117117
- Create user-friendly error messages with suggested corrections
118118
- _Requirements: 7.1, 7.3, 7.4_
119119

120-
- [ ] 6. Integrate spread creation with Django backend
120+
- [x] 6. Integrate spread creation with Django backend
121121

122122
- Connect menu system to Django API for spread persistence
123123
- Implement SpreadStats creation and association
124124
- Add success confirmation and spread activation
125125
- _Requirements: 5.5, 6.1, 6.2, 6.3_
126126

127-
- [ ] 6.1 Implement Django API integration for spread creation
127+
- [x] 6.1 Implement Django API integration for spread creation
128128

129129
- Write tests for spread creation payload generation and API integration
130130
- Create spread creation payload from menu state
131131
- POST spread data to Django spreads endpoint
132132
- Handle SpreadStats creation automatically in Django
133133
- _Requirements: 5.5, 6.1, 6.2_
134134

135-
- [ ] 6.2 Add spread activation and confirmation
135+
- [x] 6.2 Add spread activation and confirmation
136136

137137
- Write tests for spread activation and confirmation flow
138138
- Set active=True by default for new spreads
139139
- Return spread ID and confirmation to user
140140
- Clear session state after successful creation
141141
- _Requirements: 6.3, 6.4, 5.6_
142142

143-
- [ ] 7. Add logging, monitoring and session cleanup
143+
- [x] 7. Add logging, monitoring and session cleanup
144144

145145
- Implement comprehensive logging for all operations
146146
- Add session timeout and automatic cleanup
147147
- Create monitoring for spread creation activity
148148
- _Requirements: 7.1, 7.2, 7.3_
149149

150-
- [ ] 7.1 Add comprehensive logging system
150+
- [x] 7.1 Add comprehensive logging system
151151

152152
- Write tests for logging functionality and log message formats
153153
- Log all spread creation attempts with user and parameters
154154
- Add error logging with full context for troubleshooting
155155
- Implement success logging with spread details
156156
- _Requirements: 7.1, 7.2, 7.3_
157157

158-
- [ ] 7.2 Create session cleanup and monitoring
158+
- [x] 7.2 Create session cleanup and monitoring
159159

160160
- Write tests for session timeout and cleanup functionality
161161
- Implement automatic session timeout (30 minutes)
162162
- Add periodic cleanup of stale sessions
163163
- Create monitoring dashboard for spread creation metrics
164164
- _Requirements: 7.1, 7.3_
165165

166-
- [ ] 8. Integration testing and final wiring
166+
- [x] 8. Integration testing and final wiring
167167

168168
- Connect all modules and test complete user flow
169169
- Add the addspread command to bot command routing
170170
- Verify integration with existing spreads trading functionality
171171
- _Requirements: 6.5_
172172

173-
- [ ] 8.1 Wire up complete spread creation flow
173+
- [x] 8.1 Wire up complete spread creation flow
174174

175175
- Write integration tests for complete user journey from command to spread creation
176176
- Register addspread command handler in bot dispatcher
177177
- Connect all modules: state management, API integration, menus, pricing
178178
- Test complete user journey from command to spread creation
179179
- _Requirements: 6.5_
180180

181-
- [ ] 8.2 Verify integration with existing spread trading system
181+
- [x] 8.2 Verify integration with existing spread trading system
182182
- Write integration tests for spread trading system compatibility
183183
- Ensure newly created spreads appear in spreads command
184184
- Test that created spreads are properly formatted for trading logic

bot/spread_api.py

Lines changed: 137 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
"""
77

88
import aiohttp
9+
import time
910
from typing import Dict, List, Optional
11+
1012
from settings import ENDPOINT_HOST, ENDPOINTS, RETRY_SETTINGS, TCS_RO_TOKEN
1113
from tinkoff.invest.retrying.aio.client import AsyncRetryingClient
1214
from tinkoff.invest.utils import quotation_to_decimal
15+
from spread_logging import log_api_call, LoggingContext
1316

1417

1518
class APIError(Exception):
@@ -27,12 +30,13 @@ class PriceUnavailableError(APIError):
2730
pass
2831

2932

30-
async def validate_ticker(ticker: str) -> Dict:
33+
async def validate_ticker(ticker: str, user_id: Optional[int] = None) -> Dict:
3134
"""
3235
Validate ticker existence and API trading availability.
3336
3437
Args:
3538
ticker: The ticker symbol to validate
39+
user_id: Optional user ID for logging
3640
3741
Returns:
3842
Dict containing ticker information including figi, lot size, etc.
@@ -42,10 +46,24 @@ async def validate_ticker(ticker: str) -> Dict:
4246
APIError: If API trading is not available or connection fails
4347
"""
4448
url = ENDPOINT_HOST + ENDPOINTS['ticker'] + ticker + '/'
49+
start_time = time.time()
4550

4651
try:
4752
async with aiohttp.ClientSession() as session:
4853
async with session.get(url) as response:
54+
duration_ms = (time.time() - start_time) * 1000
55+
56+
# Log API call
57+
if user_id:
58+
log_api_call(
59+
user_id=user_id,
60+
endpoint='validate_ticker',
61+
method='GET',
62+
params={'ticker': ticker},
63+
response_status=response.status,
64+
duration_ms=duration_ms
65+
)
66+
4967
if response.status == 404:
5068
raise TickerNotFoundError(f"Ticker {ticker} not found")
5169
if response.status != 200:
@@ -60,18 +78,30 @@ async def validate_ticker(ticker: str) -> Dict:
6078
return data
6179

6280
except aiohttp.ClientError as e:
81+
duration_ms = (time.time() - start_time) * 1000
82+
if user_id:
83+
log_api_call(
84+
user_id=user_id,
85+
endpoint='validate_ticker',
86+
method='GET',
87+
params={'ticker': ticker},
88+
response_status=None,
89+
duration_ms=duration_ms
90+
)
6391
raise APIError(f"Unable to connect to trading system: {str(e)}")
6492

6593

6694
async def get_current_prices(
6795
figis: List[str],
68-
include_order_book: bool = False) -> Dict[str, Dict]:
96+
include_order_book: bool = False,
97+
user_id: Optional[int] = None) -> Dict[str, Dict]:
6998
"""
7099
Fetch current market prices for given FIGIs.
71100
72101
Args:
73102
figis: List of FIGI identifiers
74103
include_order_book: Whether to include bid/ask prices from order book
104+
user_id: Optional user ID for logging
75105
76106
Returns:
77107
Dict mapping FIGI to price data containing:
@@ -83,10 +113,25 @@ async def get_current_prices(
83113
PriceUnavailableError: If no price data is available
84114
APIError: If connection fails
85115
"""
116+
start_time = time.time()
117+
86118
try:
87119
async with AsyncRetryingClient(TCS_RO_TOKEN, RETRY_SETTINGS) as client:
88120
# Get last prices
89121
response = await client.market_data.get_last_prices(figi=figis)
122+
123+
duration_ms = (time.time() - start_time) * 1000
124+
125+
# Log API call
126+
if user_id:
127+
log_api_call(
128+
user_id=user_id,
129+
endpoint='get_last_prices',
130+
method='GRPC',
131+
params={'figis': figis, 'include_order_book': include_order_book},
132+
response_status=200 if response.last_prices else 404,
133+
duration_ms=duration_ms
134+
)
90135

91136
if not response.last_prices:
92137
raise PriceUnavailableError("No price data available")
@@ -102,24 +147,49 @@ async def get_current_prices(
102147

103148
# Optionally fetch order book data for bid/ask prices
104149
if include_order_book:
105-
await _add_order_book_data(client, figis, price_data)
150+
await _add_order_book_data(client, figis, price_data, user_id)
106151

107152
return price_data
108153

109154
except Exception as e:
155+
duration_ms = (time.time() - start_time) * 1000
156+
if user_id:
157+
log_api_call(
158+
user_id=user_id,
159+
endpoint='get_last_prices',
160+
method='GRPC',
161+
params={'figis': figis},
162+
response_status=None,
163+
duration_ms=duration_ms
164+
)
165+
110166
if isinstance(e, (PriceUnavailableError, APIError)):
111167
raise
112168
raise APIError(f"Unable to fetch prices: {str(e)}")
113169

114170

115-
async def _add_order_book_data(client, figis: List[str], price_data: Dict):
171+
async def _add_order_book_data(client, figis: List[str], price_data: Dict, user_id: Optional[int] = None):
116172
"""Helper function to add order book data to price data."""
117173
for figi in figis:
118174
if figi in price_data:
175+
start_time = time.time()
119176
try:
120177
order_book = await client.market_data.get_order_book(
121178
figi=figi, depth=1
122179
)
180+
181+
duration_ms = (time.time() - start_time) * 1000
182+
183+
# Log order book API call
184+
if user_id:
185+
log_api_call(
186+
user_id=user_id,
187+
endpoint='get_order_book',
188+
method='GRPC',
189+
params={'figi': figi, 'depth': 1},
190+
response_status=200,
191+
duration_ms=duration_ms
192+
)
123193

124194
if order_book.bids:
125195
price_data[figi]['bid_price'] = quotation_to_decimal(
@@ -132,11 +202,21 @@ async def _add_order_book_data(client, figis: List[str], price_data: Dict):
132202
)
133203

134204
except Exception:
205+
duration_ms = (time.time() - start_time) * 1000
206+
if user_id:
207+
log_api_call(
208+
user_id=user_id,
209+
endpoint='get_order_book',
210+
method='GRPC',
211+
params={'figi': figi, 'depth': 1},
212+
response_status=None,
213+
duration_ms=duration_ms
214+
)
135215
# If order book fails, continue without bid/ask data
136216
pass
137217

138218

139-
async def create_spread(spread_data: Dict) -> int:
219+
async def create_spread(spread_data: Dict, user_id: Optional[int] = None) -> int:
140220
"""
141221
Create a new spread in the database.
142222
@@ -148,6 +228,7 @@ async def create_spread(spread_data: Dict) -> int:
148228
- price: Spread price
149229
- amount: Amount to trade
150230
- editable_ratio: Optional ratio override
231+
user_id: Optional user ID for logging
151232
152233
Returns:
153234
The ID of the created spread
@@ -156,6 +237,7 @@ async def create_spread(spread_data: Dict) -> int:
156237
APIError: If creation fails or connection issues occur
157238
"""
158239
url = ENDPOINT_HOST + ENDPOINTS['spreads']
240+
start_time = time.time()
159241

160242
# Prepare payload for Django API
161243
payload = {
@@ -173,25 +255,50 @@ async def create_spread(spread_data: Dict) -> int:
173255
try:
174256
async with aiohttp.ClientSession() as session:
175257
async with session.post(url, json=payload) as response:
258+
duration_ms = (time.time() - start_time) * 1000
259+
260+
# Log API call
261+
if user_id:
262+
log_api_call(
263+
user_id=user_id,
264+
endpoint='create_spread',
265+
method='POST',
266+
params=payload,
267+
response_status=response.status,
268+
duration_ms=duration_ms
269+
)
270+
176271
if response.status == 201:
177272
data = await response.json()
178273
return data['id']
179274
error_text = await response.text()
180275
raise APIError(f"Error creating spread: {error_text}")
181276

182277
except aiohttp.ClientError as e:
278+
duration_ms = (time.time() - start_time) * 1000
279+
if user_id:
280+
log_api_call(
281+
user_id=user_id,
282+
endpoint='create_spread',
283+
method='POST',
284+
params=payload,
285+
response_status=None,
286+
duration_ms=duration_ms
287+
)
183288
raise APIError(f"Unable to connect to trading system: {str(e)}")
184289

185290

186291
async def check_duplicate_spread(
187292
far_leg_figi: str,
188-
near_leg_figi: str) -> Optional[int]:
293+
near_leg_figi: str,
294+
user_id: Optional[int] = None) -> Optional[int]:
189295
"""
190296
Check if a spread with the same legs already exists.
191297
192298
Args:
193299
far_leg_figi: FIGI of the far leg
194300
near_leg_figi: FIGI of the near leg
301+
user_id: Optional user ID for logging
195302
196303
Returns:
197304
The ID of the existing spread if found, None otherwise
@@ -200,10 +307,24 @@ async def check_duplicate_spread(
200307
APIError: If connection fails or server error occurs
201308
"""
202309
url = ENDPOINT_HOST + ENDPOINTS['spreads']
310+
start_time = time.time()
203311

204312
try:
205313
async with aiohttp.ClientSession() as session:
206314
async with session.get(url) as response:
315+
duration_ms = (time.time() - start_time) * 1000
316+
317+
# Log API call
318+
if user_id:
319+
log_api_call(
320+
user_id=user_id,
321+
endpoint='check_duplicate_spread',
322+
method='GET',
323+
params={'far_leg_figi': far_leg_figi, 'near_leg_figi': near_leg_figi},
324+
response_status=response.status,
325+
duration_ms=duration_ms
326+
)
327+
207328
if response.status != 200:
208329
raise APIError(f"Server error: {response.status}")
209330

@@ -219,4 +340,14 @@ async def check_duplicate_spread(
219340
return None
220341

221342
except aiohttp.ClientError as e:
343+
duration_ms = (time.time() - start_time) * 1000
344+
if user_id:
345+
log_api_call(
346+
user_id=user_id,
347+
endpoint='check_duplicate_spread',
348+
method='GET',
349+
params={'far_leg_figi': far_leg_figi, 'near_leg_figi': near_leg_figi},
350+
response_status=None,
351+
duration_ms=duration_ms
352+
)
222353
raise APIError(f"Unable to connect to trading system: {str(e)}")

0 commit comments

Comments
 (0)