BTC317,5k zł0,15%
ETH9,37k zł0,47%
XRP5,05 zł0,38%
LTC230 zł0,66%
BCH1,08k zł0,65%
DOT4,34 zł5,03%

Zestaw kodu tradera — Python i Pine Script

Sześć fragmentów kodu do skopiowania: cztery w Pythonie (wczytanie dziennika CSV, metryki, krzywa kapitału z obsunięciem, sizer pozycji z ATR) i dwa w Pine Script (zakres dnia względem ATR z alertem oraz okna sesji z planu na wykresie). Wszystko to narzędzia ANALITYCZNE — liczą, rysują i przypominają; żaden fragment nie generuje sygnałów kupna/sprzedaży i nie podejmuje decyzji, bo od tego jest playbook. Kod i komentarze są po angielsku — to konwencja tego zestawu, spójna z nazwami API, dokumentacją pandas i Pine Script oraz komunikatami o błędach (ten sam kierunek, który dla promptów na kodzie przyjmuje warsztat AI); opisy zastosowania wokół fragmentów są po polsku i prowadzą krok po kroku. Środowisko: instalacja Pythona i pierwsze uruchomienia są w 11.8; nagłówki kolumn dziennika — w dodatku F.

Stanowisko tradera i analityka: edytor kodu ze skryptami analizy dziennika w Pythonie oraz wykres z wskaźnikami Pine Script na monitorze
Najważniejsze w 60 sekund
  • Narzędzia, nie strategie: kod liczy metryki z TWOICH danych i pilnuje TWOJEGO planu — granica jest ta sama, co w bloku „czego AI nie robi" dodatku H, i z tych samych powodów
  • Wspólny format danych: fragmenty Pythona czytają nagłówki z arkusza dziennika (dodatek F) — win rate liczy break-even w mianowniku, expectancy z wyniku netto; te same definicje co w prompcie P5
  • Pine Script bez lookahead: S5 pobiera dane dzienne z lookahead=barmerge.lookahead_off, S6 nie sięga po wyższy interwał wcale (tylko funkcja time); brak lookahead nie jest dowodem braku repaintingu — opis S5 mówi, co widać na historii, a co na żywo. To pułapki, których szuka przegląd kodu promptem P3
  • Sizer zaokrągla wolumen W DÓŁ: przy przeliczaniu ryzyka na loty zaokrąglenie w górę systematycznie podnosi ryzyko ponad plan — ta jedna linijka kodu to różnica między 1% a „około 1%"

Zanim uruchomisz: trzy zasady

  • Czytasz, zanim uruchomisz. Każdy fragment jest krótki celowo — masz rozumieć każdą linijkę. Kod, którego nie rozumiesz, modyfikujesz z pomocą przeglądu z promptu P3, nie „na czuja"; a fragmentów z forów i czatów ta zasada dotyczy podwójnie.
  • Dane wchodzą z plików, decyzje nie wychodzą z kodu. Skrypty czytają eksport dziennika (format z dodatku F) i parametry, które podajesz jawnie. Wynik to liczby i wykresy do przeglądu tygodniowego — wnioski wyciągasz Ty, w rytmie audytu z 15.10, z uwzględnieniem liczebności próby, zależności między transakcjami i niepewności wyniku.
  • Środowisko z 11.8: Python 3.11 lub nowszy z pakietami pandas i matplotlib (instalacja i pierwszy skrypt — tam); fragmenty S1–S4 uruchomiono ponownie 17 IX 2026 na Python 3.12.10, pandas 3.0.5 i matplotlib 3.11.1, innych wersji nie sprawdzano. Pine Script wklejasz w edytorze wskaźników platformy wykresowej; oba wskaźniki są wersji 6.

Minimalne uruchomienie: w swoim środowisku Pythona 3.11+ sprawdź wersję poleceniem python --version, następnie zainstaluj pakiety poleceniem python -m pip install pandas==3.0.5 matplotlib==3.11.1, zapisz S1+S2+S3 jako analiza_dziennika.py, a testowy CSV jako dziennik.csv, następnie wykonaj python analiza_dziennika.py. S4 wymaga tylko biblioteki standardowej. S5 i S6 wklejaj do Pine Editor osobno. Dwa fragmenty Pine porównano z dokumentacją v6; nie wykonano ich kompilacji ani testu alertów w TradingView.

Pipeline analityczny: eksport dziennika CSV wchodzi do skryptów Pythona, wychodzą metryki i wykres krzywej kapitału, a wnioski trafiają do przeglądu tygodniowego i playbooka Dziennik CSVformat z dodatku F Python: S1–S3metryki · krzywa · obsunięcie Przegląd EOWpróba, zależności, niepewność Playbookwersjonuj reguły i testy w tym przepływie nie ma strzałki „kod → zlecenie" — i to nie jest przeoczenie

S1 · Wczytanie dziennika CSV

Python 3 · pandas

