feat: add Freetrade portfolio tracker with price fetching and holdings CSV support
This commit is contained in:
commit
38ecbd57e0
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
holdings.csv
|
||||||
52
README.md
Normal file
52
README.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Freetrade Portfolio Tracker
|
||||||
|
|
||||||
|
Fetches live stock prices from Freetrade and calculates portfolio holdings values.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m venv venv
|
||||||
|
venv\Scripts\activate # Windows
|
||||||
|
source venv/bin/activate # Linux/Mac
|
||||||
|
|
||||||
|
pip install requests beautifulsoup4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Copy the sample file and add your own positions:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp holdings.sample.csv holdings.csv
|
||||||
|
```
|
||||||
|
|
||||||
|
Format (`ticker,market,shares`):
|
||||||
|
|
||||||
|
```csv
|
||||||
|
GGP,GB,100
|
||||||
|
PAF,GB,200
|
||||||
|
SVM,US,50
|
||||||
|
```
|
||||||
|
|
||||||
|
Markets: `GB` for UK stocks, `US` for US-listed stocks.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python price.py # uses holdings.csv by default
|
||||||
|
python price.py my_portfolio.csv # custom file
|
||||||
|
```
|
||||||
|
|
||||||
|
Output (JSON):
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"ticker": "GGP",
|
||||||
|
"market": "GB",
|
||||||
|
"shares": 100,
|
||||||
|
"price": 7.67,
|
||||||
|
"total_value": 767.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
3
holdings.sample.csv
Normal file
3
holdings.sample.csv
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
GGP,GB,100
|
||||||
|
PAF,GB,200
|
||||||
|
SVM,US,50
|
||||||
|
35
price.py
Normal file
35
price.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import re
|
||||||
|
import json
|
||||||
|
import csv
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
def get_price(market, ticker):
|
||||||
|
url = f"https://web.freetrade.io/universe/{market}/{ticker}"
|
||||||
|
response = requests.get(url, headers={
|
||||||
|
"User-Agent": "Mozilla/5.0"
|
||||||
|
}, timeout=20)
|
||||||
|
text = BeautifulSoup(response.text, "html.parser").get_text(" ", strip=True)
|
||||||
|
m = re.search(r"Latest price\s*:\s*[£$]([0-9,.]+)", text)
|
||||||
|
if m:
|
||||||
|
return float(m.group(1).replace(",", ""))
|
||||||
|
return None
|
||||||
|
|
||||||
|
holdings_file = sys.argv[1] if len(sys.argv) > 1 else "holdings.csv"
|
||||||
|
|
||||||
|
results = []
|
||||||
|
with open(holdings_file) as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
for row in reader:
|
||||||
|
ticker, market, shares = row[0].strip(), row[1].strip(), int(row[2].strip())
|
||||||
|
price = get_price(market, ticker)
|
||||||
|
results.append({
|
||||||
|
"ticker": ticker,
|
||||||
|
"market": market,
|
||||||
|
"shares": shares,
|
||||||
|
"price": price,
|
||||||
|
"total_value": round(price * shares, 2) if price is not None else None
|
||||||
|
})
|
||||||
|
|
||||||
|
print(json.dumps(results, indent=2))
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
requests
|
||||||
|
beautifulsoup4
|
||||||
Loading…
x
Reference in New Issue
Block a user