avanza-mcp 1.2.0__tar.gz → 1.3.0__tar.gz
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-1.2.0 → avanza_mcp-1.3.0}/PKG-INFO +20 -1
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/README.md +19 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/justfile +1 -1
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/__init__.py +1 -1
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/prompts/__init__.py +2 -1
- avanza_mcp-1.3.0/src/avanza_mcp/prompts/workflows.py +488 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/resources/__init__.py +2 -1
- avanza_mcp-1.3.0/src/avanza_mcp/resources/usage.py +503 -0
- avanza_mcp-1.2.0/CHANGELOG.md +0 -60
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/.github/PUBLISHING.md +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/.github/workflows/ci.yml +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/.github/workflows/publish.yml +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/.gitignore +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/.python-version +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/CLAUDE.md +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/DEVELOPMENT.md +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/LICENSE.md +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/pyproject.toml +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/client/__init__.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/client/base.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/client/endpoints.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/client/exceptions.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/__init__.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/certificate.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/chart.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/common.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/etf.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/filter.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/fund.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/future_forward.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/instrument_data.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/search.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/stock.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/models/warrant.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/prompts/analysis.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/resources/instruments.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/services/__init__.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/services/market_data_service.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/services/search_service.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/__init__.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/certificates.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/etfs.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/funds.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/futures_forwards.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/instrument_data.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/market_data.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/search.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/src/avanza_mcp/tools/warrants.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/__init__.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/conftest.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/__init__.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_certificates_integration.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_chart_integration.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_etfs_integration.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_futures_forwards_integration.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_instrument_data_integration.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_tools.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/integration/test_warrants_integration.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/__init__.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_certificate_models.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_chart_models.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_client.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_etf_models.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_filter_models.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_future_forward_models.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_instrument_data_models.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_models.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/tests/unit/test_warrant_models.py +0 -0
- {avanza_mcp-1.2.0 → avanza_mcp-1.3.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: avanza-mcp
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: MCP server for Avanza public market data API - Swedish stocks, funds, and more
|
|
5
5
|
License-File: LICENSE.md
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -95,10 +95,29 @@ The author of this software is not responsible for any indirect damages (foresee
|
|
|
95
95
|
|
|
96
96
|
## 💡 MCP Prompts
|
|
97
97
|
|
|
98
|
+
### Analysis Prompts
|
|
98
99
|
- `analyze_stock` - Comprehensive stock analysis workflow
|
|
99
100
|
- `compare_funds` - Multi-fund comparison template
|
|
100
101
|
- `screen_dividend_stocks` - Dividend stock screening
|
|
101
102
|
|
|
103
|
+
### Workflow Prompts (Teach AI Efficient Data Fetching)
|
|
104
|
+
- `search_and_analyze_instrument` - Guide for finding and analyzing instruments efficiently
|
|
105
|
+
- `filter_instruments_efficiently` - Guide for filtering large datasets with pagination
|
|
106
|
+
- `compare_multiple_instruments` - Guide for comparing instruments efficiently
|
|
107
|
+
- `explore_market_segment` - Guide for exploring market segments
|
|
108
|
+
- `get_historical_analysis` - Guide for analyzing historical data
|
|
109
|
+
- `screen_by_criteria` - Guide for screening by custom criteria
|
|
110
|
+
|
|
111
|
+
## 📚 MCP Resources
|
|
112
|
+
|
|
113
|
+
### Documentation Resources
|
|
114
|
+
- `avanza://docs/usage` - Comprehensive usage guide for AI assistants
|
|
115
|
+
- `avanza://docs/quick-start` - Quick reference for common tasks
|
|
116
|
+
|
|
117
|
+
### Instrument Resources
|
|
118
|
+
- `avanza://stock/{instrument_id}` - Get stock information as markdown
|
|
119
|
+
- `avanza://fund/{instrument_id}` - Get fund information as markdown
|
|
120
|
+
|
|
102
121
|
|
|
103
122
|
### Configuration for MCP Clients
|
|
104
123
|
|
|
@@ -78,10 +78,29 @@ The author of this software is not responsible for any indirect damages (foresee
|
|
|
78
78
|
|
|
79
79
|
## 💡 MCP Prompts
|
|
80
80
|
|
|
81
|
+
### Analysis Prompts
|
|
81
82
|
- `analyze_stock` - Comprehensive stock analysis workflow
|
|
82
83
|
- `compare_funds` - Multi-fund comparison template
|
|
83
84
|
- `screen_dividend_stocks` - Dividend stock screening
|
|
84
85
|
|
|
86
|
+
### Workflow Prompts (Teach AI Efficient Data Fetching)
|
|
87
|
+
- `search_and_analyze_instrument` - Guide for finding and analyzing instruments efficiently
|
|
88
|
+
- `filter_instruments_efficiently` - Guide for filtering large datasets with pagination
|
|
89
|
+
- `compare_multiple_instruments` - Guide for comparing instruments efficiently
|
|
90
|
+
- `explore_market_segment` - Guide for exploring market segments
|
|
91
|
+
- `get_historical_analysis` - Guide for analyzing historical data
|
|
92
|
+
- `screen_by_criteria` - Guide for screening by custom criteria
|
|
93
|
+
|
|
94
|
+
## 📚 MCP Resources
|
|
95
|
+
|
|
96
|
+
### Documentation Resources
|
|
97
|
+
- `avanza://docs/usage` - Comprehensive usage guide for AI assistants
|
|
98
|
+
- `avanza://docs/quick-start` - Quick reference for common tasks
|
|
99
|
+
|
|
100
|
+
### Instrument Resources
|
|
101
|
+
- `avanza://stock/{instrument_id}` - Get stock information as markdown
|
|
102
|
+
- `avanza://fund/{instrument_id}` - Get fund information as markdown
|
|
103
|
+
|
|
85
104
|
|
|
86
105
|
### Configuration for MCP Clients
|
|
87
106
|
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
"""Workflow prompt templates emphasizing when to provide scripts vs using tools."""
|
|
2
|
+
|
|
3
|
+
from .. import mcp
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@mcp.prompt()
|
|
7
|
+
def bulk_data_script_guide(item_count: int, operation_type: str) -> str:
|
|
8
|
+
"""Guide for providing scripts for bulk data operations.
|
|
9
|
+
|
|
10
|
+
Use this when user requests data for 50+ items.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
item_count: Number of items to fetch
|
|
14
|
+
operation_type: Type of operation (e.g., "stock analysis", "ETF screening")
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Prompt explaining why and how to provide a script
|
|
18
|
+
"""
|
|
19
|
+
return f"""For {operation_type} involving {item_count} items, I should provide a script instead of using MCP tools.
|
|
20
|
+
|
|
21
|
+
## Why Not Use MCP Tools?
|
|
22
|
+
|
|
23
|
+
Making {item_count} MCP tool calls would:
|
|
24
|
+
- Take 5-10 minutes to complete
|
|
25
|
+
- Overload the MCP connection
|
|
26
|
+
- Be inefficient and error-prone
|
|
27
|
+
|
|
28
|
+
## What to Provide Instead
|
|
29
|
+
|
|
30
|
+
I'll give you a ready-to-run script that:
|
|
31
|
+
- Fetches all {item_count} items in parallel (30-60 seconds)
|
|
32
|
+
- Saves data to a file for analysis
|
|
33
|
+
- Includes error handling and rate limiting
|
|
34
|
+
|
|
35
|
+
### Python Script Template (Recommended)
|
|
36
|
+
```python
|
|
37
|
+
import httpx
|
|
38
|
+
import asyncio
|
|
39
|
+
import json
|
|
40
|
+
from datetime import datetime
|
|
41
|
+
|
|
42
|
+
async def fetch_item(client, item_id):
|
|
43
|
+
\"\"\"Fetch single item with error handling.\"\"\"
|
|
44
|
+
try:
|
|
45
|
+
url = "https://www.avanza.se/_api/..." # Specific endpoint
|
|
46
|
+
response = await client.get(url, timeout=30.0)
|
|
47
|
+
response.raise_for_status()
|
|
48
|
+
return response.json()
|
|
49
|
+
except Exception as e:
|
|
50
|
+
print(f"Error fetching {{item_id}}: {{e}}")
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
async def main():
|
|
54
|
+
item_ids = [...] # Your {item_count} IDs
|
|
55
|
+
|
|
56
|
+
print(f"Fetching {{len(item_ids)}} items...")
|
|
57
|
+
|
|
58
|
+
async with httpx.AsyncClient() as client:
|
|
59
|
+
tasks = [fetch_item(client, iid) for iid in item_ids]
|
|
60
|
+
results = await asyncio.gather(*tasks)
|
|
61
|
+
|
|
62
|
+
# Filter out errors
|
|
63
|
+
valid_results = [r for r in results if r is not None]
|
|
64
|
+
|
|
65
|
+
# Save to file
|
|
66
|
+
output_file = f"data_{{datetime.now().strftime('%Y%m%d_%H%M%S')}}.json"
|
|
67
|
+
with open(output_file, 'w') as f:
|
|
68
|
+
json.dump(valid_results, f, indent=2)
|
|
69
|
+
|
|
70
|
+
print(f"✅ Fetched {{len(valid_results)}}/{{len(item_ids)}} items")
|
|
71
|
+
print(f"📁 Saved to {{output_file}}")
|
|
72
|
+
|
|
73
|
+
if __name__ == "__main__":
|
|
74
|
+
asyncio.run(main())
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### How to Run
|
|
78
|
+
```bash
|
|
79
|
+
# Install dependencies
|
|
80
|
+
pip install httpx
|
|
81
|
+
|
|
82
|
+
# Run the script
|
|
83
|
+
python fetch_data.py
|
|
84
|
+
|
|
85
|
+
# Analyze the results
|
|
86
|
+
# I can help you analyze the JSON file after you run this!
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Next Steps
|
|
90
|
+
|
|
91
|
+
After you run the script:
|
|
92
|
+
1. Share the output file with me (or key findings)
|
|
93
|
+
2. I can help analyze patterns, create visualizations, etc.
|
|
94
|
+
3. We can drill down into specific interesting items using MCP tools
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@mcp.prompt()
|
|
99
|
+
def decide_tool_or_script(user_request: str, estimated_items: int) -> str:
|
|
100
|
+
"""Help decide whether to use tools or provide a script.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
user_request: What the user wants to do
|
|
104
|
+
estimated_items: Estimated number of items to process
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Decision prompt
|
|
108
|
+
"""
|
|
109
|
+
if estimated_items <= 20:
|
|
110
|
+
approach = "use MCP tools"
|
|
111
|
+
reason = "small enough for interactive exploration"
|
|
112
|
+
elif estimated_items <= 50:
|
|
113
|
+
approach = "ask user preference"
|
|
114
|
+
reason = "medium size - tools work but script is faster"
|
|
115
|
+
else:
|
|
116
|
+
approach = "provide script"
|
|
117
|
+
reason = "too many for MCP tools - script is much more efficient"
|
|
118
|
+
|
|
119
|
+
return f"""Request: "{user_request}"
|
|
120
|
+
Estimated items: {estimated_items}
|
|
121
|
+
|
|
122
|
+
## Decision: {approach.upper()}
|
|
123
|
+
|
|
124
|
+
**Reason**: {reason}
|
|
125
|
+
|
|
126
|
+
**Approach**:
|
|
127
|
+
{"I'll use MCP tools to fetch data interactively." if estimated_items <= 20 else
|
|
128
|
+
"I'll ask if you prefer tools (interactive) or script (faster)." if estimated_items <= 50 else
|
|
129
|
+
"I'll provide a Python/bash script for efficient bulk fetching."}
|
|
130
|
+
|
|
131
|
+
{"" if estimated_items <= 20 else f'''
|
|
132
|
+
## Script Template
|
|
133
|
+
|
|
134
|
+
For {estimated_items} items, here's what I'll provide:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
# Efficient parallel fetching
|
|
138
|
+
import httpx
|
|
139
|
+
import asyncio
|
|
140
|
+
|
|
141
|
+
async def fetch_all(ids):
|
|
142
|
+
async with httpx.AsyncClient() as client:
|
|
143
|
+
tasks = [client.get(f"https://www.avanza.se/_api/.../{{id}}") for id in ids]
|
|
144
|
+
responses = await asyncio.gather(*tasks, return_exceptions=True)
|
|
145
|
+
return [r.json() for r in responses if hasattr(r, 'json')]
|
|
146
|
+
|
|
147
|
+
# Run and save
|
|
148
|
+
results = asyncio.run(fetch_all(your_{estimated_items}_ids))
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This completes in ~30-60 seconds vs {estimated_items * 2}+ seconds with MCP tools.
|
|
152
|
+
'''}
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@mcp.prompt()
|
|
157
|
+
def filter_large_dataset(instrument_type: str, criteria: str) -> str:
|
|
158
|
+
"""Guide for filtering large datasets using API endpoints directly.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
instrument_type: Type (certificates, etfs, warrants, etc.)
|
|
162
|
+
criteria: Filtering criteria
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Prompt with curl/script for bulk filtering
|
|
166
|
+
"""
|
|
167
|
+
filter_endpoints = {
|
|
168
|
+
"certificates": "market-certificate-filter",
|
|
169
|
+
"etfs": "market-etf-filter",
|
|
170
|
+
"warrants": "market-warrant-filter",
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
endpoint = filter_endpoints.get(instrument_type.lower(), "market-etf-filter")
|
|
174
|
+
|
|
175
|
+
return f"""To screen {instrument_type} with criteria: {criteria}
|
|
176
|
+
|
|
177
|
+
## Use the Filter API Directly (Don't Loop Through MCP Tools)
|
|
178
|
+
|
|
179
|
+
Instead of calling MCP tools repeatedly, use the filter endpoint:
|
|
180
|
+
|
|
181
|
+
### Option 1: curl Command (Quick & Simple)
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
curl 'https://www.avanza.se/_api/{endpoint}/' \\
|
|
185
|
+
-H 'Content-Type: application/json' \\
|
|
186
|
+
--data-raw '{{
|
|
187
|
+
"filter": {{
|
|
188
|
+
{{"// Add your filters here based on criteria"}}
|
|
189
|
+
}},
|
|
190
|
+
"offset": 0,
|
|
191
|
+
"limit": 100,
|
|
192
|
+
"sortBy": {{"field": "name", "order": "asc"}}
|
|
193
|
+
}}' | jq '.' > results.json
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Option 2: Python Script (For Processing)
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
import httpx
|
|
200
|
+
import json
|
|
201
|
+
|
|
202
|
+
def filter_{instrument_type}(filters, limit=100):
|
|
203
|
+
\"\"\"Filter {instrument_type} directly via API.\"\"\"
|
|
204
|
+
url = "https://www.avanza.se/_api/{endpoint}/"
|
|
205
|
+
|
|
206
|
+
payload = {{
|
|
207
|
+
"filter": filters,
|
|
208
|
+
"offset": 0,
|
|
209
|
+
"limit": limit,
|
|
210
|
+
"sortBy": {{"field": "name", "order": "asc"}}
|
|
211
|
+
}}
|
|
212
|
+
|
|
213
|
+
response = httpx.post(url, json=payload, timeout=30.0)
|
|
214
|
+
return response.json()
|
|
215
|
+
|
|
216
|
+
# Example: Apply your criteria
|
|
217
|
+
filters = {{
|
|
218
|
+
# Map your criteria to filter parameters
|
|
219
|
+
}}
|
|
220
|
+
|
|
221
|
+
results = filter_{instrument_type}(filters, limit=100)
|
|
222
|
+
|
|
223
|
+
# Save
|
|
224
|
+
with open('{instrument_type}_filtered.json', 'w') as f:
|
|
225
|
+
json.dump(results, f, indent=2)
|
|
226
|
+
|
|
227
|
+
print(f"Found {{results.get('totalNumberOfOrderbooks', 0)}} matches")
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Available Filter Parameters
|
|
231
|
+
|
|
232
|
+
**For {instrument_type}**:
|
|
233
|
+
{_get_filter_params(instrument_type)}
|
|
234
|
+
|
|
235
|
+
## Pagination for Large Results
|
|
236
|
+
|
|
237
|
+
If results exceed 100:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# Get next page
|
|
241
|
+
curl 'https://www.avanza.se/_api/{endpoint}/' \\
|
|
242
|
+
-H 'Content-Type: application/json' \\
|
|
243
|
+
--data-raw '{{
|
|
244
|
+
"filter": {{}},
|
|
245
|
+
"offset": 100, # Next page
|
|
246
|
+
"limit": 100,
|
|
247
|
+
"sortBy": {{"field": "name", "order": "asc"}}
|
|
248
|
+
}}'
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## After Fetching
|
|
252
|
+
|
|
253
|
+
Once you have the results:
|
|
254
|
+
1. I can help analyze patterns in the data
|
|
255
|
+
2. Pick interesting items for deep dives using MCP tools
|
|
256
|
+
3. Create comparisons or visualizations
|
|
257
|
+
"""
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _get_filter_params(instrument_type: str) -> str:
|
|
261
|
+
"""Get filter parameters for instrument type."""
|
|
262
|
+
params = {
|
|
263
|
+
"certificates": """
|
|
264
|
+
- `directions`: ["long", "short"]
|
|
265
|
+
- `leverages`: [1.0, 2.0, 3.0, ...]
|
|
266
|
+
- `issuers`: ["Valour", "WisdomTree", ...]
|
|
267
|
+
- `underlyingInstruments`: [orderbookIds]
|
|
268
|
+
""",
|
|
269
|
+
"etfs": """
|
|
270
|
+
- `exposures`: ["usa", "europe", "global", ...]
|
|
271
|
+
- `assetCategories`: ["stock", "bond", "commodity", ...]
|
|
272
|
+
- `riskScores`: ["risk_one", "risk_two", ...]
|
|
273
|
+
- `managementFee`: (use sortBy to filter)
|
|
274
|
+
""",
|
|
275
|
+
"warrants": """
|
|
276
|
+
- `directions`: ["long", "short"]
|
|
277
|
+
- `subTypes`: ["TURBO", "MINI", ...]
|
|
278
|
+
- `issuers`: ["Societe Generale", ...]
|
|
279
|
+
- `underlyingInstruments`: [orderbookIds]
|
|
280
|
+
""",
|
|
281
|
+
}
|
|
282
|
+
return params.get(instrument_type.lower(), "Check API documentation")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@mcp.prompt()
|
|
286
|
+
def analyze_vs_fetch(operation_description: str, requires_bulk_data: bool) -> str:
|
|
287
|
+
"""Distinguish between analysis (use tools) and fetching (use scripts).
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
operation_description: What the user wants to do
|
|
291
|
+
requires_bulk_data: Whether it needs 50+ items
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Guidance prompt
|
|
295
|
+
"""
|
|
296
|
+
if requires_bulk_data:
|
|
297
|
+
return f"""Operation: "{operation_description}"
|
|
298
|
+
|
|
299
|
+
## This Requires Bulk Data Fetching
|
|
300
|
+
|
|
301
|
+
### Two-Step Approach:
|
|
302
|
+
|
|
303
|
+
**Step 1: Fetch Data (You Do This)**
|
|
304
|
+
I'll provide a script to fetch the data:
|
|
305
|
+
- Runs in 30-60 seconds
|
|
306
|
+
- Saves to JSON/CSV file
|
|
307
|
+
- Handles errors gracefully
|
|
308
|
+
|
|
309
|
+
**Step 2: Analyze Data (I Help With This)**
|
|
310
|
+
After you have the data:
|
|
311
|
+
- Share the file or key metrics
|
|
312
|
+
- I'll use MCP tools for specific deep dives
|
|
313
|
+
- We can explore patterns together
|
|
314
|
+
|
|
315
|
+
### Why This Approach?
|
|
316
|
+
|
|
317
|
+
- **Efficient**: Parallel fetching vs sequential MCP calls
|
|
318
|
+
- **Reliable**: Better error handling, can resume if failed
|
|
319
|
+
- **Flexible**: You own the data for other analyses
|
|
320
|
+
|
|
321
|
+
### Script I'll Provide
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
# Fast parallel data fetcher
|
|
325
|
+
import httpx, asyncio, json
|
|
326
|
+
|
|
327
|
+
async def fetch_all():
|
|
328
|
+
ids = [...] # Your list
|
|
329
|
+
async with httpx.AsyncClient() as c:
|
|
330
|
+
tasks = [c.get(f"https://www.avanza.se/_api/.../{{id}}") for id in ids]
|
|
331
|
+
return await asyncio.gather(*tasks, return_exceptions=True)
|
|
332
|
+
|
|
333
|
+
results = asyncio.run(fetch_all())
|
|
334
|
+
with open('data.json', 'w') as f:
|
|
335
|
+
json.dump([r.json() for r in results if hasattr(r, 'json')], f)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Next: Share Results & Analyze Together
|
|
339
|
+
"""
|
|
340
|
+
else:
|
|
341
|
+
return f"""Operation: "{operation_description}"
|
|
342
|
+
|
|
343
|
+
## This Can Use MCP Tools Directly
|
|
344
|
+
|
|
345
|
+
I'll use MCP tools interactively because:
|
|
346
|
+
- Small enough dataset (< 20 items)
|
|
347
|
+
- Interactive exploration is valuable
|
|
348
|
+
- Results available immediately
|
|
349
|
+
|
|
350
|
+
I'll proceed with using the appropriate MCP tools for this analysis.
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
@mcp.prompt()
|
|
355
|
+
def script_template_selector(
|
|
356
|
+
task: str, data_source: str, output_format: str = "json"
|
|
357
|
+
) -> str:
|
|
358
|
+
"""Provide appropriate script template based on task.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
task: What to accomplish
|
|
362
|
+
data_source: Where to get data (endpoint type)
|
|
363
|
+
output_format: Desired output format
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Complete script template
|
|
367
|
+
"""
|
|
368
|
+
return f"""Task: {task}
|
|
369
|
+
Data Source: {data_source}
|
|
370
|
+
Output: {output_format}
|
|
371
|
+
|
|
372
|
+
## Recommended Script
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
#!/usr/bin/env python3
|
|
376
|
+
\"\"\"
|
|
377
|
+
{task}
|
|
378
|
+
|
|
379
|
+
Fetches data from Avanza's public API and saves to {output_format}.
|
|
380
|
+
No authentication required.
|
|
381
|
+
\"\"\"
|
|
382
|
+
|
|
383
|
+
import httpx
|
|
384
|
+
import asyncio
|
|
385
|
+
import json
|
|
386
|
+
from pathlib import Path
|
|
387
|
+
from datetime import datetime
|
|
388
|
+
|
|
389
|
+
# Configuration
|
|
390
|
+
BASE_URL = "https://www.avanza.se/_api"
|
|
391
|
+
OUTPUT_DIR = Path("avanza_data")
|
|
392
|
+
MAX_CONCURRENT = 10 # Limit concurrent requests
|
|
393
|
+
|
|
394
|
+
async def fetch_item(client, item_id, semaphore):
|
|
395
|
+
\"\"\"Fetch single item with rate limiting.\"\"\"
|
|
396
|
+
async with semaphore:
|
|
397
|
+
try:
|
|
398
|
+
url = f"{{BASE_URL}}/{data_source}/{{item_id}}"
|
|
399
|
+
response = await client.get(url, timeout=30.0)
|
|
400
|
+
response.raise_for_status()
|
|
401
|
+
return {{"id": item_id, "data": response.json(), "error": None}}
|
|
402
|
+
except Exception as e:
|
|
403
|
+
return {{"id": item_id, "data": None, "error": str(e)}}
|
|
404
|
+
|
|
405
|
+
async def main():
|
|
406
|
+
# TODO: Replace with your actual IDs
|
|
407
|
+
item_ids = [
|
|
408
|
+
# "5247", # Example: Volvo
|
|
409
|
+
# "5361", # Example: Ericsson
|
|
410
|
+
# Add your IDs here
|
|
411
|
+
]
|
|
412
|
+
|
|
413
|
+
if not item_ids:
|
|
414
|
+
print("⚠️ Please add item IDs to the script")
|
|
415
|
+
return
|
|
416
|
+
|
|
417
|
+
print(f"📊 Fetching {{len(item_ids)}} items...")
|
|
418
|
+
|
|
419
|
+
# Create output directory
|
|
420
|
+
OUTPUT_DIR.mkdir(exist_ok=True)
|
|
421
|
+
|
|
422
|
+
# Rate limiting semaphore
|
|
423
|
+
semaphore = asyncio.Semaphore(MAX_CONCURRENT)
|
|
424
|
+
|
|
425
|
+
# Fetch all items
|
|
426
|
+
async with httpx.AsyncClient() as client:
|
|
427
|
+
tasks = [fetch_item(client, iid, semaphore) for iid in item_ids]
|
|
428
|
+
results = await asyncio.gather(*tasks)
|
|
429
|
+
|
|
430
|
+
# Separate successful and failed
|
|
431
|
+
successful = [r for r in results if r["error"] is None]
|
|
432
|
+
failed = [r for r in results if r["error"] is not None]
|
|
433
|
+
|
|
434
|
+
# Save results
|
|
435
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
436
|
+
output_file = OUTPUT_DIR / f"results_{{timestamp}}.{output_format}"
|
|
437
|
+
|
|
438
|
+
with open(output_file, "w", encoding="utf-8") as f:
|
|
439
|
+
json.dump(
|
|
440
|
+
{{
|
|
441
|
+
"metadata": {{
|
|
442
|
+
"timestamp": timestamp,
|
|
443
|
+
"total": len(item_ids),
|
|
444
|
+
"successful": len(successful),
|
|
445
|
+
"failed": len(failed),
|
|
446
|
+
}},
|
|
447
|
+
"data": [r["data"] for r in successful],
|
|
448
|
+
"errors": [{{"id": r["id"], "error": r["error"]}} for r in failed],
|
|
449
|
+
}},
|
|
450
|
+
f,
|
|
451
|
+
indent=2,
|
|
452
|
+
ensure_ascii=False,
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
# Print summary
|
|
456
|
+
print(f"\\n✅ Successfully fetched: {{len(successful)}}/{{len(item_ids)}}")
|
|
457
|
+
if failed:
|
|
458
|
+
print(f"❌ Failed: {{len(failed)}}")
|
|
459
|
+
for fail in failed[:5]: # Show first 5 errors
|
|
460
|
+
print(f" - {{fail['id']}}: {{fail['error']}}")
|
|
461
|
+
print(f"\\n📁 Saved to: {{output_file}}")
|
|
462
|
+
|
|
463
|
+
if __name__ == "__main__":
|
|
464
|
+
asyncio.run(main())
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## How to Use
|
|
468
|
+
|
|
469
|
+
1. **Save the script**: `save as fetch_data.py`
|
|
470
|
+
2. **Add your IDs**: Edit the `item_ids` list
|
|
471
|
+
3. **Install dependency**: `pip install httpx`
|
|
472
|
+
4. **Run**: `python fetch_data.py`
|
|
473
|
+
|
|
474
|
+
## What You Get
|
|
475
|
+
|
|
476
|
+
- JSON file with all results
|
|
477
|
+
- Error tracking for failed requests
|
|
478
|
+
- Timestamp for record keeping
|
|
479
|
+
- Summary of success/failure rates
|
|
480
|
+
|
|
481
|
+
## After Running
|
|
482
|
+
|
|
483
|
+
Share the results file and I can help you:
|
|
484
|
+
- Analyze patterns
|
|
485
|
+
- Create visualizations
|
|
486
|
+
- Filter and sort data
|
|
487
|
+
- Deep dive on specific items
|
|
488
|
+
"""
|