Punkt wejścia dla S2 i S3: czyta eksport arkusza z dodatku F, sprawdza nagłówki i liczbę pól, a następnie czyści kolumnę r_net. Powtórzone nazwy kolumn, pusty nagłówek oraz nadmiar lub brak pól zatrzymują import; skrypt nie wybiera sam jednej z dwóch kolumn wyniku i nie przesuwa danych do indeksu. Puste pole zapisujesz separatorem, np. ;A+;;0; ucięty wiersz jest innym przypadkiem. Ustaw separator i przecinek dziesiętny zgodnie z wariantem CSV (PL: średnik i przecinek, EN: przecinek i kropka) oraz kodowanie: plik z generatora dodatku F jest UTF-8, wariant „CSV UTF-8” zapisany z Excela również odczytasz jako UTF-8; starszy eksport w polskiej stronie kodowej może wymagać cp1250. Sprawdź wariant zapisu, bo sam język interfejsu nie określa kodowania. Puste r_net i pusty close_time_utc oznaczają wpis jeszcze niezamknięty (pomijany); czas zamknięcia bez wyniku oznacza niekompletny rekord i zatrzymuje obliczenia; wartość niepusta, której nie da się przeczytać jako liczby (także „NA" czy myślnik), zatrzymuje skrypt z numerem rekordu — tak samo zamknięta transakcja bez czasu zamknięcia w formacie z dodatku F (2026-07-08 13:15), bo bez niego kolejność krzywej byłaby zgadywana.

import pandas as pd

CSV_PATH = "dziennik.csv"   # journal export (add-on F structure)
CSV_SEP  = ";"              # ";" for PL-locale CSV, "," for EN
DECIMAL  = ","              # "," for PL-locale CSV, "." for EN
ENCODING = "utf-8-sig"      # add-on F generator writes UTF-8 (BOM handled); legacy Polish-code-page CSV may need "cp1250"

REQUIRED = ["r_net", "setup", "setup_class", "error_code", "close_time_utc"]

# Read a local file and validate its shape BEFORE building a DataFrame.
# pandas.read_csv can rename duplicate headers or infer an index from extra fields.
import csv

try:
    with open(CSV_PATH, encoding=ENCODING, newline="") as source:
        reader = csv.reader(source, delimiter=CSV_SEP, strict=True)
        header = next(reader, None)
        if not header:
            raise SystemExit("Cannot read CSV: the header is empty.")
        if any(not name or name != name.strip() for name in header):
            raise SystemExit("Empty or space-padded column name — fix the CSV header.")
        duplicates = sorted({name for name in header if header.count(name) > 1})
        if duplicates:
            raise SystemExit(f"Duplicate columns: {duplicates} — resolve them in the journal.")
        rows, record_indexes = [], []
        for index, row in enumerate(reader):
            if not row:                              # a completely empty CSV record
                continue
            if len(row) != len(header):
                raise SystemExit(f"Record {index + 1}: expected {len(header)} fields, got {len(row)} "
                                 f"— check separators and quotes; no fields were discarded.")
            rows.append(row)
            record_indexes.append(index)
    df = pd.DataFrame(rows, columns=header, index=record_indexes, dtype="string").replace("", pd.NA)
except UnicodeDecodeError:
    raise SystemExit(f"Cannot decode {CSV_PATH} as {ENCODING} — check UTF-8 or legacy cp1250.") from None
except (OSError, csv.Error) as exc:
    raise SystemExit(f"Cannot read {CSV_PATH}: {exc}") from None

missing = [c for c in REQUIRED if c not in df.columns]
if missing:
    raise SystemExit(f"Missing columns: {missing} — check headers against add-on F and the CSV_SEP setting.")

raw = df["r_net"]                                   # an empty result is allowed only when the close time is also empty
txt = raw.astype("string").str.strip().str.replace(DECIMAL, ".", regex=False)   # parse the column ourselves: one bad cell must not hide the readable ones
r   = pd.to_numeric(txt, errors="coerce").astype("Float64")  # avoid integer wraparound in later sums
r   = r.where(r.abs() != float("inf"))              # inf is not a valid R-multiple either
invalid = raw.notna() & r.isna()                    # filled in, but not readable as a number
if invalid.any():
    raise SystemExit(f"Invalid r_net in records {(df.index[invalid] + 1).tolist()} "
                     f"(record 1 = first row under the header) — check values and DECIMAL; "
                     f"NA, dashes and infinity are not numbers.")

blank = df.isna().all(axis=1)                       # whole row empty = unused sheet row (whole-sheet XLSX export), not a trade
df, r = df[~blank], r[~blank]
has_close = df["close_time_utc"].fillna("").str.strip().ne("")
incomplete = r.isna() & has_close
if incomplete.any():
    raise SystemExit(f"Missing r_net for closed records {(df.index[incomplete] + 1).tolist()} "
                     f"— complete the net result before calculating metrics.")
open_trades = int(r.isna().sum())
df = df.assign(r_net=r).dropna(subset=["r_net"])   # index kept = record numbers for the message below

# equity curve and drawdown need chronological order (add-on F: by close time) — a closed trade without a readable close time stops the script
close_text = df["close_time_utc"].fillna("").str.strip()
has_time = close_text.str.fullmatch(r"\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})?")
closed = pd.to_datetime(close_text.where(has_time), errors="coerce", format="ISO8601", utc=True)   # add-on F writes 2026-07-08 13:15; a fixed format keeps the check independent of row order, utc=True keeps a stray "Z"/"+00:00" suffix from crashing it
if closed.isna().any():
    raise SystemExit(f"close_time_utc not readable in records {(df.index[closed.isna()] + 1).tolist()} "
                     f"— every closed trade needs a close time in the add-on F format (2026-07-08 13:15); fix the journal, not the script.")
df = df.assign(_t=closed).sort_values("_t", kind="stable").drop(columns="_t").reset_index(drop=True)

print(f"Loaded {len(df)} closed trades ({open_trades} row(s) skipped: no close time and no r_net).")

Dostaniesz: ramkę danych df gotową dla kolejnych fragmentów, posortowaną po czasie zamknięcia (w tej kolejności liczy się krzywa i obsunięcie), plus liczbę pominiętych transakcji otwartych (pusty wiersz to cały rekord pusty, jak w eksporcie całej karty XLSX — takich wierszy skrypt nie liczy; wiersz z danymi, lecz bez wyniku i czasu zamknięcia, jest pomijany jako jeszcze niezamknięty; zamknięty wpis bez wyniku zatrzymuje skrypt). Zatrzymanie z listą rekordów — popraw wskazane wiersze w dzienniku, nie skrypt; przy błędzie całej kolumny sprawdź także stałą DECIMAL. Numer rekordu liczy się po nagłówku, także przez puste rekordy; pole w cudzysłowie może zawierać separator albo nową linię i nadal należy do tego samego rekordu. Jedna transakcja ma wystąpić raz: przed łączeniem eksportów sprawdź identyfikatory trade_id — S1 nie deduplikuje historii. Data bez godziny nie wystarcza do odtworzenia kolejności. Czas bez oznaczenia strefy jest traktowany jako UTC; jawny offset ISO 8601 jest przeliczany na UTC. Przy identycznych czasach skrypt zachowuje kolejność rekordów — ustal ją w dzienniku, bo wpływa na ścieżkę obsunięcia.

S2 · Metryki dziennika

Python 3 · pandas

Te same definicje co w prompcie P5 i dashboardzie arkusza: win rate z break-even w mianowniku, profit factor z komunikatem zamiast dzielenia przez zero, expectancy jako średnia z R netto (r_net), obsunięcie z krzywej skumulowanej liczone od punktu 0 R (pierwsza strata to już obsunięcie — jak w formule arkusza F). Uruchamiaj po S1. Definicje metryk są wspólne, lecz polityka błędów różni się od P5: S1 zatrzymuje analizę przy niepoprawnym wyniku zamkniętej transakcji, a P5 poleca pominąć taki wiersz i go policzyć. Porównuj wyniki dopiero po uzgodnieniu, które transakcje weszły do obu prób.

import math

def metrics(r):
    """Core journal metrics on a series of net R-multiples.
    Definitions (same as add-on F dashboard):
    - win rate: trades with r > 0 over ALL valid trades (break-even stays in denominator)
    - profit factor: sum of positive r over abs(sum of negative r)
    - expectancy: arithmetic mean of r (net of costs)
    - max drawdown: max drop of the cumulative R curve from its running peak,
      the curve starting at 0 R (so a first loss of -1 R is already a 1 R drawdown)"""
    n = len(r)
    if n == 0:
        raise SystemExit("No valid trades — nothing to measure.")
    try:
        r = r.astype(float)
    except (TypeError, ValueError):
        raise SystemExit("Metrics require numeric net R values — run S1 first.") from None
    if not r.map(math.isfinite).all():
        raise SystemExit("Metrics require finite net R values — run S1 first.")
    wins   = r[r > 0]
    losses = r[r < 0]
    equity   = r.cumsum()
    peak     = equity.cummax().clip(lower=0)   # add-on F: MAX(0; running peak)
    drawdown = peak - equity
    try:
        total = math.fsum(r)
        pf_value = None if losses.empty else math.fsum(wins) / abs(math.fsum(losses))
    except (OverflowError, ValueError, ZeroDivisionError):
        raise SystemExit("Numeric range exceeded — check the R values; no metrics returned.") from None
    if (not equity.map(math.isfinite).all() or not drawdown.map(math.isfinite).all()
            or (pf_value is not None and not math.isfinite(pf_value))):
        raise SystemExit("Numeric range exceeded — check the R values; no metrics returned.")
    pf = "no losses in sample" if pf_value is None else round(pf_value, 2)
    return {
        "trades":         n,
        "win_rate":       round(len(wins) / n, 3),
        "profit_factor":  pf,
        "expectancy_R":   round(total / n, 3),
        "sum_R":          round(total, 2),
        "max_drawdown_R": round(drawdown.max(), 2),
    }

for key, value in metrics(df["r_net"]).items():
    print(f"{key:>16}: {value}")

Dostaniesz: sześć metryk do przeglądu tygodniowego; przy braku strat PF jest komunikatem, nie liczbą. Jeśli wartości przekraczają zakres obliczeń, skrypt zatrzymuje analizę zamiast zwracać inf, NaN lub pozornie zerowe obsunięcie. WR = 0,4 oznacza 40%. PF liczony jest w R, a nie w PLN; przy zmiennym ryzyku kwotowym te wyniki mogą się różnić. Koszty są już w r_net — S2 nie odejmuje ich ponownie. Nie oczekuj oceny przewagi na podstawie samej średniej ani przekroczenia liczby 20 czy 50 transakcji. Definicje dashboardu znajdziesz w dodatku F, a plan doboru próby, kosztów i walidacji w dodatku N.

S3 · Krzywa kapitału i obsunięcie

Python 3 · pandas + matplotlib

Rysuje krzywą skumulowanego R i pod nią obsunięcie od szczytu, zapisuje PNG obok pliku CSV z CSV_PATH (S1), niezależnie od katalogu, z którego uruchamiasz skrypt. Krzywa pokazuje kolejność wyników i klastry strat, które giną w liczbach zbiorczych. Jest to suma R, nie saldo rachunku w PLN ani procentowe equity: przy różnym ryzyku kwotowym nie odtworzy zmian salda. Wpłat, wypłat i wyniku otwartych pozycji ten wykres nie obejmuje. Uruchamiaj po S1.

import math
import matplotlib.pyplot as plt
from pathlib import Path

if len(df) == 0:
    raise SystemExit("No closed trades — nothing to plot.")

equity   = df["r_net"].astype(float).cumsum()
drawdown = equity.cummax().clip(lower=0) - equity   # same definition as S2 and add-on F (curve starts at 0 R)

if not equity.map(math.isfinite).all() or not drawdown.map(math.isfinite).all():
    raise SystemExit("Numeric range exceeded — check the R values; no chart saved.")

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6), sharex=True,
                               gridspec_kw={"height_ratios": [3, 1]})
