66"""
77
88import aiohttp
9+ import time
910from typing import Dict , List , Optional
11+
1012from settings import ENDPOINT_HOST , ENDPOINTS , RETRY_SETTINGS , TCS_RO_TOKEN
1113from tinkoff .invest .retrying .aio .client import AsyncRetryingClient
1214from tinkoff .invest .utils import quotation_to_decimal
15+ from spread_logging import log_api_call , LoggingContext
1316
1417
1518class 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
6694async 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
186291async 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