Skip to content

Commit 9b6759e

Browse files
committed
task 4
1 parent 5fa7a2d commit 9b6759e

File tree

10 files changed

+3337
-36
lines changed

10 files changed

+3337
-36
lines changed

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,60 +23,60 @@
2323
- Implement ticker parsing and validation from command arguments
2424
- _Requirements: 1.1, 1.4_
2525

26-
- [ ] 2. Implement ticker validation and API integration
26+
- [x] 2. Implement ticker validation and API integration
2727

2828
- Create API integration module for ticker validation
2929
- Add price fetching functionality using existing Tinkoff integration
3030
- Implement duplicate spread detection
3131
- _Requirements: 1.2, 1.3, 7.5_
3232

33-
- [ ] 2.1 Create API integration module for spread creation
33+
- [x] 2.1 Create API integration module for spread creation
3434

3535
- Write tests for ticker validation and price fetching functions
3636
- Write `bot/spread_api.py` with ticker validation functions
3737
- Implement validate_ticker function using existing ticker endpoint
3838
- Add get_current_prices function using existing price fetching logic
3939
- _Requirements: 1.2, 1.3_
4040

41-
- [ ] 2.2 Add spread creation API endpoint integration
41+
- [x] 2.2 Add spread creation API endpoint integration
4242

4343
- Write tests for spread creation and duplicate detection functions
4444
- Implement create_spread function to POST to Django spreads endpoint
4545
- Add check_duplicate_spread function to detect existing spreads
4646
- Handle API errors and connection issues gracefully
4747
- _Requirements: 6.1, 6.2, 7.4_
4848

49-
- [ ] 3. Implement market-neutral price calculation logic
49+
- [x] 3. Implement market-neutral price calculation logic
5050

5151
- Create price calculator module with ratio calculation
5252
- Implement spread price calculations for different asset type combinations
5353
- Add market-neutral explanation generation
5454
- _Requirements: 2.1, 2.2, 2.3_
5555

56-
- [ ] 3.1 Create spread pricing calculation module
56+
- [x] 3.1 Create spread pricing calculation module
5757

5858
- Write tests for spread price calculations and ratio calculations for different asset combinations
5959
- Write `bot/spread_pricing.py` with calculate_spread_price function
6060
- Implement calculate_ratio function for stock-future and future-future pairs
6161
- Add get_market_neutral_explanation function for user display
6262
- _Requirements: 2.1, 2.2, 2.3_
6363

64-
- [ ] 3.2 Integrate price calculations with existing Tinkoff API
64+
- [x] 3.2 Integrate price calculations with existing Tinkoff API
6565

6666
- Write tests for price integration and error handling scenarios
6767
- Use existing get_current_prices_by_uid function for market data
6868
- Implement bid/ask price fetching for spread calculations
6969
- Add price unavailable handling and fallback logic
7070
- _Requirements: 2.5, 2.6_
7171

72-
- [ ] 4. Create interactive menu system with inline keyboards
72+
- [x] 4. Create interactive menu system with inline keyboards
7373

7474
- Implement inline keyboard generators for each step
7575
- Create callback query handlers for menu interactions
7676
- Add menu navigation and state transitions
7777
- _Requirements: 2.4, 3.1, 4.1, 5.1_
7878

79-
- [ ] 4.1 Create inline keyboard menu generators
79+
- [x] 4.1 Create inline keyboard menu generators
8080

8181
- Write tests for menu generation functions and keyboard layouts
8282
- Write `bot/spread_menu.py` with menu generation functions
@@ -85,7 +85,7 @@
8585
- Create generate_confirmation_menu for final review
8686
- _Requirements: 2.4, 3.1, 4.1, 5.1_
8787

88-
- [ ] 4.2 Implement callback query handlers
88+
- [x] 4.2 Implement callback query handlers
8989

9090
- Write tests for callback handling and state transitions
9191
- Add callback query handler to `bot/spread_creator.py`