x = [0, *(equity.index + 1)]                        # trade 0 = the 0 R starting point the drawdown is measured from
ax1.plot(x, [0.0, *equity.values])
ax1.set_ylabel("equity (R)")
ax1.set_title("Equity curve — cumulative net R")
ax1.grid(True, alpha=0.3)

ax2.fill_between(x, [0.0, *(-drawdown.values)], 0, alpha=0.5)
ax2.set_ylabel("drawdown (R)")
ax2.set_xlabel("trade #")
ax2.grid(True, alpha=0.3)

fig.tight_layout()
PNG_PATH = Path(CSV_PATH).with_name("equity_curve.png")   # next to the journal file, whatever the working directory
fig.savefig(PNG_PATH, dpi=150)
plt.close(fig)
print(f"Saved: {PNG_PATH}")

Dostaniesz: plik equity_curve.png obok dziennika; krzywa zaczyna się w punkcie 0 R (transakcja 0), od którego liczone jest obsunięcie. Dolny panel to obsunięcie skumulowanego R od szczytu (od 0 R, jak w S2 i arkuszu F) za całą historię — nie jest to strata tygodniowa. Limit tygodniowy z planu (dodatek E) sprawdzaj osobno, tydzień po tygodniu, w jednostce i z regułą resetu zapisanymi w planie.

