227 lines
7.7 KiB
Markdown
227 lines
7.7 KiB
Markdown
# Multi-Asset Portfolio Simulator (Yahoo Finance, Python)
|
||
|
||
Szybki symulator wielo-aktywny z jedną wspólną kasą.
|
||
Działa w pętli co 2 minuty i dla paczki tickerów:
|
||
|
||
- pobiera **batch** danych z Yahoo jednym zapytaniem (`yfinance.download`),
|
||
- **dopina tylko nowe świece** (cache ostatniego czasu per ticker),
|
||
- liczy sygnał na **close** każdej świecy,
|
||
- **egzekwuje** zlecenie na **kolejnym open** (bardziej realistycznie),
|
||
- prowadzi portfel (wspólne saldo, SL/TP intrabar, prowizja, poślizg),
|
||
- zapisuje wyniki do `dane/` oraz liczy metryki **MaxDD, Sharpe, CAGR**.
|
||
|
||
> Uwaga: to **symulacja** oparta o dane z Yahoo. Nie składa prawdziwych zleceń.
|
||
|
||
---
|
||
|
||
## Funkcje (skrót)
|
||
|
||
- Multi-asset (~20 domyślnych: krypto + forex + złoto `GC=F`)
|
||
- Batch download (znacznie szybciej niż pojedynczo)
|
||
- Tylko nowe świece (bez pobierania pełnej historii co rundę)
|
||
- Sygnał: EMA200 + SMA(20/50) + RSI(14) + ATR-SL/TP + filtry:
|
||
**MACD, Stochastic, Bollinger, ADX, Supertrend**
|
||
- Egzekucja na **kolejnym OPEN**
|
||
- Portfel: **jedna** kasa dla wszystkich instrumentów, risk per trade (% equity)
|
||
- Zapisy CSV + **portfolio_summary** z MaxDD / Sharpe (annual) / CAGR
|
||
|
||
---
|
||
|
||
## Wymagania
|
||
|
||
- Python 3.9+
|
||
- `pip install yfinance pandas numpy`
|
||
|
||
---
|
||
|
||
## Szybki start
|
||
|
||
```bash
|
||
# 1) instalacja
|
||
chmod +x install.sh
|
||
./install.sh
|
||
|
||
# 2) uruchomienie (wirtualne środowisko)
|
||
source venv/bin/activate
|
||
python app.py
|
||
|
||
# (opcjonalnie w tle)
|
||
nohup python app.py > output.log 2>&1 &
|
||
```
|
||
|
||
Logi lecą do `stdout` (lub do `output.log` przy `nohup`).
|
||
|
||
---
|
||
|
||
## Struktura plików
|
||
|
||
```
|
||
project/
|
||
├─ app.py # główna pętla: batch → nowe świece → decyzje → egzekucja → zapis
|
||
├─ config.py # konfiguracja (tickery, interwał, risk, katalog 'dane', itp.)
|
||
├─ data.py # fetch_batch(): pobieranie paczki tickerów z yfinance
|
||
├─ indicators.py # implementacje wskaźników (SMA/EMA/RSI/ATR/MACD/…)
|
||
├─ strategy.py # evaluate_signal(): logika generowania sygnałów + SL/TP + rpu
|
||
├─ portfolio.py # model portfela: pozycje, pending orders, SL/TP, PnL, equity
|
||
├─ metrics.py # metryki portfelowe: MaxDD, Sharpe (annualized), CAGR
|
||
├─ io_utils.py # zapisy do 'dane/': trades, equity, summary; tworzenie katalogów
|
||
├─ requirements.txt # zależności (yfinance, pandas, numpy)
|
||
└─ install.sh # instalator: venv + pip install -r requirements.txt
|
||
```
|
||
|
||
### Co jest w każdym pliku?
|
||
|
||
- **`config.py`**
|
||
Definiuje `CFG` (tickers, interwał, okres pobierania `yf_period`, minimalna historia, risk, kasa startowa, SL/TP, katalog wyjściowy).
|
||
Zmienisz tu listę instrumentów lub parametry ryzyka.
|
||
|
||
- **`data.py`**
|
||
`fetch_batch(tickers, period, interval)` — jedno zapytanie do Yahoo dla wielu tickerów. Zwraca słownik `{ticker: DataFrame}` z kolumnami `open, high, low, close, volume`.
|
||
|
||
- **`indicators.py`**
|
||
Wskaźniki: `sma, ema, rsi, atr, macd, stoch_kd, bollinger, adx_val, supertrend`.
|
||
|
||
- **`strategy.py`**
|
||
`evaluate_signal(df)` → `Decision(signal, sl, tp, rpu)`
|
||
- **signal**: `BUY` / `SELL` / `NONE`
|
||
- **sl/tp**: poziomy na bazie ATR i RR
|
||
- **rpu** (*risk per unit*): ile ryzyka na jednostkę (do wyliczenia wielkości pozycji)
|
||
|
||
- **`portfolio.py`**
|
||
Model portfela z jedną kasą:
|
||
- egzekucja **na kolejnym OPEN** (pending orders),
|
||
- SL/TP intrabar,
|
||
- prowizja i poślizg,
|
||
- `portfolio_equity` (czas, equity) i lista `trades`.
|
||
|
||
- **`metrics.py`**
|
||
Metryki portfela na bazie krzywej equity:
|
||
- **Max Drawdown** – minimalna wartość `equity/rolling_max - 1`,
|
||
- **Sharpe annualized** – z 1-min zwrotów (525 600 okresów/rok),
|
||
- **CAGR** – roczna złożona stopa wzrostu.
|
||
|
||
- **`io_utils.py`**
|
||
Tworzy strukturę `dane/`, zapisuje:
|
||
- `dane/portfolio_equity.csv` – equity w czasie,
|
||
- `dane/portfolio_summary.txt` – JSON z metrykami i statystyką,
|
||
- `dane/<TICKER>/<TICKER>_trades.csv` – dziennik zagrań per instrument.
|
||
|
||
- **`app.py`**
|
||
Główna pętla:
|
||
1) `fetch_batch` → nowe świeczki,
|
||
2) dla każdego **nowego** bara: najpierw egzekucja pending na **open**, potem sygnał na **close** i zaplanowanie zlecenia na **kolejny open**,
|
||
3) zapis CSV + metryki,
|
||
4) pauza do pełnych 2 minut na rundę.
|
||
|
||
---
|
||
|
||
## Struktura wyjściowa (w `dane/`)
|
||
|
||
```
|
||
dane/
|
||
├─ portfolio_equity.csv # [time(ms), equity, datetime]
|
||
├─ portfolio_summary.txt # JSON z metrykami (MaxDD, Sharpe, CAGR, itp.)
|
||
├─ BTC-USD/
|
||
│ └─ BTC-USD_trades.csv # dziennik transakcji dla BTC
|
||
├─ ETH-USD/
|
||
│ └─ ETH-USD_trades.csv
|
||
└─ ...
|
||
```
|
||
|
||
**`*_trades.csv`** (kolumny):
|
||
`time, datetime, ticker, action(OPEN/CLOSE), side(long/short), price, size, pnl, reason, equity_after`
|
||
|
||
---
|
||
|
||
## Konfiguracja (najczęściej zmieniane)
|
||
|
||
Otwórz `config.py`:
|
||
|
||
```python
|
||
CFG.tickers = ["BTC-USD","ETH-USD","EURUSD=X", ...] # Twoja lista
|
||
CFG.interval = "1m" # 1m / 2m / 5m / 15m / 30m / 60m / 1h
|
||
CFG.yf_period = "2d" # krótszy = szybciej; dopasuj do interwału
|
||
CFG.risk_per_trade = 0.005 # 0.5% equity na trade
|
||
CFG.starting_cash = 10000.0 # kasa startowa (w jednostce bazowej)
|
||
CFG.sl_atr_mult = 2.0 # ile ATR do SL
|
||
CFG.tp_rr = 1.5 # stosunek TP do ryzyka
|
||
CFG.root_dir = "dane" # katalog wyjściowy
|
||
```
|
||
|
||
> **Tip**: przy zmianie interwału ustaw odpowiednie `yf_period` (np. 1m→2d, 5m→10d, 1h→60d), żeby mieć min. ~250 barów do wskaźników.
|
||
|
||
---
|
||
|
||
## Jak to działa (timeline 1 świecy)
|
||
|
||
1. Nowy bar `t` przychodzi z Yahoo.
|
||
2. Jeśli był **pending order** zaplanowany na **bar `t`**, **otwiera się** na jego **OPEN**.
|
||
3. Jeśli jest otwarta pozycja, sprawdzamy **SL/TP intrabar** (`low/high`).
|
||
4. Na **CLOSE** bara `t` liczymy sygnał i **plan** (pending) na **OPEN** bara `t+1`.
|
||
5. Snapshot portfelowego equity po close.
|
||
|
||
---
|
||
|
||
## Metryki
|
||
|
||
- **MaxDD**: min wartość z `(equity/rolling_max - 1)`.
|
||
- **Sharpe (annualized)**: liczone z 1-min zwrotów `pct_change`, skalowane przez √(525 600).
|
||
(Brak stopy wolnej od ryzyka/rf ~ 0 w tej wersji.)
|
||
- **CAGR**: `((equity_end/equity_start)^(1/lata)) - 1`.
|
||
|
||
> Uwaga: przy bardzo krótkich danych te metryki mogą być niestabilne.
|
||
|
||
---
|
||
|
||
## FAQ
|
||
|
||
**Q: Chcę inne interwały (np. 5m).**
|
||
A: Zmień `CFG.interval = "5m"` oraz `CFG.yf_period = "10d"` (żeby mieć ≥250 barów).
|
||
|
||
**Q: Jak dodać/zmienić tickery?**
|
||
A: Zedytuj listę w `config.py`. Dla złota używamy `GC=F` (futures).
|
||
|
||
**Q: Foldery per ticker nie powstają od razu.**
|
||
A: Tworzą się na starcie (`ensure_dirs`). Jeśli jakiś ticker nie ma danych z Yahoo, plik z transakcjami może nie powstać — ale katalog jest.
|
||
|
||
**Q: Wolno działa?**
|
||
A: Ta wersja używa **jednego** zapytania na rundę + tylko **nowe** świece. Dalsze przyspieszenie: rozbij tickery na dwie paczki, jeśli Yahoo dławi się liczbą symboli.
|
||
|
||
**Q: Jak uruchomić jako usługę systemd?**
|
||
A:
|
||
```ini
|
||
# /etc/systemd/system/portfolio.service
|
||
[Unit]
|
||
Description=Multi-Asset Portfolio Simulator
|
||
After=network.target
|
||
|
||
[Service]
|
||
User=USER
|
||
WorkingDirectory=/ścieżka/do/project
|
||
ExecStart=/ścieżka/do/project/venv/bin/python /ścieżka/do/project/app.py
|
||
Restart=always
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
```
|
||
Potem:
|
||
```bash
|
||
sudo systemctl daemon-reload
|
||
sudo systemctl enable --now portfolio.service
|
||
journalctl -u portfolio.service -f
|
||
```
|
||
|
||
---
|
||
|
||
## Troubleshooting
|
||
|
||
- **Yahoo 404 / brak danych dla symbolu** → Yahoo czasem zwraca puste dane. Po prostu pomija dany ticker w tej rundzie.
|
||
- **Za mało świec (history_min_bars)** → zwiększ `yf_period`.
|
||
- **Błędy czasu** → wymuszamy UTC; jeśli masz własne źródło, ujednolicaj strefę.
|
||
- **Sharpe = 0** → zbyt krótki okres lub zerowa zmienność w danych (rzadkie).
|
||
|
||
---
|
||
|
||
## Licencja
|
||
Ten kod jest przykładowy/edukacyjny; używaj na własną odpowiedzialność. Nie stanowi porady inwestycyjnej.
|