bot/spread_api.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
"""
2+
API integration module for spread creation.
3+
4+
This module provides functions for ticker validation and price fetching
5+
using the existing Tinkoff integration and Django API endpoints.
6+
"""
7+
8+
import aiohttp
9+
from typing import Dict, List, Optional
10+
from settings import ENDPOINT_HOST, ENDPOINTS, RETRY_SETTINGS, TCS_RO_TOKEN
11+
from tinkoff.invest.retrying.aio.client import AsyncRetryingClient
12+
from tinkoff.invest.utils import quotation_to_decimal
13+
14+
15+
class APIError(Exception):
16+
"""Base exception for API-related errors."""
17+
pass
18+
19+
20+
class TickerNotFoundError(APIError):
21+
"""Raised when a ticker is not found in the database."""
22+
pass
23+
24+
25+
class PriceUnavailableError(APIError):
26+
"""Raised when price data is unavailable."""
27+
pass
28+
29+
30+
async def validate_ticker(ticker: str) -> Dict:
31+
"""
32+
Validate ticker existence and API trading availability.
33+
34+
Args:
35+
ticker: The ticker symbol to validate
36+
37+
Returns:
38+
Dict containing ticker information including figi, lot size, etc.
39+
40+
Raises:
41+
TickerNotFoundError: If ticker doesn't exist
42+
APIError: If API trading is not available or connection fails
43+
"""
44+
url = ENDPOINT_HOST + ENDPOINTS['ticker'] + ticker + '/'
45+
46+
try:
47+
async with aiohttp.ClientSession() as session:
48+
async with session.get(url) as response:
49+
if response.status == 404:
50+
raise TickerNotFoundError(f"Ticker {ticker} not found")
51+
if response.status != 200:
52+
raise APIError(f"Server error: {response.status}")
53+
54+
data = await response.json()
55+
56+
# Check if API trading is available
57+
if not data.get('api_trading_available', False):
58+
raise APIError(f"API trading not available for {ticker}")
59+
60+
return data
61+
62+
except aiohttp.ClientError as e:
63+
raise APIError(f"Unable to connect to trading system: {str(e)}")
64+
65+
66+
async def get_current_prices(
67+
figis: List[str],
68+
include_order_book: bool = False) -> Dict[str, Dict]:
69+
"""
70+
Fetch current market prices for given FIGIs.
71+
72+
Args:
73+
figis: List of FIGI identifiers
74+
include_order_book: Whether to include bid/ask prices from order book
75+
76+
Returns:
77+
Dict mapping FIGI to price data containing:
78+
- last_price: Latest trade price
79+
- bid_price: Best bid price (if include_order_book=True)
80+
- ask_price: Best ask price (if include_order_book=True)
81+
82+
Raises:
83+
PriceUnavailableError: If no price data is available
84+
APIError: If connection fails
85+
"""
86+
try:
87+
async with AsyncRetryingClient(TCS_RO_TOKEN, RETRY_SETTINGS) as client:
88+
# Get last prices
89+
response = await client.market_data.get_last_prices(figi=figis)
90+
91+
if not response.last_prices:
92+
raise PriceUnavailableError("No price data available")
93+
94+
# Build price data dictionary
95+
price_data = {}
96+
for price_item in response.last_prices:
97+
figi = price_item.figi
98+
price_data[figi] = {
99+
'figi': figi,
100+
'last_price': quotation_to_decimal(price_item.price)
101+
}
102+
103+
# Optionally fetch order book data for bid/ask prices
104+
if include_order_book:
105+
await _add_order_book_data(client, figis, price_data)
106+
107+
return price_data
108+
109+
except Exception as e:
110+
if isinstance(e, (PriceUnavailableError, APIError)):
111+
raise
112+
raise APIError(f"Unable to fetch prices: {str(e)}")
113+
114+
115+
async def _add_order_book_data(client, figis: List[str], price_data: Dict):
116+
"""Helper function to add order book data to price data."""
117+
for figi in figis:
118+
if figi in price_data:
119+
try:
120+
order_book = await client.market_data.get_order_book(
121+
figi=figi, depth=1
122+
)
123+
124+
if order_book.bids:
125+
price_data[figi]['bid_price'] = quotation_to_decimal(
126+
order_book.bids[0].price
127+
)
128+
129+
if order_book.asks:
130+
price_data[figi]['ask_price'] = quotation_to_decimal(
131+
order_book.asks[0].price
132+
)
133+
134+
except Exception:
135+
# If order book fails, continue without bid/ask data
136+
pass
137+
138+
139+
async def create_spread(spread_data: Dict) -> int:
140+
"""
141+
Create a new spread in the database.
142+
143+
Args:
144+
spread_data: Dictionary containing spread parameters:
145+
- far_leg_figi: FIGI of the far leg
146+
- near_leg_figi: FIGI of the near leg
147+
- sell: Boolean indicating sell direction
148+
- price: Spread price
149+
- amount: Amount to trade
150+
- editable_ratio: Optional ratio override
151+
152+
Returns:
153+
The ID of the created spread
154+
155+
Raises:
156+
APIError: If creation fails or connection issues occur
157+
"""
158+
url = ENDPOINT_HOST + ENDPOINTS['spreads']
159+
160+
# Prepare payload for Django API
161+
payload = {
162+
'far_leg_figi': spread_data['far_leg_figi'],
163+
'near_leg_figi': spread_data['near_leg_figi'],
164+
'sell': spread_data['sell'],
165+
'price': spread_data['price'],
166+
'amount': spread_data['amount']
167+
}
168+
169+
# Add optional ratio if provided
170+
if 'editable_ratio' in spread_data and spread_data['editable_ratio']:
171+
payload['editable_ratio'] = spread_data['editable_ratio']
172+
173+
try:
174+
async with aiohttp.ClientSession() as session:
175+
async with session.post(url, json=payload) as response:
176+
if response.status == 201:
177+
data = await response.json()
178+
return data['id']
179+
error_text = await response.text()
180+
raise APIError(f"Error creating spread: {error_text}")
181+
182+
except aiohttp.ClientError as e:
183+
raise APIError(f"Unable to connect to trading system: {str(e)}")
184+
185+
186+
async def check_duplicate_spread(
187+
far_leg_figi: str,
188+
near_leg_figi: str) -> Optional[int]:
189+
"""
190+
Check if a spread with the same legs already exists.
191+
192+
Args:
193+
far_leg_figi: FIGI of the far leg
194+
near_leg_figi: FIGI of the near leg
195+
196+
Returns:
197+
The ID of the existing spread if found, None otherwise
198+
199+
Raises:
200+
APIError: If connection fails or server error occurs
201+
"""
202+
url = ENDPOINT_HOST + ENDPOINTS['spreads']
203+
204+
try:
205+
async with aiohttp.ClientSession() as session:
206+
async with session.get(url) as response:
207+
if response.status != 200:
208+
raise APIError(f"Server error: {response.status}")
209+
210+
spreads = await response.json()
211+
212+
# Check for duplicate spread with same legs
213+
for spread in spreads:
214+
far_match = spread['far_leg']['figi'] == far_leg_figi
215+
near_match = spread['near_leg']['figi'] == near_leg_figi
216+
if far_match and near_match:
217+
return spread['id']
218+
219+
return None
220+
221+
except aiohttp.ClientError as e:
222+
raise APIError(f"Unable to connect to trading system: {str(e)}")

0 commit comments

Comments
 (0)