Plik testowy i oczekiwany wynik (S1 + S2 + S3) CSV · wariant PL

Zapisz jako dziennik.csv (UTF-8) obok sklejonego skryptu S1+S2+S3 i uruchom. Plik ma tylko kolumny, których S1 potrzebuje — pełny eksport z dodatku F (26 kolumn) wczytuje się tak samo. Ostatni wiersz to transakcja otwarta (puste r_net), dwie pierwsze zamknięte to straty, więc obsunięcie liczy się od punktu 0 R.

trade_id;close_time_utc;setup;setup_class;r_net;error_code
2026-041;2026-07-08 13:15;wybicie-azja-londyn;A+;-1,00;5
2026-042;2026-07-09 16:40;powrot-do-sredniej;B;-1,08;3
2026-043;2026-07-10 09:05;wybicie-azja-londyn;A+;1,83;0
2026-044;2026-07-13 12:20;wybicie-azja-londyn;B;0,00;0
2026-045;2026-07-14 14:30;powrot-do-sredniej;C;2,40;0
2026-046;;wybicie-azja-londyn;B;;

Oczekiwany wydruk (Python 3.12.10, pandas 3.0.5, matplotlib 3.11.1; uruchomione 17 IX 2026):

Loaded 5 closed trades (1 row(s) skipped: no close time and no r_net).
          trades: 5
        win_rate: 0.4
   profit_factor: 2.03
    expectancy_R: 0.43
           sum_R: 2.15
  max_drawdown_R: 2.08
Saved: equity_curve.png

