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,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
|