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.
@@ -0,0 +1,246 @@
1
+ """Fund-related Pydantic models."""
2
+
3
+ from datetime import date, datetime
4
+ from decimal import Decimal
5
+
6
+ from pydantic import BaseModel, ConfigDict, Field
7
+
8
+
9
+ # Standard model config for all models
10
+ MODEL_CONFIG = ConfigDict(
11
+ populate_by_name=True,
12
+ str_strip_whitespace=True,
13
+ validate_assignment=True,
14
+ extra="allow", # Don't fail on extra fields from API
15
+ )
16
+
17
+
18
+ class FundPerformance(BaseModel):
19
+ """Fund performance metrics over various time periods."""
20
+
21
+ model_config = MODEL_CONFIG
22
+
23
+ today: Decimal | None = Field(None, description="Performance today (%)")
24
+ one_week: Decimal | None = Field(None, alias="oneWeek", description="1 week return (%)")
25
+ one_month: Decimal | None = Field(
26
+ None, alias="oneMonth", description="1 month return (%)"
27
+ )
28
+ three_months: Decimal | None = Field(
29
+ None, alias="threeMonths", description="3 month return (%)"
30
+ )
31
+ this_year: Decimal | None = Field(
32
+ None, alias="thisYear", description="Year to date return (%)"
33
+ )
34
+ one_year: Decimal | None = Field(None, alias="oneYear", description="1 year return (%)")
35
+ three_years: Decimal | None = Field(
36
+ None, alias="threeYears", description="3 year return (%)"
37
+ )
38
+ five_years: Decimal | None = Field(
39
+ None, alias="fiveYears", description="5 year return (%)"
40
+ )
41
+ ten_years: Decimal | None = Field(
42
+ None, alias="tenYears", description="10 year return (%)"
43
+ )
44
+
45
+
46
+ class FundFee(BaseModel):
47
+ """Fund fee information."""
48
+
49
+ model_config = MODEL_CONFIG
50
+
51
+ ongoing_charges: Decimal | None = Field(
52
+ None, alias="ongoingCharges", description="Ongoing charges (%)"
53
+ )
54
+ entry_charge: Decimal | None = Field(
55
+ None, alias="entryCharge", description="Entry fee (%)"
56
+ )
57
+ exit_charge: Decimal | None = Field(None, alias="exitCharge", description="Exit fee (%)")
58
+
59
+
60
+ class ChartDataPoint(BaseModel):
61
+ """Data point for portfolio allocation charts."""
62
+
63
+ model_config = MODEL_CONFIG
64
+
65
+ name: str | None = None
66
+ y: float | None = None
67
+
68
+
69
+ class FundInfo(BaseModel):
70
+ """Detailed fund information."""
71
+
72
+ model_config = MODEL_CONFIG
73
+
74
+ # Basic info
75
+ id: str | None = Field(None, description="Fund ID")
76
+ name: str = Field(..., description="Fund name")
77
+ isin: str | None = Field(None, description="ISIN identifier")
78
+ description: str | None = Field(None, description="Fund description")
79
+
80
+ # Price and NAV
81
+ nav: Decimal | None = Field(None, description="Net Asset Value")
82
+ nav_date: date | None = Field(None, alias="navDate", description="NAV date")
83
+ currency: str = Field(default="SEK", description="Fund currency")
84
+
85
+ # Performance
86
+ development: FundPerformance | None = Field(
87
+ None, description="Performance over time periods"
88
+ )
89
+ change_since_three_months: Decimal | None = Field(
90
+ None, alias="changeSinceThreeMonths", description="3 month change (%)"
91
+ )
92
+ change_since_one_year: Decimal | None = Field(
93
+ None, alias="changeSinceOneYear", description="1 year change (%)"
94
+ )
95
+
96
+ # Risk metrics
97
+ risk: int | None = Field(None, description="Risk level (1-7)")
98
+ risk_level: str | None = Field(None, alias="riskLevel", description="Risk category")
99
+ rating: int | None = Field(None, description="Rating (e.g., Morningstar)")
100
+ standard_deviation: Decimal | None = Field(
101
+ None, alias="standardDeviation", description="Standard deviation"
102
+ )
103
+ sharpe_ratio: Decimal | None = Field(
104
+ None, alias="sharpeRatio", description="Sharpe ratio"
105
+ )
106
+
107
+ # Fees
108
+ fee: FundFee | None = Field(None, description="Fee information")
109
+
110
+ # Fund characteristics
111
+ fund_company: str | None = Field(None, alias="fundCompany", description="Fund company")
112
+ fund_type_name: str | None = Field(
113
+ None, alias="fundTypeName", description="Fund type"
114
+ )
115
+ category: str | None = Field(None, description="Fund category")
116
+ aum: Decimal | None = Field(
117
+ None, alias="capital", description="Assets under management"
118
+ )
119
+ start_date: date | None = Field(None, alias="startDate", description="Fund inception date")
120
+
121
+ # Trading
122
+ tradeable: bool = Field(default=True, description="Whether fund is tradeable")
123
+ buy_fee: Decimal | None = Field(None, alias="buyFee", description="Buy fee (%)")
124
+ sell_fee: Decimal | None = Field(None, alias="sellFee", description="Sell fee (%)")
125
+ prospectus: str | None = Field(None, description="Prospectus URL")
126
+
127
+ # Portfolio allocations
128
+ country_chart_data: list[ChartDataPoint] = Field(
129
+ default_factory=list,
130
+ alias="countryChartData",
131
+ description="Geographic allocation by country",
132
+ )
133
+ sector_chart_data: list[ChartDataPoint] = Field(
134
+ default_factory=list,
135
+ alias="sectorChartData",
136
+ description="Sector allocation",
137
+ )
138
+ holding_chart_data: list[ChartDataPoint] = Field(
139
+ default_factory=list,
140
+ alias="holdingChartData",
141
+ description="Top holdings",
142
+ )
143
+ portfolio_date: date | None = Field(
144
+ None, alias="portfolioDate", description="Date of portfolio data"
145
+ )
146
+
147
+ # Timestamps
148
+ last_updated: datetime | None = Field(
149
+ None, alias="lastUpdated", description="Last update time"
150
+ )
151
+
152
+
153
+ # === Models for additional fund endpoints ===
154
+
155
+
156
+ class ProductInvolvement(BaseModel):
157
+ """Product involvement information for sustainability metrics."""
158
+
159
+ model_config = MODEL_CONFIG
160
+
161
+ product: str
162
+ productDescription: str
163
+ value: float
164
+ name: str
165
+
166
+
167
+ class SustainabilityGoal(BaseModel):
168
+ """UN Sustainable Development Goal information."""
169
+
170
+ model_config = MODEL_CONFIG
171
+
172
+ goalId: int | None = None
173
+ goalName: str | None = None
174
+ goalDescription: str | None = None
175
+
176
+
177
+ class FundSustainability(BaseModel):
178
+ """Fund sustainability and ESG metrics."""
179
+
180
+ model_config = MODEL_CONFIG
181
+
182
+ lowCarbon: bool | None = None
183
+ esgScore: float | None = None
184
+ environmentalScore: float | None = None
185
+ socialScore: float | None = None
186
+ governanceScore: float | None = None
187
+ controversyScore: float | None = None
188
+ carbonSolutionsInvolvement: float | None = None
189
+ productInvolvements: list[ProductInvolvement] = []
190
+ sustainabilityRating: int | None = None
191
+ sustainabilityRatingCategoryName: str | None = None
192
+ oilSandsExtractionInvolvement: float | None = None
193
+ arcticOilAndGasExplorationInvolvement: float | None = None
194
+ thermalCoalPowerGenerationInvolvement: float | None = None
195
+ thermalCoalInvolvement: float | None = None
196
+ oilAndGasProductionInvolvement: float | None = None
197
+ environmentalRating: int | None = None
198
+ socialRating: int | None = None
199
+ governanceRating: int | None = None
200
+ svanen: bool | None = None
201
+ euArticleType: dict | str | None = None # Can be dict with 'value' and 'name' or string
202
+ aumCoveredCarbon: float | None = None
203
+ fossilFuelInvolvement: float | None = None
204
+ carbonRiskScore: float | None = None
205
+ sustainabilityDevelopmentGoals: list[SustainabilityGoal] = []
206
+
207
+
208
+ class FundChartDataPoint(BaseModel):
209
+ """Single data point in fund chart."""
210
+
211
+ model_config = MODEL_CONFIG
212
+
213
+ x: int # timestamp
214
+ y: float # value (typically percentage)
215
+
216
+
217
+ class FundChart(BaseModel):
218
+ """Fund chart data with historical performance."""
219
+
220
+ model_config = MODEL_CONFIG
221
+
222
+ id: str
223
+ dataSerie: list[FundChartDataPoint]
224
+ name: str | None = None
225
+ fromDate: str | None = None
226
+ toDate: str | None = None
227
+
228
+
229
+ class FundChartPeriod(BaseModel):
230
+ """Fund performance for a specific time period."""
231
+
232
+ model_config = MODEL_CONFIG
233
+
234
+ timePeriod: str
235
+ change: float
236
+ startDate: str
237
+
238
+
239
+ class FundDescription(BaseModel):
240
+ """Fund description and category information."""
241
+
242
+ model_config = MODEL_CONFIG
243
+
244
+ response: str
245
+ heading: str
246
+ detailedCategoryDescription: str
@@ -0,0 +1,123 @@
1
+ """Search result models matching Avanza API response structure."""
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ # Standard model config for all models
7
+ MODEL_CONFIG = ConfigDict(
8
+ populate_by_name=True,
9
+ str_strip_whitespace=True,
10
+ validate_assignment=True,
11
+ extra="allow", # Don't fail on extra fields from API
12
+ )
13
+
14
+
15
+ class SearchPrice(BaseModel):
16
+ """Price information for a search result."""
17
+
18
+ model_config = MODEL_CONFIG
19
+
20
+ last: str | None = None
21
+ currency: str | None = None
22
+ todayChangePercent: str | None = None
23
+ todayChangeValue: str | None = None
24
+ todayChangeDirection: int = 0
25
+ threeMonthsAgoChangePercent: str | None = None
26
+ threeMonthsAgoChangeDirection: int = 0
27
+ spread: str | None = None
28
+
29
+
30
+ class StockSector(BaseModel):
31
+ """Stock sector classification."""
32
+
33
+ model_config = MODEL_CONFIG
34
+
35
+ id: int
36
+ level: int
37
+ name: str
38
+ englishName: str
39
+ highlightedName: str | None = None
40
+
41
+
42
+ class FundTag(BaseModel):
43
+ """Fund classification tag."""
44
+
45
+ model_config = MODEL_CONFIG
46
+
47
+ title: str
48
+ category: str
49
+ tagCategory: str
50
+ highlightedTitle: str | None = None
51
+
52
+
53
+ class SearchHit(BaseModel):
54
+ """Individual search result from the Avanza API."""
55
+
56
+ model_config = MODEL_CONFIG
57
+
58
+ type: str
59
+ title: str
60
+ highlightedTitle: str
61
+ description: str
62
+ highlightedDescription: str
63
+ path: str | None = None
64
+ flagCode: str | None = None
65
+ orderBookId: str | None = None # May be None for certain instrument types
66
+ urlSlugName: str
67
+ tradeable: bool
68
+ sellable: bool
69
+ buyable: bool
70
+ price: SearchPrice | None = None # May be None for certain instrument types
71
+ stockSectors: list[StockSector] = Field(default_factory=list)
72
+ fundTags: list[FundTag] = Field(default_factory=list)
73
+ marketPlaceName: str
74
+ subType: str | None = None
75
+ highlightedSubType: str = ""
76
+
77
+
78
+ class TypeFacet(BaseModel):
79
+ """Facet count for an instrument type."""
80
+
81
+ model_config = MODEL_CONFIG
82
+
83
+ type: str
84
+ count: int
85
+
86
+
87
+ class SearchFacets(BaseModel):
88
+ """Search result facets with type counts."""
89
+
90
+ model_config = MODEL_CONFIG
91
+
92
+ types: list[TypeFacet]
93
+
94
+
95
+ class SearchFilter(BaseModel):
96
+ """Applied search filters."""
97
+
98
+ model_config = MODEL_CONFIG
99
+
100
+ types: list[str] = Field(default_factory=list)
101
+
102
+
103
+ class SearchPagination(BaseModel):
104
+ """Pagination information."""
105
+
106
+ model_config = MODEL_CONFIG
107
+
108
+ size: int
109
+ # Using field alias since 'from' is a Python keyword
110
+ from_: int = Field(alias="from")
111
+
112
+
113
+ class SearchResponse(BaseModel):
114
+ """Complete search API response from Avanza."""
115
+
116
+ model_config = MODEL_CONFIG
117
+
118
+ totalNumberOfHits: int
119
+ hits: list[SearchHit]
120
+ searchQuery: str
121
+ searchFilter: SearchFilter
122
+ facets: SearchFacets
123
+ pagination: SearchPagination
@@ -0,0 +1,268 @@
1
+ """Stock-related Pydantic models matching Avanza API."""
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ # Standard model config for all models
7
+ MODEL_CONFIG = ConfigDict(
8
+ populate_by_name=True,
9
+ str_strip_whitespace=True,
10
+ validate_assignment=True,
11
+ extra="allow", # Don't fail on extra fields from API
12
+ )
13
+
14
+
15
+ class Quote(BaseModel):
16
+ """Real-time stock quote data."""
17
+
18
+ model_config = MODEL_CONFIG
19
+
20
+ buy: float | None = None
21
+ sell: float | None = None
22
+ last: float | None = None
23
+ highest: float | None = None
24
+ lowest: float | None = None
25
+ change: float | None = None
26
+ changePercent: float | None = None
27
+ spread: float | None = None
28
+ timeOfLast: int | None = None
29
+ totalValueTraded: float | None = None
30
+ totalVolumeTraded: float | None = None
31
+ updated: int | None = None
32
+ volumeWeightedAveragePrice: float | None = None
33
+ isRealTime: bool | None = None
34
+
35
+
36
+ class Listing(BaseModel):
37
+ """Stock listing information."""
38
+
39
+ model_config = MODEL_CONFIG
40
+
41
+ shortName: str
42
+ tickerSymbol: str | None = None
43
+ countryCode: str | None = None
44
+ currency: str
45
+ marketPlaceCode: str | None = None
46
+ marketPlaceName: str
47
+ tickSizeListId: str | None = None
48
+ marketTradesAvailable: bool | None = None
49
+
50
+
51
+ class MarketPlace(BaseModel):
52
+ """Market place information."""
53
+
54
+ model_config = MODEL_CONFIG
55
+
56
+ marketOpen: bool
57
+ tradingTime: str | None = None
58
+ closingTime: str | None = None
59
+ country: str | None = None
60
+ name: str | None = None
61
+
62
+
63
+ class ShareInfo(BaseModel):
64
+ """Share value information."""
65
+
66
+ model_config = MODEL_CONFIG
67
+
68
+ value: float
69
+ currency: str
70
+
71
+
72
+ class ReportInfo(BaseModel):
73
+ """Company report information."""
74
+
75
+ model_config = MODEL_CONFIG
76
+
77
+ date: str
78
+ reportType: str
79
+
80
+
81
+ class Sector(BaseModel):
82
+ """Stock sector classification."""
83
+
84
+ model_config = MODEL_CONFIG
85
+
86
+ sectorId: str
87
+ sectorName: str
88
+
89
+
90
+ class KeyIndicators(BaseModel):
91
+ """Stock key financial indicators."""
92
+
93
+ model_config = MODEL_CONFIG
94
+
95
+ numberOfOwners: int | None = None
96
+ reportDate: str | None = None
97
+ volatility: float | None = None
98
+ beta: float | None = None
99
+ priceEarningsRatio: float | None = None
100
+ priceSalesRatio: float | None = None
101
+ evEbitRatio: float | None = None
102
+ returnOnEquity: float | None = None
103
+ returnOnTotalAssets: float | None = None
104
+ equityRatio: float | None = None
105
+ capitalTurnover: float | None = None
106
+ operatingProfitMargin: float | None = None
107
+ netMargin: float | None = None
108
+ marketCapital: ShareInfo | None = None
109
+ equityPerShare: ShareInfo | None = None
110
+ turnoverPerShare: ShareInfo | None = None
111
+ earningsPerShare: ShareInfo | None = None
112
+ dividendsPerYear: int | None = None
113
+ nextReport: ReportInfo | None = None
114
+ previousReport: ReportInfo | None = None
115
+ directYield: float | None = None
116
+
117
+
118
+ class HistoricalClosingPrices(BaseModel):
119
+ """Historical closing prices."""
120
+
121
+ model_config = MODEL_CONFIG
122
+
123
+ oneDay: float | None = None
124
+ oneWeek: float | None = None
125
+ oneMonth: float | None = None
126
+ threeMonths: float | None = None
127
+ startOfYear: float | None = None
128
+ oneYear: float | None = None
129
+ threeYears: float | None = None
130
+ fiveYears: float | None = None
131
+ start: float | None = None
132
+
133
+
134
+ class Company(BaseModel):
135
+ """Company information."""
136
+
137
+ model_config = MODEL_CONFIG
138
+
139
+ name: str | None = None
140
+ description: str | None = None
141
+ ceo: str | None = None
142
+ chairman: str | None = None
143
+ url: str | None = None
144
+ marketCapital: ShareInfo | None = None
145
+
146
+
147
+ class StockInfo(BaseModel):
148
+ """Complete stock information from Avanza API."""
149
+
150
+ model_config = MODEL_CONFIG
151
+
152
+ orderbookId: str
153
+ name: str
154
+ isin: str | None = None
155
+ instrumentId: str | None = None
156
+ sectors: list[Sector] = []
157
+ tradable: str | None = None
158
+ listing: Listing
159
+ marketPlace: MarketPlace | None = None
160
+ historicalClosingPrices: HistoricalClosingPrices | None = None
161
+ keyIndicators: KeyIndicators | None = None
162
+ quote: Quote
163
+ type: str | None = None
164
+ company: Company | None = None
165
+ relatedStocks: list | None = None
166
+ dividends: list | None = None
167
+
168
+
169
+ # === Models for additional endpoints ===
170
+
171
+
172
+ class OHLCDataPoint(BaseModel):
173
+ """OHLC (Open, High, Low, Close) data point for price charts."""
174
+
175
+ model_config = MODEL_CONFIG
176
+
177
+ timestamp: int
178
+ open: float
179
+ close: float
180
+ low: float
181
+ high: float
182
+ totalVolumeTraded: int
183
+
184
+
185
+ class ChartMetadata(BaseModel):
186
+ """Metadata for price chart responses."""
187
+
188
+ model_config = MODEL_CONFIG
189
+
190
+ resolution: str | dict | None = None # Can be string or dict
191
+
192
+
193
+ class StockChart(BaseModel):
194
+ """Stock price chart data with OHLC values."""
195
+
196
+ model_config = MODEL_CONFIG
197
+
198
+ ohlc: list[OHLCDataPoint]
199
+ metadata: ChartMetadata | None = None
200
+ from_: int | str | None = Field(None, alias="from") # 'from' is a Python keyword
201
+ to: int | str | None = None # Can be int or date string
202
+ previousClosingPrice: float | None = None
203
+
204
+
205
+ class MarketplaceInfo(BaseModel):
206
+ """Marketplace status and trading hours."""
207
+
208
+ model_config = MODEL_CONFIG
209
+
210
+ marketOpen: bool
211
+ timeLeftMs: int | None = None
212
+ openingTime: str | None = None
213
+ todayClosingTime: str | None = None
214
+ normalClosingTime: str | None = None
215
+
216
+
217
+ class BrokerTradeSummary(BaseModel):
218
+ """Summary of broker trades for a stock."""
219
+
220
+ model_config = MODEL_CONFIG
221
+
222
+ brokerCode: str
223
+ sellVolume: int
224
+ buyVolume: int
225
+ netBuyVolume: int
226
+ brokerName: str
227
+
228
+
229
+ class Trade(BaseModel):
230
+ """Individual trade information."""
231
+
232
+ model_config = MODEL_CONFIG
233
+
234
+ buyer: str
235
+ seller: str
236
+ dealTime: int
237
+ price: float
238
+ volume: int
239
+ matchedOnMarket: bool
240
+ cancelled: bool
241
+
242
+
243
+ class OrderSide(BaseModel):
244
+ """Buy or sell side of an order."""
245
+
246
+ model_config = MODEL_CONFIG
247
+
248
+ price: float
249
+ volume: int
250
+ priceString: str
251
+
252
+
253
+ class OrderLevel(BaseModel):
254
+ """Single level in the order book depth."""
255
+
256
+ model_config = MODEL_CONFIG
257
+
258
+ buySide: OrderSide | None = None
259
+ sellSide: OrderSide | None = None
260
+
261
+
262
+ class OrderDepth(BaseModel):
263
+ """Order book depth showing buy and sell orders."""
264
+
265
+ model_config = MODEL_CONFIG
266
+
267
+ receivedTime: int | None = None # May be None when market is closed
268
+ levels: list[OrderLevel] = [] # Empty list when no order book data available
@@ -0,0 +1,6 @@
1
+ """MCP prompts for Avanza API."""
2
+
3
+ # Import to register prompts via decorators
4
+ from . import analysis # noqa: F401
5
+
6
+ __all__ = ["analysis"]