Rachunek do sprawdzenia ręcznie: wygrane 1,83 + 2,40 = 4,23 R, straty 1,00 + 1,08 = 2,08 R → PF 2,03; suma 2,15 R / 5 = 0,43 R; krzywa −1,00 → −2,08 → −0,25 → −0,25 → 2,15 ze szczytem startowym 0 R → obsunięcie 2,08 R (bez punktu 0 R wyszłoby 1,08 R — to różnica, o którą chodzi w definicji z dodatku F). Wydruk dotyczy dokładnie tego pliku na podanych wersjach; przy rozbieżności sprawdź po kolei dane (separator, przecinek dziesiętny, kodowanie, kolejność wierszy), stałe w S1, kod i wersje pakietów.

S4 · Sizer pozycji z ATR

Python 3 · biblioteka standardowa

Wolumen w lotach dla stopa o szerokości k × ATR: kwota ryzyka z kapitału i procentu, dystans z ATR, zaokrąglenie W DÓŁ do kroku lota Twojego rachunku (parametry lot_step i min_lot, domyślnie 0,01) — żeby ryzyko PLANOWANE nie przekroczyło budżetu. Rzeczywista strata może być większa: poślizgu, luki przez stop, spreadu, prowizji ani swapu w tym rachunku nie ma. Kapitał i wartość pipsa muszą być w tej samej walucie rachunku; osobno sprawdź maksymalny wolumen i wymagany depozyt w specyfikacji. ATR podajesz w pipsach (EURUSD 0,0080 → 80, USDJPY 0,80 → 80; rozmiar pipsa — profil pary, dodatek B), a wartość pipsa dla 1 lota znajdziesz w specyfikacji instrumentu (albo policzysz kalkulatorem z dodatku C).

import math
from decimal import Decimal
from fractions import Fraction

def atr_position_size(capital, risk_pct, atr_pips, atr_mult, pip_value_per_lot,
                      lot_step=0.01, min_lot=0.01):
    """Volume in lots for a stop of atr_mult × ATR.
    atr_pips: ATR in PIPS, not in price units (EURUSD ATR 0.0080 -> 80; USDJPY ATR 0.80 -> 80 — pip size of the pair).
    pip_value_per_lot: value of 1 pip for 1.00 lot, in account currency
    (e.g. ~10 USD for EURUSD on a USD account — check instrument specs).
    lot_step / min_lot: volume step and minimum of YOUR account (0.01 is common, not universal).
    Rounds DOWN to multiples of lot_step, measured from zero. Fraction arithmetic
    preserves the decimal inputs without depending on the global Decimal precision.
    Planned risk is not the maximum loss: slippage, gaps, spread and swap are not in this model."""
    inputs = (capital, risk_pct, atr_pips, atr_mult, pip_value_per_lot, lot_step, min_lot)
    if any(isinstance(x, bool) or not isinstance(x, (int, float, Decimal)) for x in inputs):
        raise ValueError("Use numeric values, not text or booleans (Python decimals use a dot).")
    decimals = tuple(Decimal(str(x)) for x in inputs)
    if not all(x.is_finite() and x > 0 for x in decimals):
        raise ValueError("All inputs must be finite, positive numbers.")
    c, p, a, k, v, step, minimum = map(Fraction, decimals)
    if p > 100:
        raise ValueError("risk_pct is a percentage of capital (1.0 = 1%).")
    risk_amount = c * p / 100
    sl_pips     = a * k
    lots        = math.floor(risk_amount / (sl_pips * v * step)) * step
    if lots < minimum:
        return {"warning": "No admissible volume for this lot_step and min_lot within the risk budget."}
    exact = {"risk_amount": risk_amount, "sl_pips": sl_pips,
             "lots": lots, "planned_risk": lots * sl_pips * v}
    try:
        result = {key: float(value) for key, value in exact.items()}
    except OverflowError:
        raise ValueError("Inputs exceed the output numeric range.") from None
    if not all(math.isfinite(value) and value > 0 for value in result.values()):
        raise ValueError("Inputs exceed the output numeric range.")
    return result  # floats for display; no extra rounding of the stop or money values


print(atr_position_size(capital=10_000, risk_pct=1.0,
                        atr_pips=80, atr_mult=1.5, pip_value_per_lot=10.0))
# {'risk_amount': 100.0, 'sl_pips': 120.0, 'lots': 0.08, 'planned_risk': 96.0}

Dostaniesz: loty zaokrąglone w dół do kroku i wynikającą z nich kwotę ryzyka PLANOWANEGO (w modelu bez kosztów ≤ budżet; faktyczna strata może być większa). Krok jest liczony od zera; sprawdź, czy taką siatkę wolumenu stosuje Twój rachunek. Stop i kwoty nie są dodatkowo zaokrąglane: np. ATR 0,26 pipsa × 1,25 daje 0,325 pipsa, nie 0,3. Wynik zwracany jest jako liczby float do odczytu, natomiast liczba kroków lota wynika z dokładnych ułamków; minimalny krok ceny instrumentu nadal wymaga sprawdzenia osobno. Jeśli dostosujesz stop do tego kroku, przelicz wolumen jeszcze raz. Nie oczekuj: decyzji, czy wchodzić — sizer liczy „ile", nigdy „czy".

S5 · Zakres dnia vs ATR z alertem

Pine Script v6 · wskaźnik

