avanza-mcp 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- avanza_mcp/__init__.py +29 -0
- avanza_mcp/client/__init__.py +25 -0
- avanza_mcp/client/base.py +375 -0
- avanza_mcp/client/endpoints.py +41 -0
- avanza_mcp/client/exceptions.py +66 -0
- avanza_mcp/models/__init__.py +41 -0
- avanza_mcp/models/common.py +50 -0
- avanza_mcp/models/fund.py +246 -0
- avanza_mcp/models/search.py +123 -0
- avanza_mcp/models/stock.py +268 -0
- avanza_mcp/prompts/__init__.py +6 -0
- avanza_mcp/prompts/analysis.py +116 -0
- avanza_mcp/resources/__init__.py +6 -0
- avanza_mcp/resources/instruments.py +127 -0
- avanza_mcp/services/__init__.py +6 -0
- avanza_mcp/services/market_data_service.py +298 -0
- avanza_mcp/services/search_service.py +64 -0
- avanza_mcp/tools/__init__.py +8 -0
- avanza_mcp/tools/funds.py +267 -0
- avanza_mcp/tools/market_data.py +508 -0
- avanza_mcp/tools/search.py +119 -0
- avanza_mcp-1.0.0.dist-info/METADATA +99 -0
- avanza_mcp-1.0.0.dist-info/RECORD +26 -0
- avanza_mcp-1.0.0.dist-info/WHEEL +4 -0
- avanza_mcp-1.0.0.dist-info/entry_points.txt +2 -0
- avanza_mcp-1.0.0.dist-info/licenses/LICENSE.md +21 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
"""Market data MCP tools for stocks, funds, and other instruments."""
|
|
2
|
+
|
|
3
|
+
from fastmcp import Context
|
|
4
|
+
|
|
5
|
+
from .. import mcp
|
|
6
|
+
from ..client import AvanzaClient
|
|
7
|
+
from ..services import MarketDataService
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@mcp.tool()
|
|
11
|
+
async def get_stock_info(
|
|
12
|
+
ctx: Context,
|
|
13
|
+
instrument_id: str,
|
|
14
|
+
) -> dict:
|
|
15
|
+
"""Get detailed information about a specific stock.
|
|
16
|
+
|
|
17
|
+
Provides comprehensive stock data including current price, trading volume,
|
|
18
|
+
market capitalization, valuation metrics (P/E, P/B ratios), dividend yield,
|
|
19
|
+
and company description.
|
|
20
|
+
|
|
21
|
+
Use search_instruments() first to find the instrument_id for a stock.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
ctx: MCP context for logging
|
|
25
|
+
instrument_id: Avanza instrument ID from search results
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Detailed stock information including:
|
|
29
|
+
- quote: Current price, change, volume, trading data
|
|
30
|
+
- company: Description, CEO, sector, market cap
|
|
31
|
+
- key_ratios: P/E ratio, dividend yield, volatility, beta
|
|
32
|
+
- Trading metadata: market, tradeable status
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
Get info for Volvo B (ID from search):
|
|
36
|
+
>>> get_stock_info(instrument_id="5479")
|
|
37
|
+
"""
|
|
38
|
+
ctx.info(f"Fetching stock info for ID: {instrument_id}")
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
async with AvanzaClient() as client:
|
|
42
|
+
service = MarketDataService(client)
|
|
43
|
+
stock_info = await service.get_stock_info(instrument_id)
|
|
44
|
+
|
|
45
|
+
ctx.info(f"Retrieved info for: {stock_info.name}")
|
|
46
|
+
return stock_info.model_dump(by_alias=True, exclude_none=True)
|
|
47
|
+
|
|
48
|
+
except Exception as e:
|
|
49
|
+
ctx.error(f"Failed to fetch stock info: {str(e)}")
|
|
50
|
+
raise
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@mcp.tool()
|
|
54
|
+
async def get_fund_info(
|
|
55
|
+
ctx: Context,
|
|
56
|
+
instrument_id: str,
|
|
57
|
+
) -> dict:
|
|
58
|
+
"""Get detailed information about a mutual fund.
|
|
59
|
+
|
|
60
|
+
Provides comprehensive fund data including NAV (Net Asset Value), performance
|
|
61
|
+
over various time periods, risk metrics, fees, and fund characteristics.
|
|
62
|
+
|
|
63
|
+
Use search_instruments() with instrument_type="fund" to find fund IDs.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
ctx: MCP context for logging
|
|
67
|
+
instrument_id: Avanza fund ID from search results
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Detailed fund information including:
|
|
71
|
+
- Basic info: name, ISIN, description, NAV
|
|
72
|
+
- Performance: returns over 1w, 1m, 3m, YTD, 1y, 3y, 5y, 10y
|
|
73
|
+
- Risk: risk level (1-7), standard deviation, Sharpe ratio
|
|
74
|
+
- Fees: ongoing charges, entry/exit fees
|
|
75
|
+
- Characteristics: fund company, type, category, AUM
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
Get info for a specific fund:
|
|
79
|
+
>>> get_fund_info(instrument_id="12345")
|
|
80
|
+
"""
|
|
81
|
+
ctx.info(f"Fetching fund info for ID: {instrument_id}")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
async with AvanzaClient() as client:
|
|
85
|
+
service = MarketDataService(client)
|
|
86
|
+
fund_info = await service.get_fund_info(instrument_id)
|
|
87
|
+
|
|
88
|
+
ctx.info(f"Retrieved info for: {fund_info.name}")
|
|
89
|
+
return fund_info.model_dump(by_alias=True, exclude_none=True)
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
ctx.error(f"Failed to fetch fund info: {str(e)}")
|
|
93
|
+
raise
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@mcp.tool()
|
|
97
|
+
async def get_stock_chart(
|
|
98
|
+
ctx: Context,
|
|
99
|
+
instrument_id: str,
|
|
100
|
+
time_period: str = "one_year",
|
|
101
|
+
) -> dict:
|
|
102
|
+
"""Get historical price chart data (OHLC) for a stock.
|
|
103
|
+
|
|
104
|
+
Retrieves time series price data with Open, High, Low, Close values and volume.
|
|
105
|
+
Perfect for charting and technical analysis.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
ctx: MCP context for logging
|
|
109
|
+
instrument_id: Avanza instrument ID
|
|
110
|
+
time_period: Time period for chart data. Options:
|
|
111
|
+
- "today": Intraday data for today
|
|
112
|
+
- "one_week": Past week
|
|
113
|
+
- "one_month": Past month
|
|
114
|
+
- "three_months": Past 3 months
|
|
115
|
+
- "this_year": Year to date
|
|
116
|
+
- "one_year": Past year (default)
|
|
117
|
+
- "three_years": Past 3 years
|
|
118
|
+
- "five_years": Past 5 years
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Chart data with OHLC values:
|
|
122
|
+
- ohlc: Array of data points with timestamp, open, high, low, close, volume
|
|
123
|
+
- metadata: Chart metadata
|
|
124
|
+
- from/to: Time range
|
|
125
|
+
- previousClosingPrice: Previous closing price
|
|
126
|
+
|
|
127
|
+
Examples:
|
|
128
|
+
Get 1 year daily data:
|
|
129
|
+
>>> get_stock_chart(instrument_id="5269", time_period="one_year")
|
|
130
|
+
|
|
131
|
+
Get past week data:
|
|
132
|
+
>>> get_stock_chart(instrument_id="5269", time_period="one_week")
|
|
133
|
+
"""
|
|
134
|
+
ctx.info(f"Fetching chart data for ID: {instrument_id} (time_period={time_period})")
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
async with AvanzaClient() as client:
|
|
138
|
+
service = MarketDataService(client)
|
|
139
|
+
chart_data = await service.get_chart_data(
|
|
140
|
+
instrument_id=instrument_id,
|
|
141
|
+
time_period=time_period,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
data_points = len(chart_data.ohlc)
|
|
145
|
+
ctx.info(f"Retrieved {data_points} OHLC data points")
|
|
146
|
+
return chart_data.model_dump(by_alias=True, exclude_none=True)
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
ctx.error(f"Failed to fetch chart data: {str(e)}")
|
|
150
|
+
raise
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@mcp.tool()
|
|
154
|
+
async def get_orderbook(
|
|
155
|
+
ctx: Context,
|
|
156
|
+
instrument_id: str,
|
|
157
|
+
) -> dict:
|
|
158
|
+
"""Get real-time order book depth for an instrument.
|
|
159
|
+
|
|
160
|
+
Shows current buy and sell orders with prices and volumes at each level.
|
|
161
|
+
Useful for understanding market depth, liquidity, and bid-ask spread.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
ctx: MCP context for logging
|
|
165
|
+
instrument_id: Avanza instrument ID
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Order book depth data with:
|
|
169
|
+
- receivedTime: Timestamp of order book data
|
|
170
|
+
- levels: Array of price levels, each containing:
|
|
171
|
+
- buySide: Buy order with price, volume, priceString
|
|
172
|
+
- sellSide: Sell order with price, volume, priceString
|
|
173
|
+
|
|
174
|
+
Examples:
|
|
175
|
+
Get current order book:
|
|
176
|
+
>>> get_orderbook(instrument_id="5269")
|
|
177
|
+
"""
|
|
178
|
+
ctx.info(f"Fetching order book for ID: {instrument_id}")
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
async with AvanzaClient() as client:
|
|
182
|
+
service = MarketDataService(client)
|
|
183
|
+
orderbook = await service.get_order_depth(instrument_id)
|
|
184
|
+
|
|
185
|
+
levels_count = len(orderbook.levels)
|
|
186
|
+
ctx.info(f"Retrieved order book with {levels_count} levels")
|
|
187
|
+
return orderbook.model_dump(by_alias=True, exclude_none=True)
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
ctx.error(f"Failed to fetch order book: {str(e)}")
|
|
191
|
+
raise
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@mcp.tool()
|
|
195
|
+
async def get_stock_analysis(
|
|
196
|
+
ctx: Context,
|
|
197
|
+
instrument_id: str,
|
|
198
|
+
) -> dict:
|
|
199
|
+
"""Get detailed stock analysis with key financial ratios.
|
|
200
|
+
|
|
201
|
+
Provides comprehensive financial analysis including key ratios grouped by:
|
|
202
|
+
- Annual data (stockKeyRatiosByYear)
|
|
203
|
+
- Quarterly data (stockKeyRatiosByQuarter)
|
|
204
|
+
- Quarter-over-quarter comparisons (stockKeyRatiosByQuarterQuarter)
|
|
205
|
+
- Trailing twelve months (TTM) data (stockKeyRatiosByQuarterTTM)
|
|
206
|
+
- Company-level annual ratios (companyKeyRatiosByYear)
|
|
207
|
+
|
|
208
|
+
Use search_instruments() first to find the instrument_id for a stock.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
ctx: MCP context for logging
|
|
212
|
+
instrument_id: Avanza instrument ID from search results
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Comprehensive analysis data with:
|
|
216
|
+
- stockKeyRatiosByYear: Annual financial ratios
|
|
217
|
+
- stockKeyRatiosByQuarter: Quarterly financial ratios
|
|
218
|
+
- stockKeyRatiosByQuarterTTM: TTM financial ratios
|
|
219
|
+
- stockKeyRatiosByQuarterQuarter: Q-o-Q comparisons
|
|
220
|
+
- companyKeyRatiosByYear: Company-level annual metrics
|
|
221
|
+
- And more detailed financial analysis data
|
|
222
|
+
|
|
223
|
+
Examples:
|
|
224
|
+
Get analysis for Volvo B (ID from search):
|
|
225
|
+
>>> get_stock_analysis(instrument_id="5479")
|
|
226
|
+
"""
|
|
227
|
+
ctx.info(f"Fetching stock analysis for ID: {instrument_id}")
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
async with AvanzaClient() as client:
|
|
231
|
+
service = MarketDataService(client)
|
|
232
|
+
analysis = await service.get_stock_analysis(instrument_id)
|
|
233
|
+
|
|
234
|
+
ctx.info(f"Retrieved analysis data")
|
|
235
|
+
return analysis
|
|
236
|
+
|
|
237
|
+
except Exception as e:
|
|
238
|
+
ctx.error(f"Failed to fetch stock analysis: {str(e)}")
|
|
239
|
+
raise
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@mcp.tool()
|
|
243
|
+
async def get_stock_quote(
|
|
244
|
+
ctx: Context,
|
|
245
|
+
instrument_id: str,
|
|
246
|
+
) -> dict:
|
|
247
|
+
"""Get real-time stock quote with current pricing and trading data.
|
|
248
|
+
|
|
249
|
+
Provides immediate price information including bid/ask spread, last trade,
|
|
250
|
+
trading volumes, and real-time status. Lighter weight than get_stock_info.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
ctx: MCP context for logging
|
|
254
|
+
instrument_id: Avanza instrument ID from search results
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Real-time quote data with:
|
|
258
|
+
- buy: Current buy (bid) price
|
|
259
|
+
- sell: Current sell (ask) price
|
|
260
|
+
- last: Last traded price
|
|
261
|
+
- highest: Highest price today
|
|
262
|
+
- lowest: Lowest price today
|
|
263
|
+
- change: Price change
|
|
264
|
+
- changePercent: Percentage change
|
|
265
|
+
- totalValueTraded: Total value traded
|
|
266
|
+
- totalVolumeTraded: Total volume traded
|
|
267
|
+
- volumeWeightedAveragePrice: VWAP
|
|
268
|
+
- isRealTime: Whether data is real-time
|
|
269
|
+
|
|
270
|
+
Examples:
|
|
271
|
+
Get current quote for a stock:
|
|
272
|
+
>>> get_stock_quote(instrument_id="5269")
|
|
273
|
+
"""
|
|
274
|
+
ctx.info(f"Fetching stock quote for ID: {instrument_id}")
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
async with AvanzaClient() as client:
|
|
278
|
+
service = MarketDataService(client)
|
|
279
|
+
quote = await service.get_stock_quote(instrument_id)
|
|
280
|
+
|
|
281
|
+
ctx.info(f"Retrieved quote: last={quote.last}, change={quote.changePercent}%")
|
|
282
|
+
return quote.model_dump(by_alias=True, exclude_none=True)
|
|
283
|
+
|
|
284
|
+
except Exception as e:
|
|
285
|
+
ctx.error(f"Failed to fetch stock quote: {str(e)}")
|
|
286
|
+
raise
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@mcp.tool()
|
|
290
|
+
async def get_marketplace_info(
|
|
291
|
+
ctx: Context,
|
|
292
|
+
instrument_id: str,
|
|
293
|
+
) -> dict:
|
|
294
|
+
"""Get marketplace status and trading hours for an instrument.
|
|
295
|
+
|
|
296
|
+
Provides information about whether the market is open, time remaining until close,
|
|
297
|
+
and today's opening/closing times.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
ctx: MCP context for logging
|
|
301
|
+
instrument_id: Avanza instrument ID from search results
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Marketplace information with:
|
|
305
|
+
- marketOpen: Boolean indicating if market is currently open
|
|
306
|
+
- timeLeftMs: Milliseconds remaining until market close
|
|
307
|
+
- openingTime: Today's market opening time
|
|
308
|
+
- todayClosingTime: Today's market closing time
|
|
309
|
+
- normalClosingTime: Standard market closing time
|
|
310
|
+
|
|
311
|
+
Examples:
|
|
312
|
+
Check if market is open:
|
|
313
|
+
>>> get_marketplace_info(instrument_id="5269")
|
|
314
|
+
"""
|
|
315
|
+
ctx.info(f"Fetching marketplace info for ID: {instrument_id}")
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
async with AvanzaClient() as client:
|
|
319
|
+
service = MarketDataService(client)
|
|
320
|
+
marketplace = await service.get_marketplace_info(instrument_id)
|
|
321
|
+
|
|
322
|
+
status = "open" if marketplace.marketOpen else "closed"
|
|
323
|
+
ctx.info(f"Market is {status}")
|
|
324
|
+
return marketplace.model_dump(by_alias=True, exclude_none=True)
|
|
325
|
+
|
|
326
|
+
except Exception as e:
|
|
327
|
+
ctx.error(f"Failed to fetch marketplace info: {str(e)}")
|
|
328
|
+
raise
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@mcp.tool()
|
|
332
|
+
async def get_recent_trades(
|
|
333
|
+
ctx: Context,
|
|
334
|
+
instrument_id: str,
|
|
335
|
+
) -> dict:
|
|
336
|
+
"""Get recent trades for an instrument.
|
|
337
|
+
|
|
338
|
+
Returns a list of the most recent trades with price, volume, timestamp,
|
|
339
|
+
and trade metadata. Useful for understanding recent trading activity.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
ctx: MCP context for logging
|
|
343
|
+
instrument_id: Avanza instrument ID from search results
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
List of recent trades, each containing:
|
|
347
|
+
- buyer: Buyer information
|
|
348
|
+
- seller: Seller information
|
|
349
|
+
- dealTime: Trade timestamp
|
|
350
|
+
- price: Trade price
|
|
351
|
+
- volume: Trade volume
|
|
352
|
+
- matchedOnMarket: Market where trade was matched
|
|
353
|
+
|
|
354
|
+
Examples:
|
|
355
|
+
Get recent trades:
|
|
356
|
+
>>> get_recent_trades(instrument_id="5269")
|
|
357
|
+
"""
|
|
358
|
+
ctx.info(f"Fetching recent trades for ID: {instrument_id}")
|
|
359
|
+
|
|
360
|
+
try:
|
|
361
|
+
async with AvanzaClient() as client:
|
|
362
|
+
service = MarketDataService(client)
|
|
363
|
+
trades = await service.get_trades(instrument_id)
|
|
364
|
+
|
|
365
|
+
ctx.info(f"Retrieved {len(trades)} recent trades")
|
|
366
|
+
return {
|
|
367
|
+
"trades": [trade.model_dump(by_alias=True, exclude_none=True) for trade in trades]
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
except Exception as e:
|
|
371
|
+
ctx.error(f"Failed to fetch recent trades: {str(e)}")
|
|
372
|
+
raise
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@mcp.tool()
|
|
376
|
+
async def get_broker_trade_summary(
|
|
377
|
+
ctx: Context,
|
|
378
|
+
instrument_id: str,
|
|
379
|
+
) -> dict:
|
|
380
|
+
"""Get broker trade summary showing buy/sell activity.
|
|
381
|
+
|
|
382
|
+
Returns aggregated broker trading data showing total buy and sell volumes.
|
|
383
|
+
Useful for understanding institutional trading activity.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
ctx: MCP context for logging
|
|
387
|
+
instrument_id: Avanza instrument ID from search results
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
List of broker trade summaries with:
|
|
391
|
+
- brokerCode: Broker identifier
|
|
392
|
+
- buyVolume: Total volume bought
|
|
393
|
+
- sellVolume: Total volume sold
|
|
394
|
+
- netVolume: Net volume (buy - sell)
|
|
395
|
+
|
|
396
|
+
Examples:
|
|
397
|
+
Get broker trading activity:
|
|
398
|
+
>>> get_broker_trade_summary(instrument_id="5269")
|
|
399
|
+
"""
|
|
400
|
+
ctx.info(f"Fetching broker trade summary for ID: {instrument_id}")
|
|
401
|
+
|
|
402
|
+
try:
|
|
403
|
+
async with AvanzaClient() as client:
|
|
404
|
+
service = MarketDataService(client)
|
|
405
|
+
summaries = await service.get_broker_trades(instrument_id)
|
|
406
|
+
|
|
407
|
+
ctx.info(f"Retrieved {len(summaries)} broker trade summaries")
|
|
408
|
+
return {
|
|
409
|
+
"summaries": [
|
|
410
|
+
summary.model_dump(by_alias=True, exclude_none=True) for summary in summaries
|
|
411
|
+
]
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
except Exception as e:
|
|
415
|
+
ctx.error(f"Failed to fetch broker trade summary: {str(e)}")
|
|
416
|
+
raise
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
@mcp.tool()
|
|
420
|
+
async def get_dividends(
|
|
421
|
+
ctx: Context,
|
|
422
|
+
instrument_id: str,
|
|
423
|
+
) -> dict:
|
|
424
|
+
"""Get historical dividend data for a stock.
|
|
425
|
+
|
|
426
|
+
Returns dividend history by year including amounts, dates, and yields.
|
|
427
|
+
Useful for income investors and dividend analysis.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
ctx: MCP context for logging
|
|
431
|
+
instrument_id: Avanza instrument ID from search results
|
|
432
|
+
|
|
433
|
+
Returns:
|
|
434
|
+
Dividend data with:
|
|
435
|
+
- dividendsByYear: Array of yearly dividend data including:
|
|
436
|
+
- year: Year of dividend
|
|
437
|
+
- dividend: Dividend amount per share
|
|
438
|
+
- exDate: Ex-dividend date
|
|
439
|
+
- paymentDate: Payment date
|
|
440
|
+
- yield: Dividend yield percentage
|
|
441
|
+
- currency: Dividend currency
|
|
442
|
+
|
|
443
|
+
Examples:
|
|
444
|
+
Get dividend history for a stock:
|
|
445
|
+
>>> get_dividends(instrument_id="5479")
|
|
446
|
+
"""
|
|
447
|
+
ctx.info(f"Fetching dividend data for ID: {instrument_id}")
|
|
448
|
+
|
|
449
|
+
try:
|
|
450
|
+
async with AvanzaClient() as client:
|
|
451
|
+
service = MarketDataService(client)
|
|
452
|
+
dividends = await service.get_dividends(instrument_id)
|
|
453
|
+
|
|
454
|
+
years = len(dividends.get("dividendsByYear", []))
|
|
455
|
+
ctx.info(f"Retrieved {years} years of dividend data")
|
|
456
|
+
return dividends
|
|
457
|
+
|
|
458
|
+
except Exception as e:
|
|
459
|
+
ctx.error(f"Failed to fetch dividend data: {str(e)}")
|
|
460
|
+
raise
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@mcp.tool()
|
|
464
|
+
async def get_company_financials(
|
|
465
|
+
ctx: Context,
|
|
466
|
+
instrument_id: str,
|
|
467
|
+
) -> dict:
|
|
468
|
+
"""Get company financial statements and metrics.
|
|
469
|
+
|
|
470
|
+
Returns comprehensive financial data including revenue, profits, margins,
|
|
471
|
+
and other key metrics by year and quarter.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
ctx: MCP context for logging
|
|
475
|
+
instrument_id: Avanza instrument ID from search results
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
Financial data with:
|
|
479
|
+
- companyFinancialsByYear: Annual financial data
|
|
480
|
+
- companyFinancialsByQuarter: Quarterly financial data
|
|
481
|
+
- companyFinancialsByQuarterTTM: Trailing twelve months data
|
|
482
|
+
Each containing metrics like:
|
|
483
|
+
- revenue: Total revenue
|
|
484
|
+
- operatingProfit: Operating profit
|
|
485
|
+
- netProfit: Net profit
|
|
486
|
+
- grossMargin: Gross profit margin
|
|
487
|
+
- operatingMargin: Operating margin
|
|
488
|
+
- netMargin: Net profit margin
|
|
489
|
+
|
|
490
|
+
Examples:
|
|
491
|
+
Get financials for a company:
|
|
492
|
+
>>> get_company_financials(instrument_id="5479")
|
|
493
|
+
"""
|
|
494
|
+
ctx.info(f"Fetching company financials for ID: {instrument_id}")
|
|
495
|
+
|
|
496
|
+
try:
|
|
497
|
+
async with AvanzaClient() as client:
|
|
498
|
+
service = MarketDataService(client)
|
|
499
|
+
financials = await service.get_company_financials(instrument_id)
|
|
500
|
+
|
|
501
|
+
years = len(financials.get("companyFinancialsByYear", []))
|
|
502
|
+
quarters = len(financials.get("companyFinancialsByQuarter", []))
|
|
503
|
+
ctx.info(f"Retrieved financials: {years} years, {quarters} quarters")
|
|
504
|
+
return financials
|
|
505
|
+
|
|
506
|
+
except Exception as e:
|
|
507
|
+
ctx.error(f"Failed to fetch company financials: {str(e)}")
|
|
508
|
+
raise
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Search-related MCP tools."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from fastmcp import Context
|
|
6
|
+
|
|
7
|
+
from .. import mcp
|
|
8
|
+
from ..client import AvanzaClient
|
|
9
|
+
from ..services import SearchService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@mcp.tool()
|
|
13
|
+
async def search_instruments(
|
|
14
|
+
ctx: Context,
|
|
15
|
+
query: str,
|
|
16
|
+
instrument_type: Literal[
|
|
17
|
+
"stock", "fund", "etf", "certificate", "warrant", "all"
|
|
18
|
+
] = "all",
|
|
19
|
+
limit: int = 10,
|
|
20
|
+
) -> dict:
|
|
21
|
+
"""Search for financial instruments on Avanza.
|
|
22
|
+
|
|
23
|
+
Searches across stocks, funds, ETFs, certificates, and warrants.
|
|
24
|
+
Returns detailed search results including price info, sectors, and metadata.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
ctx: MCP context for logging
|
|
28
|
+
query: Search term (company name, ticker symbol, or ISIN)
|
|
29
|
+
instrument_type: Type of instrument to search for. Options:
|
|
30
|
+
- "stock": Stocks only
|
|
31
|
+
- "fund": Mutual funds only
|
|
32
|
+
- "etf": ETFs only
|
|
33
|
+
- "certificate": Certificates only
|
|
34
|
+
- "warrant": Warrants only
|
|
35
|
+
- "all": All instrument types (default)
|
|
36
|
+
limit: Maximum number of results to return (1-50, default: 10)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
Search response with:
|
|
40
|
+
- totalNumberOfHits: Total matching results
|
|
41
|
+
- hits: Array of search results with:
|
|
42
|
+
- orderBookId: Unique ID
|
|
43
|
+
- type: Instrument type (STOCK, FUND, etc.)
|
|
44
|
+
- title: Name
|
|
45
|
+
- price: Price information
|
|
46
|
+
- marketPlaceName: Exchange/market
|
|
47
|
+
- And more details...
|
|
48
|
+
- facets: Type breakdowns with counts
|
|
49
|
+
- searchQuery: The query that was executed
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
Search for Volvo stock:
|
|
53
|
+
>>> search_instruments(query="Volvo", instrument_type="stock", limit=5)
|
|
54
|
+
|
|
55
|
+
Search for any instrument matching "Global":
|
|
56
|
+
>>> search_instruments(query="Global", instrument_type="all", limit=10)
|
|
57
|
+
"""
|
|
58
|
+
ctx.info(f"Searching for '{query}' (type: {instrument_type}, limit: {limit})")
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
# Validate limit
|
|
62
|
+
limit = max(1, min(limit, 50))
|
|
63
|
+
|
|
64
|
+
async with AvanzaClient() as client:
|
|
65
|
+
service = SearchService(client)
|
|
66
|
+
response = await service.search(
|
|
67
|
+
query=query,
|
|
68
|
+
instrument_type=instrument_type if instrument_type != "all" else None,
|
|
69
|
+
limit=limit,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
ctx.info(f"Found {response.totalNumberOfHits} total hits, returning {len(response.hits)} results")
|
|
73
|
+
|
|
74
|
+
# Return the full response as dict
|
|
75
|
+
return response.model_dump(by_alias=True)
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
ctx.error(f"Search failed: {str(e)}")
|
|
79
|
+
raise
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@mcp.tool()
|
|
83
|
+
async def get_instrument_by_order_book_id(
|
|
84
|
+
ctx: Context,
|
|
85
|
+
order_book_id: str,
|
|
86
|
+
) -> dict | None:
|
|
87
|
+
"""Look up a financial instrument by its order book ID.
|
|
88
|
+
|
|
89
|
+
The order book ID is the unique identifier returned from search results.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
ctx: MCP context for logging
|
|
93
|
+
order_book_id: Order book ID to search for
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
First matching search hit if found, None otherwise
|
|
97
|
+
|
|
98
|
+
Examples:
|
|
99
|
+
Look up by order book ID:
|
|
100
|
+
>>> get_instrument_by_order_book_id(order_book_id="878733")
|
|
101
|
+
"""
|
|
102
|
+
ctx.info(f"Looking up instrument with order book ID: {order_book_id}")
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
async with AvanzaClient() as client:
|
|
106
|
+
service = SearchService(client)
|
|
107
|
+
response = await service.search(query=order_book_id, limit=1)
|
|
108
|
+
|
|
109
|
+
if response.hits:
|
|
110
|
+
hit = response.hits[0]
|
|
111
|
+
ctx.info(f"Found: {hit.title}")
|
|
112
|
+
return hit.model_dump()
|
|
113
|
+
else:
|
|
114
|
+
ctx.info("No instrument found with that order book ID")
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
ctx.error(f"Lookup failed: {str(e)}")
|
|
119
|
+
raise
|