avanza-mcp 1.1.0__py3-none-any.whl → 1.3.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 +3 -1
- avanza_mcp/client/endpoints.py +28 -0
- avanza_mcp/models/__init__.py +87 -7
- avanza_mcp/models/certificate.py +87 -0
- avanza_mcp/models/chart.py +51 -0
- avanza_mcp/models/common.py +15 -0
- avanza_mcp/models/etf.py +92 -0
- avanza_mcp/models/filter.py +51 -0
- avanza_mcp/models/future_forward.py +63 -0
- avanza_mcp/models/instrument_data.py +26 -0
- avanza_mcp/models/warrant.py +88 -0
- avanza_mcp/prompts/__init__.py +2 -1
- avanza_mcp/prompts/workflows.py +488 -0
- avanza_mcp/resources/__init__.py +2 -1
- avanza_mcp/resources/usage.py +503 -0
- avanza_mcp/services/market_data_service.py +313 -0
- avanza_mcp/tools/__init__.py +15 -1
- avanza_mcp/tools/certificates.py +156 -0
- avanza_mcp/tools/etfs.py +151 -0
- avanza_mcp/tools/futures_forwards.py +174 -0
- avanza_mcp/tools/instrument_data.py +135 -0
- avanza_mcp/tools/warrants.py +139 -0
- {avanza_mcp-1.1.0.dist-info → avanza_mcp-1.3.0.dist-info}/METADATA +56 -1
- avanza_mcp-1.3.0.dist-info/RECORD +40 -0
- avanza_mcp-1.1.0.dist-info/RECORD +0 -26
- {avanza_mcp-1.1.0.dist-info → avanza_mcp-1.3.0.dist-info}/WHEEL +0 -0
- {avanza_mcp-1.1.0.dist-info → avanza_mcp-1.3.0.dist-info}/entry_points.txt +0 -0
- {avanza_mcp-1.1.0.dist-info → avanza_mcp-1.3.0.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -4,6 +4,32 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
from ..client.base import AvanzaClient
|
|
6
6
|
from ..client.endpoints import PublicEndpoint
|
|
7
|
+
from ..models.certificate import (
|
|
8
|
+
CertificateDetails,
|
|
9
|
+
CertificateFilterRequest,
|
|
10
|
+
CertificateFilterResponse,
|
|
11
|
+
CertificateInfo,
|
|
12
|
+
)
|
|
13
|
+
from ..models.chart import ChartData
|
|
14
|
+
from ..models.etf import (
|
|
15
|
+
ETFDetails,
|
|
16
|
+
ETFFilterRequest,
|
|
17
|
+
ETFFilterResponse,
|
|
18
|
+
ETFInfo,
|
|
19
|
+
)
|
|
20
|
+
from ..models.future_forward import (
|
|
21
|
+
FutureForwardDetails,
|
|
22
|
+
FutureForwardInfo,
|
|
23
|
+
FutureForwardMatrixRequest,
|
|
24
|
+
FutureForwardMatrixResponse,
|
|
25
|
+
)
|
|
26
|
+
from ..models.instrument_data import NumberOfOwners, ShortSellingData
|
|
27
|
+
from ..models.warrant import (
|
|
28
|
+
WarrantDetails,
|
|
29
|
+
WarrantFilterRequest,
|
|
30
|
+
WarrantFilterResponse,
|
|
31
|
+
WarrantInfo,
|
|
32
|
+
)
|
|
7
33
|
from ..models.fund import (
|
|
8
34
|
FundChart,
|
|
9
35
|
FundChartPeriod,
|
|
@@ -296,3 +322,290 @@ class MarketDataService:
|
|
|
296
322
|
raw_data = await self._client.get(endpoint)
|
|
297
323
|
return FundDescription.model_validate(raw_data)
|
|
298
324
|
|
|
325
|
+
# === Certificates ===
|
|
326
|
+
|
|
327
|
+
async def filter_certificates(
|
|
328
|
+
self, filter_request: CertificateFilterRequest
|
|
329
|
+
) -> CertificateFilterResponse:
|
|
330
|
+
"""Filter and list certificates with pagination.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
filter_request: Filter criteria and pagination parameters
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Filtered list of certificates
|
|
337
|
+
|
|
338
|
+
Raises:
|
|
339
|
+
AvanzaError: If request fails
|
|
340
|
+
"""
|
|
341
|
+
endpoint = PublicEndpoint.CERTIFICATE_FILTER.value
|
|
342
|
+
raw_data = await self._client.post(
|
|
343
|
+
endpoint, json=filter_request.model_dump(by_alias=True, exclude_none=True)
|
|
344
|
+
)
|
|
345
|
+
return CertificateFilterResponse.model_validate(raw_data)
|
|
346
|
+
|
|
347
|
+
async def get_certificate_info(self, instrument_id: str) -> CertificateInfo:
|
|
348
|
+
"""Fetch detailed certificate information.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
instrument_id: Avanza certificate ID
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
Detailed certificate information
|
|
355
|
+
|
|
356
|
+
Raises:
|
|
357
|
+
AvanzaError: If request fails
|
|
358
|
+
"""
|
|
359
|
+
endpoint = PublicEndpoint.CERTIFICATE_INFO.format(id=instrument_id)
|
|
360
|
+
raw_data = await self._client.get(endpoint)
|
|
361
|
+
return CertificateInfo.model_validate(raw_data)
|
|
362
|
+
|
|
363
|
+
async def get_certificate_details(self, instrument_id: str) -> CertificateDetails:
|
|
364
|
+
"""Fetch extended certificate details.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
instrument_id: Avanza certificate ID
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
Extended certificate details
|
|
371
|
+
|
|
372
|
+
Raises:
|
|
373
|
+
AvanzaError: If request fails
|
|
374
|
+
"""
|
|
375
|
+
endpoint = PublicEndpoint.CERTIFICATE_DETAILS.format(id=instrument_id)
|
|
376
|
+
raw_data = await self._client.get(endpoint)
|
|
377
|
+
return CertificateDetails.model_validate(raw_data)
|
|
378
|
+
|
|
379
|
+
# === Warrants ===
|
|
380
|
+
|
|
381
|
+
async def filter_warrants(
|
|
382
|
+
self, filter_request: WarrantFilterRequest
|
|
383
|
+
) -> WarrantFilterResponse:
|
|
384
|
+
"""Filter and list warrants with pagination.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
filter_request: Filter criteria and pagination parameters
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
Filtered list of warrants
|
|
391
|
+
|
|
392
|
+
Raises:
|
|
393
|
+
AvanzaError: If request fails
|
|
394
|
+
"""
|
|
395
|
+
endpoint = PublicEndpoint.WARRANT_FILTER.value
|
|
396
|
+
raw_data = await self._client.post(
|
|
397
|
+
endpoint, json=filter_request.model_dump(by_alias=True, exclude_none=True)
|
|
398
|
+
)
|
|
399
|
+
return WarrantFilterResponse.model_validate(raw_data)
|
|
400
|
+
|
|
401
|
+
async def get_warrant_info(self, instrument_id: str) -> WarrantInfo:
|
|
402
|
+
"""Fetch detailed warrant information.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
instrument_id: Avanza warrant ID
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
Detailed warrant information
|
|
409
|
+
|
|
410
|
+
Raises:
|
|
411
|
+
AvanzaError: If request fails
|
|
412
|
+
"""
|
|
413
|
+
endpoint = PublicEndpoint.WARRANT_INFO.format(id=instrument_id)
|
|
414
|
+
raw_data = await self._client.get(endpoint)
|
|
415
|
+
return WarrantInfo.model_validate(raw_data)
|
|
416
|
+
|
|
417
|
+
async def get_warrant_details(self, instrument_id: str) -> WarrantDetails:
|
|
418
|
+
"""Fetch extended warrant details.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
instrument_id: Avanza warrant ID
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
Extended warrant details
|
|
425
|
+
|
|
426
|
+
Raises:
|
|
427
|
+
AvanzaError: If request fails
|
|
428
|
+
"""
|
|
429
|
+
endpoint = PublicEndpoint.WARRANT_DETAILS.format(id=instrument_id)
|
|
430
|
+
raw_data = await self._client.get(endpoint)
|
|
431
|
+
return WarrantDetails.model_validate(raw_data)
|
|
432
|
+
|
|
433
|
+
# === ETFs ===
|
|
434
|
+
|
|
435
|
+
async def filter_etfs(self, filter_request: ETFFilterRequest) -> ETFFilterResponse:
|
|
436
|
+
"""Filter and list ETFs with pagination.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
filter_request: Filter criteria and pagination parameters
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
Filtered list of ETFs
|
|
443
|
+
|
|
444
|
+
Raises:
|
|
445
|
+
AvanzaError: If request fails
|
|
446
|
+
"""
|
|
447
|
+
endpoint = PublicEndpoint.ETF_FILTER.value
|
|
448
|
+
raw_data = await self._client.post(
|
|
449
|
+
endpoint, json=filter_request.model_dump(by_alias=True, exclude_none=True)
|
|
450
|
+
)
|
|
451
|
+
return ETFFilterResponse.model_validate(raw_data)
|
|
452
|
+
|
|
453
|
+
async def get_etf_info(self, instrument_id: str) -> ETFInfo:
|
|
454
|
+
"""Fetch detailed ETF information.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
instrument_id: Avanza ETF ID
|
|
458
|
+
|
|
459
|
+
Returns:
|
|
460
|
+
Detailed ETF information
|
|
461
|
+
|
|
462
|
+
Raises:
|
|
463
|
+
AvanzaError: If request fails
|
|
464
|
+
"""
|
|
465
|
+
endpoint = PublicEndpoint.ETF_INFO.format(id=instrument_id)
|
|
466
|
+
raw_data = await self._client.get(endpoint)
|
|
467
|
+
return ETFInfo.model_validate(raw_data)
|
|
468
|
+
|
|
469
|
+
async def get_etf_details(self, instrument_id: str) -> ETFDetails:
|
|
470
|
+
"""Fetch extended ETF details.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
instrument_id: Avanza ETF ID
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
Extended ETF details
|
|
477
|
+
|
|
478
|
+
Raises:
|
|
479
|
+
AvanzaError: If request fails
|
|
480
|
+
"""
|
|
481
|
+
endpoint = PublicEndpoint.ETF_DETAILS.format(id=instrument_id)
|
|
482
|
+
raw_data = await self._client.get(endpoint)
|
|
483
|
+
return ETFDetails.model_validate(raw_data)
|
|
484
|
+
|
|
485
|
+
# === Futures/Forwards ===
|
|
486
|
+
|
|
487
|
+
async def list_futures_forwards(
|
|
488
|
+
self, request: FutureForwardMatrixRequest
|
|
489
|
+
) -> FutureForwardMatrixResponse:
|
|
490
|
+
"""List futures and forwards using matrix endpoint.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
request: Matrix list request parameters
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
Matrix response with futures/forwards
|
|
497
|
+
|
|
498
|
+
Raises:
|
|
499
|
+
AvanzaError: If request fails
|
|
500
|
+
"""
|
|
501
|
+
endpoint = PublicEndpoint.FUTURE_FORWARD_MATRIX.value
|
|
502
|
+
raw_data = await self._client.post(
|
|
503
|
+
endpoint, json=request.model_dump(by_alias=True, exclude_none=True)
|
|
504
|
+
)
|
|
505
|
+
return FutureForwardMatrixResponse.model_validate(raw_data)
|
|
506
|
+
|
|
507
|
+
async def get_future_forward_info(self, instrument_id: str) -> FutureForwardInfo:
|
|
508
|
+
"""Fetch detailed future/forward information.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
instrument_id: Avanza future/forward ID
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
Detailed future/forward information
|
|
515
|
+
|
|
516
|
+
Raises:
|
|
517
|
+
AvanzaError: If request fails
|
|
518
|
+
"""
|
|
519
|
+
endpoint = PublicEndpoint.FUTURE_FORWARD_INFO.format(id=instrument_id)
|
|
520
|
+
raw_data = await self._client.get(endpoint)
|
|
521
|
+
return FutureForwardInfo.model_validate(raw_data)
|
|
522
|
+
|
|
523
|
+
async def get_future_forward_details(
|
|
524
|
+
self, instrument_id: str
|
|
525
|
+
) -> FutureForwardDetails:
|
|
526
|
+
"""Fetch extended future/forward details.
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
instrument_id: Avanza future/forward ID
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
Extended future/forward details
|
|
533
|
+
|
|
534
|
+
Raises:
|
|
535
|
+
AvanzaError: If request fails
|
|
536
|
+
"""
|
|
537
|
+
endpoint = PublicEndpoint.FUTURE_FORWARD_DETAILS.format(id=instrument_id)
|
|
538
|
+
raw_data = await self._client.get(endpoint)
|
|
539
|
+
return FutureForwardDetails.model_validate(raw_data)
|
|
540
|
+
|
|
541
|
+
async def get_future_forward_filter_options(self) -> dict[str, Any]:
|
|
542
|
+
"""Get available filter options for futures/forwards.
|
|
543
|
+
|
|
544
|
+
Returns:
|
|
545
|
+
Available filter options including underlying instruments, dates, etc.
|
|
546
|
+
|
|
547
|
+
Raises:
|
|
548
|
+
AvanzaError: If request fails
|
|
549
|
+
"""
|
|
550
|
+
endpoint = PublicEndpoint.FUTURE_FORWARD_FILTER_OPTIONS.value
|
|
551
|
+
raw_data = await self._client.get(endpoint)
|
|
552
|
+
return raw_data
|
|
553
|
+
|
|
554
|
+
# === Additional Features ===
|
|
555
|
+
|
|
556
|
+
async def get_number_of_owners(self, instrument_id: str) -> NumberOfOwners:
|
|
557
|
+
"""Get number of owners for an instrument.
|
|
558
|
+
|
|
559
|
+
Args:
|
|
560
|
+
instrument_id: Avanza instrument ID
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
Number of owners data
|
|
564
|
+
|
|
565
|
+
Raises:
|
|
566
|
+
AvanzaError: If request fails
|
|
567
|
+
"""
|
|
568
|
+
endpoint = PublicEndpoint.NUMBER_OF_OWNERS.format(id=instrument_id)
|
|
569
|
+
raw_data = await self._client.get(endpoint)
|
|
570
|
+
return NumberOfOwners.model_validate(raw_data)
|
|
571
|
+
|
|
572
|
+
async def get_short_selling(self, instrument_id: str) -> ShortSellingData:
|
|
573
|
+
"""Get short selling data for an instrument.
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
instrument_id: Avanza instrument ID
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
Short selling data
|
|
580
|
+
|
|
581
|
+
Raises:
|
|
582
|
+
AvanzaError: If request fails
|
|
583
|
+
"""
|
|
584
|
+
endpoint = PublicEndpoint.SHORT_SELLING.format(id=instrument_id)
|
|
585
|
+
raw_data = await self._client.get(endpoint)
|
|
586
|
+
return ShortSellingData.model_validate(raw_data)
|
|
587
|
+
|
|
588
|
+
async def get_marketmaker_chart(
|
|
589
|
+
self, instrument_id: str, time_period: str = "today"
|
|
590
|
+
) -> ChartData:
|
|
591
|
+
"""Get price chart data for traded products (certificates, warrants, ETFs).
|
|
592
|
+
|
|
593
|
+
This endpoint provides OHLC (Open-High-Low-Close) candlestick data
|
|
594
|
+
with market maker information for traded products.
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
instrument_id: Avanza instrument ID
|
|
598
|
+
time_period: Time period for chart data (default: "today")
|
|
599
|
+
Available periods: today, one_week, one_month, three_months,
|
|
600
|
+
six_months, one_year, three_years, five_years, etc.
|
|
601
|
+
|
|
602
|
+
Returns:
|
|
603
|
+
Chart data with OHLC candlesticks and metadata
|
|
604
|
+
|
|
605
|
+
Raises:
|
|
606
|
+
AvanzaError: If request fails
|
|
607
|
+
"""
|
|
608
|
+
endpoint = PublicEndpoint.MARKETMAKER_CHART.format(id=instrument_id)
|
|
609
|
+
raw_data = await self._client.get(endpoint, params={"timePeriod": time_period})
|
|
610
|
+
return ChartData.model_validate(raw_data)
|
|
611
|
+
|
avanza_mcp/tools/__init__.py
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
"""MCP tools for Avanza API."""
|
|
2
2
|
|
|
3
3
|
# Import to register tools via decorators
|
|
4
|
+
from . import certificates # noqa: F401
|
|
5
|
+
from . import etfs # noqa: F401
|
|
4
6
|
from . import funds # noqa: F401
|
|
7
|
+
from . import futures_forwards # noqa: F401
|
|
8
|
+
from . import instrument_data # noqa: F401
|
|
5
9
|
from . import market_data # noqa: F401
|
|
6
10
|
from . import search # noqa: F401
|
|
11
|
+
from . import warrants # noqa: F401
|
|
7
12
|
|
|
8
|
-
__all__ = [
|
|
13
|
+
__all__ = [
|
|
14
|
+
"certificates",
|
|
15
|
+
"etfs",
|
|
16
|
+
"funds",
|
|
17
|
+
"futures_forwards",
|
|
18
|
+
"instrument_data",
|
|
19
|
+
"market_data",
|
|
20
|
+
"search",
|
|
21
|
+
"warrants",
|
|
22
|
+
]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Certificate MCP tools."""
|
|
2
|
+
|
|
3
|
+
from fastmcp import Context
|
|
4
|
+
|
|
5
|
+
from .. import mcp
|
|
6
|
+
from ..client import AvanzaClient
|
|
7
|
+
from ..models.certificate import CertificateFilter, CertificateFilterRequest
|
|
8
|
+
from ..models.filter import SortBy
|
|
9
|
+
from ..services import MarketDataService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@mcp.tool()
|
|
13
|
+
async def filter_certificates(
|
|
14
|
+
ctx: Context,
|
|
15
|
+
offset: int = 0,
|
|
16
|
+
limit: int = 20,
|
|
17
|
+
directions: list[str] | None = None,
|
|
18
|
+
leverages: list[float] | None = None,
|
|
19
|
+
issuers: list[str] | None = None,
|
|
20
|
+
categories: list[str] | None = None,
|
|
21
|
+
exposures: list[str] | None = None,
|
|
22
|
+
underlying_instruments: list[str] | None = None,
|
|
23
|
+
sort_field: str = "name",
|
|
24
|
+
sort_order: str = "asc",
|
|
25
|
+
) -> dict:
|
|
26
|
+
"""Filter and list certificates with optional filtering and pagination.
|
|
27
|
+
|
|
28
|
+
Search through available certificates with optional filters for direction
|
|
29
|
+
(long/short), leverage, issuer, category, exposure, and underlying instrument.
|
|
30
|
+
Supports pagination for large result sets.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
ctx: MCP context for logging
|
|
34
|
+
offset: Number of results to skip (default: 0)
|
|
35
|
+
limit: Maximum number of results to return (default: 20, max: 100)
|
|
36
|
+
directions: Filter by direction, e.g., ["long"], ["short"]
|
|
37
|
+
leverages: Filter by leverage values, e.g., [1.0, 2.0]
|
|
38
|
+
issuers: Filter by issuer names, e.g., ["Valour", "21Shares"]
|
|
39
|
+
categories: Filter by categories
|
|
40
|
+
exposures: Filter by exposures
|
|
41
|
+
underlying_instruments: Filter by underlying instrument IDs
|
|
42
|
+
sort_field: Field to sort by (default: "name")
|
|
43
|
+
sort_order: Sort order "asc" or "desc" (default: "asc")
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Filtered list of certificates with:
|
|
47
|
+
- certificates: Array of certificate objects with orderbookId, name, price, etc.
|
|
48
|
+
- pagination: Current offset and limit
|
|
49
|
+
- totalNumberOfOrderbooks: Total matching certificates
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
List first 20 certificates:
|
|
53
|
+
>>> filter_certificates()
|
|
54
|
+
|
|
55
|
+
Find long certificates with 1x leverage:
|
|
56
|
+
>>> filter_certificates(directions=["long"], leverages=[1.0])
|
|
57
|
+
|
|
58
|
+
Get next page of results:
|
|
59
|
+
>>> filter_certificates(offset=20, limit=20)
|
|
60
|
+
"""
|
|
61
|
+
ctx.info(f"Filtering certificates: offset={offset}, limit={limit}")
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
filter_req = CertificateFilterRequest(
|
|
65
|
+
filter=CertificateFilter(
|
|
66
|
+
directions=directions or [],
|
|
67
|
+
leverages=leverages or [],
|
|
68
|
+
issuers=issuers or [],
|
|
69
|
+
categories=categories or [],
|
|
70
|
+
exposures=exposures or [],
|
|
71
|
+
underlyingInstruments=underlying_instruments or [],
|
|
72
|
+
),
|
|
73
|
+
offset=offset,
|
|
74
|
+
limit=min(limit, 100),
|
|
75
|
+
sortBy=SortBy(field=sort_field, order=sort_order),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
async with AvanzaClient() as client:
|
|
79
|
+
service = MarketDataService(client)
|
|
80
|
+
result = await service.filter_certificates(filter_req)
|
|
81
|
+
|
|
82
|
+
ctx.info(f"Retrieved {len(result.certificates)} certificates")
|
|
83
|
+
return result.model_dump(by_alias=True, exclude_none=True)
|
|
84
|
+
|
|
85
|
+
except Exception as e:
|
|
86
|
+
ctx.error(f"Failed to filter certificates: {str(e)}")
|
|
87
|
+
raise
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@mcp.tool()
|
|
91
|
+
async def get_certificate_info(ctx: Context, instrument_id: str) -> dict:
|
|
92
|
+
"""Get detailed information about a specific certificate.
|
|
93
|
+
|
|
94
|
+
Provides comprehensive certificate data including current price, leverage,
|
|
95
|
+
underlying instrument, issuer, and historical performance.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
ctx: MCP context for logging
|
|
99
|
+
instrument_id: Avanza certificate ID (orderbookId from filter results)
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Detailed certificate information including:
|
|
103
|
+
- Basic info: name, ISIN, issuer
|
|
104
|
+
- Pricing: current quote, buy/sell prices
|
|
105
|
+
- Characteristics: leverage, direction, underlying instrument
|
|
106
|
+
- Historical data: price history over various periods
|
|
107
|
+
|
|
108
|
+
Examples:
|
|
109
|
+
Get info for a certificate:
|
|
110
|
+
>>> get_certificate_info(instrument_id="1756318")
|
|
111
|
+
"""
|
|
112
|
+
ctx.info(f"Fetching certificate info for ID: {instrument_id}")
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
async with AvanzaClient() as client:
|
|
116
|
+
service = MarketDataService(client)
|
|
117
|
+
certificate = await service.get_certificate_info(instrument_id)
|
|
118
|
+
|
|
119
|
+
ctx.info(f"Retrieved info for: {certificate.name}")
|
|
120
|
+
return certificate.model_dump(by_alias=True, exclude_none=True)
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
ctx.error(f"Failed to fetch certificate info: {str(e)}")
|
|
124
|
+
raise
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@mcp.tool()
|
|
128
|
+
async def get_certificate_details(ctx: Context, instrument_id: str) -> dict:
|
|
129
|
+
"""Get extended details about a specific certificate.
|
|
130
|
+
|
|
131
|
+
Provides additional detailed information beyond basic certificate info.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
ctx: MCP context for logging
|
|
135
|
+
instrument_id: Avanza certificate ID
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Extended certificate details
|
|
139
|
+
|
|
140
|
+
Examples:
|
|
141
|
+
Get detailed info:
|
|
142
|
+
>>> get_certificate_details(instrument_id="1756318")
|
|
143
|
+
"""
|
|
144
|
+
ctx.info(f"Fetching certificate details for ID: {instrument_id}")
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
async with AvanzaClient() as client:
|
|
148
|
+
service = MarketDataService(client)
|
|
149
|
+
details = await service.get_certificate_details(instrument_id)
|
|
150
|
+
|
|
151
|
+
ctx.info("Retrieved certificate details")
|
|
152
|
+
return details.model_dump(by_alias=True, exclude_none=True)
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
ctx.error(f"Failed to fetch certificate details: {str(e)}")
|
|
156
|
+
raise
|
avanza_mcp/tools/etfs.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""ETF MCP tools."""
|
|
2
|
+
|
|
3
|
+
from fastmcp import Context
|
|
4
|
+
|
|
5
|
+
from .. import mcp
|
|
6
|
+
from ..client import AvanzaClient
|
|
7
|
+
from ..models.etf import ETFFilter, ETFFilterRequest
|
|
8
|
+
from ..models.filter import SortBy
|
|
9
|
+
from ..services import MarketDataService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@mcp.tool()
|
|
13
|
+
async def filter_etfs(
|
|
14
|
+
ctx: Context,
|
|
15
|
+
offset: int = 0,
|
|
16
|
+
limit: int = 20,
|
|
17
|
+
asset_categories: list[str] | None = None,
|
|
18
|
+
sub_categories: list[str] | None = None,
|
|
19
|
+
exposures: list[str] | None = None,
|
|
20
|
+
risk_scores: list[str] | None = None,
|
|
21
|
+
directions: list[str] | None = None,
|
|
22
|
+
issuers: list[str] | None = None,
|
|
23
|
+
currency_codes: list[str] | None = None,
|
|
24
|
+
sort_field: str = "numberOfOwners",
|
|
25
|
+
sort_order: str = "desc",
|
|
26
|
+
) -> dict:
|
|
27
|
+
"""Filter and list ETFs with optional filtering and pagination.
|
|
28
|
+
|
|
29
|
+
Search through exchange-traded funds with filters for asset category,
|
|
30
|
+
geographic exposure, issuer, risk score, and more.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
ctx: MCP context for logging
|
|
34
|
+
offset: Number of results to skip (default: 0)
|
|
35
|
+
limit: Maximum number of results (default: 20, max: 100)
|
|
36
|
+
asset_categories: Filter by category, e.g., ["stock"], ["commodity"]
|
|
37
|
+
sub_categories: Filter by sub-category, e.g., ["teknologi"], ["fastigheter"]
|
|
38
|
+
exposures: Filter by geographic exposure, e.g., ["usa"], ["global"]
|
|
39
|
+
risk_scores: Filter by risk score levels
|
|
40
|
+
directions: Filter by direction (for leveraged ETFs)
|
|
41
|
+
issuers: Filter by issuer, e.g., ["iShares"], ["Vanguard"]
|
|
42
|
+
currency_codes: Filter by currency, e.g., ["SEK"], ["USD"]
|
|
43
|
+
sort_field: Field to sort by (default: "numberOfOwners")
|
|
44
|
+
sort_order: Sort order "asc" or "desc" (default: "desc")
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Filtered list of ETFs with fees, yield, number of owners, etc.
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
List most popular ETFs:
|
|
51
|
+
>>> filter_etfs(sort_field="numberOfOwners", sort_order="desc")
|
|
52
|
+
|
|
53
|
+
Find US technology ETFs:
|
|
54
|
+
>>> filter_etfs(exposures=["usa"], sub_categories=["teknologi"])
|
|
55
|
+
|
|
56
|
+
Get affordable ETFs with low fees:
|
|
57
|
+
>>> filter_etfs(sort_field="managementFee", sort_order="asc")
|
|
58
|
+
"""
|
|
59
|
+
ctx.info(f"Filtering ETFs: offset={offset}, limit={limit}")
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
filter_req = ETFFilterRequest(
|
|
63
|
+
filter=ETFFilter(
|
|
64
|
+
assetCategories=asset_categories or [],
|
|
65
|
+
subCategories=sub_categories or [],
|
|
66
|
+
exposures=exposures or [],
|
|
67
|
+
riskScores=risk_scores or [],
|
|
68
|
+
directions=directions or [],
|
|
69
|
+
issuers=issuers or [],
|
|
70
|
+
currencyCodes=currency_codes or [],
|
|
71
|
+
),
|
|
72
|
+
offset=offset,
|
|
73
|
+
limit=min(limit, 100),
|
|
74
|
+
sortBy=SortBy(field=sort_field, order=sort_order),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
async with AvanzaClient() as client:
|
|
78
|
+
service = MarketDataService(client)
|
|
79
|
+
result = await service.filter_etfs(filter_req)
|
|
80
|
+
|
|
81
|
+
ctx.info(f"Retrieved {len(result.etfs)} ETFs")
|
|
82
|
+
return result.model_dump(by_alias=True, exclude_none=True)
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
ctx.error(f"Failed to filter ETFs: {str(e)}")
|
|
86
|
+
raise
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@mcp.tool()
|
|
90
|
+
async def get_etf_info(ctx: Context, instrument_id: str) -> dict:
|
|
91
|
+
"""Get detailed information about a specific ETF.
|
|
92
|
+
|
|
93
|
+
Provides comprehensive ETF data including current NAV, dividend yield,
|
|
94
|
+
management fees, risk score, and historical performance.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
ctx: MCP context for logging
|
|
98
|
+
instrument_id: Avanza ETF ID
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
Detailed ETF information with fees, yield, performance, etc.
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
Get ETF info:
|
|
105
|
+
>>> get_etf_info(instrument_id="742236")
|
|
106
|
+
"""
|
|
107
|
+
ctx.info(f"Fetching ETF info for ID: {instrument_id}")
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
async with AvanzaClient() as client:
|
|
111
|
+
service = MarketDataService(client)
|
|
112
|
+
etf = await service.get_etf_info(instrument_id)
|
|
113
|
+
|
|
114
|
+
ctx.info(f"Retrieved info for: {etf.name}")
|
|
115
|
+
return etf.model_dump(by_alias=True, exclude_none=True)
|
|
116
|
+
|
|
117
|
+
except Exception as e:
|
|
118
|
+
ctx.error(f"Failed to fetch ETF info: {str(e)}")
|
|
119
|
+
raise
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@mcp.tool()
|
|
123
|
+
async def get_etf_details(ctx: Context, instrument_id: str) -> dict:
|
|
124
|
+
"""Get extended details about a specific ETF.
|
|
125
|
+
|
|
126
|
+
Provides additional detailed information beyond basic ETF info.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
ctx: MCP context for logging
|
|
130
|
+
instrument_id: Avanza ETF ID
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Extended ETF details
|
|
134
|
+
|
|
135
|
+
Examples:
|
|
136
|
+
Get detailed info:
|
|
137
|
+
>>> get_etf_details(instrument_id="742236")
|
|
138
|
+
"""
|
|
139
|
+
ctx.info(f"Fetching ETF details for ID: {instrument_id}")
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
async with AvanzaClient() as client:
|
|
143
|
+
service = MarketDataService(client)
|
|
144
|
+
details = await service.get_etf_details(instrument_id)
|
|
145
|
+
|
|
146
|
+
ctx.info("Retrieved ETF details")
|
|
147
|
+
return details.model_dump(by_alias=True, exclude_none=True)
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
ctx.error(f"Failed to fetch ETF details: {str(e)}")
|
|
151
|
+
raise
|