Narzędzie świadomości zmienności (wskaźnik intraday): na świecach bieżących pokazuje, jaki procent dziennego ATR rynek już „zużył", i alarmuje po przekroczeniu progu — moment na pytanie z planu, nie na pogoń. Dane dzienne pobierane bez lookahead (bez wartości z przyszłości), na historii request.security utrzymuje ostatnią potwierdzoną wartość dzienną aż do końca kolejnego okresu D; na ostatniej świecy intraday tego okresu pojawia się jego nowy wynik. Na żywo pobiera rozwijający się zakres i ATR bieżącej świecy D, więc wcześniejsze odczyty mogą zmienić się po odświeżeniu (repainting). Dzień oznacza świecę D dostawcy danych, nie dobę Europe/Warsaw. ATR w mianowniku też zmienia się w trakcie dnia; 100% nie jest limitem możliwego ruchu. Na historii nie odtworzysz tą metodą kolejnych dzisiejszych przekroczeń progu.

//@version=6
indicator("Daily range vs ATR (awareness tool)", overlay=false)

if not timeframe.isintraday
    runtime.error("Use a time-based intraday chart below 1D.")

atrLen  = input.int(14, "ATR length", minval=2)
warnPct = input.int(80, "Alert when day range reaches % of ATR", minval=10, maxval=200)

// Historical: last confirmed D value, updated at the END of each D period.
// Realtime: developing D values; elapsed realtime readings can repaint after reload.
dayHigh = request.security(syminfo.tickerid, "D", high,            lookahead=barmerge.lookahead_off)
dayLow  = request.security(syminfo.tickerid, "D", low,             lookahead=barmerge.lookahead_off)
dayAtr  = request.security(syminfo.tickerid, "D", ta.atr(atrLen),  lookahead=barmerge.lookahead_off)

rangePct = dayAtr > 0 ? 100.0 * (dayHigh - dayLow) / dayAtr : na

plot(rangePct, "Day range as % of ATR", linewidth=2)
hline(100, "100% of ATR")
hline(warnPct, "Alert level", linestyle=hline.style_dotted)

alertcondition(ta.crossover(rangePct, warnPct),
     title="Range reached % of ATR",
     message="Daily range reached your % of ATR — re-read the plan before any new entry.")

Dostaniesz: panel pod wykresem plus warunek alertu — sam alert tworzysz w platformie po dodaniu wskaźnika, wybierając warunek „Range reached % of ATR". Użyj zwykłych świec czasowych intraday; na Heikin Ashi/Renko dane mogą mieć inny charakter. Brak odczytu (na) oznacza brak wymaganego ATR lub danych, a nie 0%. Warunek wymaga przejścia z wartości ≤ próg do wartości > próg: samo utrzymywanie się powyżej nie wywołuje kolejnego alertu. Ustawienie „Once Per Bar Close” ogranicza alarmy do zamknięcia świecy intraday, lecz nie potwierdza całej świecy D. Po zmianie parametrów utwórz alert ponownie. Nie oczekuj: sygnału kierunku — wysoki odczyt mówi „ruch już się odbył", a co z tym zrobić, mówi playbook.

S6 · Okna sesji z planu na wykresie

Pine Script v6 · wskaźnik

Sekcja 3 planu (dodatek E) narysowana na wykresie: podświetla godziny, w których WOLNO otwierać pozycje, i alarmuje, gdy okno się zamyka. Godziny podajesz w strefie Europe/Warsaw — identyfikator IANA uwzględnia CET/CEST. Domyślnie okna obowiązują od poniedziałku do piątku; opcja pozwala objąć wszystkie dni. Podświetlenie potwierdza tylko godzinę z planu, nie pozostałe warunki wejścia. Używaj zwykłych świec czasowych intraday.

//@version=6
indicator("Trading-plan session windows", overlay=true)

if not timeframe.isintraday
    runtime.error("Use a time-based intraday chart below 1D.")

tz   = input.string("Europe/Warsaw", "Time zone of the windows")
win1 = input.session("0900-1200", "Window 1")
win2 = input.session("1400-1700", "Window 2")

weekdaysOnly = input.bool(true, "Monday to Friday only")
days = weekdaysOnly ? ":23456" : ":1234567"

inWin = not na(time(timeframe.period, win1 + days, tz)) or not na(time(timeframe.period, win2 + days, tz))

bgcolor(inWin ? color.new(color.teal, 88) : na, title="Inside a plan window")

windowJustClosed = inWin[1] and not inWin
alertcondition(windowJustClosed,
     title="Plan window closed",
     message="Your trading window just closed — manage open positions only, no new entries (plan, section 3).")

