Analiza · trend_pullback · macaklin
Prednost tvoje mean-reversion strategije traje točno tri dana. Nakon toga se pretvara u gubitak — i to je najjasniji rezultat u cijeloj analizi.
Sažetak
Tradeovi zatvoreni unutar 3 dana: +1,31% prosječno, 84% pogodaka. Tradeovi koji prežive 4 dana i više: −1,58% prosječno, 32% pogodaka. Prijelaz je nagao, ne postupan.
12,7% tradeova završi ispod −3% i nose 75% svih gubitaka. Deset najgorih tradeova samo po sebi je 21% ukupnog gubitka. Najgori pojedinačni: −19,55% (TSLA).
Najgori trade na SPY-u je −6,65%, na TSLA −19,55%. Isti stop od 3% na SPY-u je normalan, a na TSLA se okida na običnom šumu. To je cijeli argument za ATR.
bot/backtest.py uopće ne simulira stop-loss, a bot/main.py ga uživo primjenjuje (check_stop_loss, STOP_LOSS_PCT = 0.03). To znači da tvoj backtest ne opisuje ono što tvoj bot radi. Onaj trade od −19,55% live bot nikad ne bi imao — bio bi zatvoren oko −3%. Sve brojke u ovoj analizi zato su donja granica stvarnosti, a paritet backtest↔live ti je trenutno slomljen na risk sloju.
Nalaz 1
Strategija kupuje dip u uzlaznom trendu i čeka odskok. Ako odskok dođe, dođe brzo. Podjela svih 616 tradeova po trajanju pokazuje da se prijelaz iz dobitka u gubitak događa između trećeg i petog bara — i to nije blagi pad nego preokret.
| Preživi dulje od | Tradeova | Udio | Prosjek | Pogodaka |
|---|---|---|---|---|
| 1 bara | 501 | 81,3% | +0,245% | 63,7% |
| 2 bara | 294 | 47,7% | −0,895% | 42,9% |
| 3 bara | 188 | 30,5% | −1,583% | 31,9% |
| 5 barova | 81 | 13,1% | −3,259% | 14,8% |
| 7 barova | 33 | 5,4% | −5,125% | 6,1% |
| 10 barova | 9 | 1,5% | −6,142% | 0,0% |
Označeni redak je točka preokreta: čim trade preživi drugi bar, prosjek te skupine pada u minus.
Ovo još nije dokaz da vremenski izlaz zarađuje. Postoji pristranost odabira: strategija izlazi na snagu (RSI(2) > 70), pa trade koji traje dugo traje dugo upravo zato što nije odskočio. Duga trajanja su djelomično posljedica gubitka, ne uzrok.
Ono što ovi podaci dokazuju jest da je populacija dugih tradeova jako loša i da vremenski izlaz treba ozbiljno testirati. Ono što ne mogu pokazati jest po kojoj bi se cijeni izašlo na 3. baru — za to trebaju cijene, ne samo zapisi o tradeovima. Zato je uz ovu analizu i skript koji to mjeri kako treba.
Nalaz 2
Strategija ima 68,2% pogodaka, ali prosječan gubitak (−3,04%) veći je od prosječnog dobitka (+2,03%). To je normalno za mean reversion. Problem nije prosjek nego rep: mali broj vrlo loših tradeova nosi većinu ukupnog gubitka.
| Tradeovi ispod praga | Broj | Udio svih | Zbroj gubitka | Udio svih gubitaka |
|---|---|---|---|---|
| −2% | 102 | 16,6% | −507,8 pp | 85,3% |
| −3% (tvoj live stop) | 78 | 12,7% | −448,3 pp | 75,3% |
| −5% | 31 | 5,0% | −262,6 pp | 44,1% |
| −7% | 15 | 2,4% | −167,0 pp | 28,1% |
| −10% | 7 | 1,1% | −100,6 pp | 16,9% |
pp = postotni bodovi, zbroj pnl_pct po tradeu. Ukupan zbroj svih 616 tradeova je +258,6 pp.
Naivan izračun — odsijeci svaki gubitak na −3% — daje +214 pp poboljšanja. Taj broj je lažan i namjerno ga ne stavljam u zaključke.
Razlog: stop se okida na intrabar minimumu, ne na konačnom rezultatu. Trade koji je završio na +1,5% možda je usput pao na −4% i bio bi izbačen s gubitkom. Odsijecanje računa samo korist, a ne i cijenu — a cijena su prerano zatvoreni dobitnici. Pravi odgovor traži prolazak kroz low svakog bara, što skript radi.
Nalaz 3
Rezultat po simbolu pokazuje da strategija radi na šest od osam dionica, a pada na dvije. Zanimljivo je koje su to dvije.
Fiksni stop od 3% na SPY-u je otprilike dvostruki dnevni raspon — razuman prostor za disanje. Na TSLA je 3% često manje od jednog prosječnog dnevnog raspona, pa se stop okida na običnom šumu, prije nego strategija uopće dobije priliku. Isti broj, potpuno različito značenje.
ATR to rješava time što stop mjeri u jedinicama volatilnosti same dionice umjesto u postocima. Stop na ulaz − 2 × ATR(14) daje TSLA širok prostor, a SPY-u uzak, automatski i bez ručnog podešavanja po simbolu. Posljedica: ako uz to veličinu pozicije računaš iz udaljenosti do stopa, rizik u eurima postaje jednak na svakom simbolu — što ti trenutni MAX_POSITION_PCT = 0.10 ne daje, jer fiksira iznos pozicije, a ne rizik.
| Simbol | Tradeova | Pogodaka | Prosjek | Ukupno | Najgori | Prosj. barova |
|---|---|---|---|---|---|---|
| NVDA | 88 | 68,2% | +1,04% | +91,6 | −17,06% | 3,5 |
| META | 71 | 71,8% | +1,08% | +76,8 | −12,05% | 3,4 |
| AMZN | 77 | 74,0% | +0,60% | +46,2 | −7,97% | 3,1 |
| MSFT | 70 | 74,3% | +0,41% | +28,7 | −5,64% | 2,9 |
| GOOGL | 82 | 69,5% | +0,31% | +25,0 | −9,16% | 3,0 |
| SPY | 93 | 73,1% | +0,24% | +22,5 | −6,65% | 2,9 |
| AAPL | 75 | 57,3% | −0,06% | −4,3 | −6,78% | 3,3 |
| TSLA | 60 | 53,3% | −0,46% | −27,8 | −19,55% | 3,3 |
Sljedeći korak
U scripts/analyze_exits.py je gotova analiza koja prolazi kroz stvarne barove i simulira 18 kombinacija pravila izlaza. Provjerava stop intrabar preko low, a kod gapa izlazi po open, ne po stopu — dakle ne uljepšava rezultat.
cd D:\macaklin .venv\Scripts\activate python -m scripts.analyze_exits
Podrazumijevano vrti tvoj trend_pullback portfelj (AAPL, MSFT, SPY), 1000 dnevnih barova, trošak 5 bps po strani, i dijeli podatke 60/40 na in-sample i out-of-sample. Opcije:
python -m scripts.analyze_exits --symbols AAPL MSFT SPY NVDA GOOGL TSLA --bars 1500 python -m scripts.analyze_exits --fee-bps 10 # pesimističniji trošak python -m scripts.analyze_exits --split 0 # bez podjele, sve odjednom
Arhitektura
Tvoj Strategy.evaluate(df) → Signal je čista funkcija nad cijenama i ne zna ništa o poziciji — nema ulaznu cijenu ni broj barova držanja. I ATR stop i vremenski izlaz ovise upravo o tome. Ako ih ugurate u strategiju, čista funkcija prestaje biti čista i gubiš paritet backtest↔live koji si namjerno izgradio.
Ispravno mjesto je zaseban sloj — ExitPolicy u bot/services/exits.py — koji prima stanje pozicije i vraća odluku o izlazu. Isti objekt onda koristi i backtest.py i main.py, pa je stop simuliran identično kako se izvršava. U skriptu je već napisan i radi; kad odlučiš koje parametre želiš, premjesti ga iz scripts/ u bot/services/ i priključi na oba mjesta.
Skript koristi: stop → signal strategije → vremenski izlaz. Stop ide prvi jer je najkonzervativniji; ako bar otvori ispod stopa, izlaz je po cijeni otvaranja, ne po stopu. Zadrži isti redoslijed u main.py — tamo check_stop_loss već ide prije strategija, što je točno.
Ograničenja
--fee-bps 0, dakle bez ikakvih troškova. Uz 5 bps po strani i 616 tradeova to je oko 62 pp manje od navedenih +258,6 pp.pnl_pct nije prinos portfelja. Backtest svaki put ulaže sav kapital u jednu poziciju; stvarni portfelj s pet paralelnih pozicija ponaša se drukčije.