Dostaniesz: tło w godzinach handlu i warunek alertu na zamknięcie okna („Plan window closed" — alert tworzysz w platformie po dodaniu wskaźnika). Alert wykrywa pierwszą świecę poza oknem po świecy w oknie, więc nie gwarantuje powiadomienia dokładnie o godzinie końca: bez nowej świecy i aktualizacji danych nie wykona kontroli. Używaj interwału, którego otwarcia i zamknięcia pokrywają się z granicami okien. Na przykład przy M15, oknie 09:00–12:00 i dostępnej świecy 12:00 tło kończy się o 12:00, a warunek jest aktywny na tej pierwszej świecy poza oknem. Częstotliwość „Once Per Bar” może powiadomić na jej pierwszej aktualizacji; „Once Per Bar Close” dopiero przy zamknięciu. Nakładające się okna są sumą: koniec pierwszego nie zamyka drugiego. Po zmianie godzin odtwórz alert. Nie oczekuj: blokady zleceń — wskaźnik przypomina o planie; egzekwuje go Twoja checklista pre-trade.

Wskaźniki Pine Script v6 na platformie TradingView: podświetlone okna sesji z planu (S6) oraz dolny panel zakresu dnia względem ATR z poziomem alertu 80% (S5)
Wskaźniki Pine Script v6 na platformie TradingView: podświetlone okna sesji z planu (S6) oraz dolny panel zakresu dnia względem ATR z poziomem alertu 80% (S5)
Cały zestaw jednym plikiem generowany lokalnie w przeglądarce

Sześć fragmentów z nagłówkami w jednym pliku tekstowym, instrukcja uruchomienia i wersje bibliotek, a na końcu także CSV testowy i oczekiwany wydruk — do archiwum przy dzienniku albo do własnego repozytorium. TXT łączy różne języki: nie uruchamiaj całego pliku jako Pythona. Wybierz S1+S2+S3, samodzielny S4 albo pojedynczy wskaźnik Pine.

Rzeczywisty wynik S3 na pięciu transakcjach testowych: okno edytora kodu ze skryptem Pythona i metrykami w terminalu obok wygenerowanego wykresu krzywej kapitału i obsunięcia
Rzeczywisty wynik S3 na pięciu transakcjach testowych: okno edytora kodu ze skryptem Pythona i metrykami w terminalu obok wygenerowanego wykresu krzywej kapitału i obsunięcia

FAQ

Dlaczego kod i komentarze są po angielsku, skoro kurs jest polski?

To konwencja tego zestawu: komentarze po angielsku są spójne z nazwami API, dokumentacją pandas i Pine Script oraz komunikatami o błędach, które i tak czytasz po angielsku; ten sam kierunek dla promptów na kodzie przyjmuje warsztat AI (dodatek H, P3). Polskie opisy wokół fragmentów mówią, CO kod robi i kiedy go użyć; angielskie komentarze w środku mówią JAK. Wyjątek z promptu P5 (komentarze po polsku w kodzie generowanym na Twoje zamówienie) dotyczy kodu jednorazowego, nie biblioteki.

Jak uruchomić fragmenty Pythona?

Instalację Pythona, środowiska i pakietów prowadzi krok po kroku 11.8 — na potrzeby tego zestawu wystarczą pandas i matplotlib. Fragmenty S1–S3 sklejasz w jeden plik analiza_dziennika.py (S1 na górze) i uruchamiasz poleceniem python analiza_dziennika.py w katalogu z eksportem dziennika; S4 działa samodzielnie. Ścieżka względna CSV_PATH odnosi się do katalogu roboczego terminala. Zacznij od testowego CSV, a rzeczywisty dziennik analizuj na kopii; equity_curve.png jest nadpisywany przy kolejnym uruchomieniu. Jeśli terminal odpowiada błędem, którego nie rozumiesz — wklej kod i pełny komunikat do przeglądu P3 z dodatku H, zamiast zgadywać.

Mogę te fragmenty modyfikować i rozbudowywać?

Po to są — to zestaw STARTOWY. Naturalne rozszerzenia: metryki per setup (grupowanie po kolumnie setup przed wywołaniem funkcji z S2), filtr po error_code, żeby policzyć expectancy czystego wykonania, albo osobne krzywe dla klas A+/B. Dwie zasady przy modyfikacjach: każdą zmianę przepuszczasz przez przegląd P3 (zwłaszcza wszystko, co dotyka request.security w Pine), definicje metryk i reguły strategii wersjonujesz. Błąd obliczeniowy popraw od razu i przelicz porównywane okresy tą samą metodą; zmianę reguł strategii opisz w protokole testowania (dodatek N) i sprawdź na nowych danych. Sam koniec kwartału nie uzasadnia zmiany, a jego trwanie nie uzasadnia utrzymywania błędu.

Dlaczego w zestawie nie ma strategii ani EA?

Z tego samego powodu, dla którego warsztat AI nie generuje sygnałów: gotowa strategia z internetu ma nieznaną przewagę, nieznane założenia i zero związku z Twoim planem — a EA wykonujący cudzą logikę na Twoim rachunku to delegowanie decyzji, za które płacisz wyłącznie Ty. Kod w tym kursie wzmacnia proces (pomiar, wizualizacja, pilnowanie planu), nie zastępuje go. Jeśli chcesz automatyzować własną, przetestowaną strategię — ścieżka prowadzi przez backtest z 15.4, protokół testowania z dodatku N i przegląd kodu P3, nie przez kopiowanie „zyskownych" skryptów.

Scenariusz ilustracyjny: zaokrąglanie „bo równiej". Sizer wyliczał 0,07 lota, trader klikał 0,1 — „różnica kosmetyczna, a okrągło". Kosmetyka po przeliczeniu: +43% ryzyka na każdej transakcji, systematycznie, przez cały kwartał; limit dzienny −2R był faktycznie limitem −2,9R, a obsunięcie z krzywej kapitału przekroczyło plan, choć trader uznawał każde zaokrąglenie za kosmetyczne. Stąd math.floor w S4 zamiast round: maszyna zaokrągla w dół bez dyskusji, człowiek — w górę z uzasadnieniem.
Scenariusz ilustracyjny: klaster strat widoczny dopiero na krzywej. Metryki kwartału wyglądały przyzwoicie: expectancy dodatnie, obsunięcie w normie. Dopiero wykres z S3 pokazał, że całe obsunięcie to jeden tydzień — a filtr po godzinie wejścia dodany do S2 doprecyzował: wszystkie wpadki między 21:00 a 23:00 UTC (dziennik z dodatku F zapisuje czasy w UTC), na szerokich spreadach, „bo jeszcze chwilę posiedzę". Czy ta pora pokrywa się z rolowaniem, zależy od brokera i strefy — to do sprawdzenia w jego specyfikacji, nie do założenia. Liczba zbiorcza uśrednia; krzywa pokazuje KIEDY system krwawi. Jedna linijka w planie (koniec wejść o 21:00 UTC, czyli 23:00 CEST / 22:00 CET — w planie i w oknach S6 zapisz to w Europe/Warsaw) to w tym scenariuszu wniosek do sprawdzenia na kolejnych tygodniach, nie gotowa poprawa wyniku — i to jest dokładnie ten rodzaj wniosku, do którego ten zestaw istnieje.

Powiązane w kursie

Zastrzeżenie: strona ma charakter edukacyjny i nie jest poradą inwestycyjną. Fragmenty kodu są narzędziami analitycznymi uruchamianymi na własną odpowiedzialność: przed użyciem czytasz kod, wyniki weryfikujesz (np. kalkulatorem z dodatku C), a decyzje handlowe podejmujesz według własnego planu. Fragmenty Pythona (S1–S4) nie łączą się z internetem ani rachunkiem — czytają lokalne pliki i rysują wykresy; wskaźniki Pine (S5–S6) działają w Twojej platformie wykresowej na jej danych. Żaden fragment nie generuje sygnałów kupna/sprzedaży i nie składa zleceń.

Źródła

  1. Dokumentacje: pandas 3.0.5 i matplotlib 3.11.1 (funkcje użyte w S1–S3), Python: odczyt CSV i ułamki Fraction (kontrola formatu oraz dokładne wyznaczenie liczby kroków lota) — fragmenty S1–S4 uruchomione na danych syntetycznych 17 IX 2026 na Python 3.12.10; TradingView Pine Script v6: inne interwały i dane, sesje, czas i strefy, alerty (request.security z parametrem lookahead i repainting, hline, input.session, alertcondition) — kod S5–S6 sprawdzony w odczycie względem dokumentacji, bez kompilacji.
  2. Wewnętrzne: 11.8 Python w tradingu (środowisko), 15.9 (definicje metryk i format dziennika), 15.10 (zasady wnioskowania) — zestaw jest ich wykonawczym skrótem.
  3. Van K. Tharp, Trade Your Way to Financial Freedom, wyd. 2, McGraw-Hill 2007 — R-multiple i expectancy, wspólny język S2, arkusza F i promptu P5.

Jarosław Wasiński LinkedIn

Redaktor naczelny MyBank.pl • Analityk finansowy i rynkowy

mgr Jarosław Wasiński — niezależny analityk i praktyk z ponad 20-letnim doświadczeniem w sektorze finansowym. Twórca i redaktor naczelny portalu MyBank.pl, dostarczającego rzetelną wiedzę o finansach osobistych, bankowości i inwestycjach od 2004 roku.

  • Bankowość i produkty finansowe: porównania kont osobistych i firmowych, analiza taryf opłat, testy aplikacji mobilnych, recenzje kredytów, lokat i kart kredytowych — z naciskiem na realne koszty i ukryte opłaty.
  • Rynki finansowe i makroekonomia: analiza fundamentalna rynków walutowych (Forex) i makroekonomicznych od 2007 roku, zarządzanie ryzykiem kapitału, struktura rynków OTC.
  • Kryptowaluty: analiza rynku kryptowalut, mechanizmów blockchain i tokenizacji aktywów w kontekście portfela inwestycyjnego.

Autor setek komentarzy rynkowych, analiz porównawczych produktów bankowych i materiałów edukacyjnych. Zwolennik transparentności — każdy ranking i recenzja na MyBank.pl opiera się na jawnej metodologii i zweryfikowanych źródłach (taryfy banków, regulaminy promocji, dane NBP).

Treści mają charakter edukacyjny i informacyjny — nie stanowią porady inwestycyjnej, rekomendacji ani oferty. Decyzje finansowe podejmuj na podstawie własnej analizy i konsultacji z doradcą.