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.
- 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 funkcjatime); 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
pandasimatplotlib(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.
S1 · Wczytanie dziennika CSV
Python 3 · pandasPunkt 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 · pandasTe 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 + matplotlibRysuje 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.
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 standardowaWolumen 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źnikNarzę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źnikSekcja 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.
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.
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.
math.floor w S4 zamiast round: maszyna zaokrągla w dół bez dyskusji, człowiek — w górę z uzasadnieniem.Powiązane w kursie
- 11.8 Python w tradingu — instalacja środowiska, pierwszy skrypt i praca z pakietami
- Dodatek F — arkusz dziennika — format CSV, który czytają S1–S3, i te same metryki w wersji formuł arkusza
- Dodatek H, prompt P3 — przegląd każdej modyfikacji tego kodu (lookahead, repainting, błędy logiki)
- Dział narzędzi — kalkulatory — wartość pipsa i sizing w wersji przeglądarkowej, do weryfikacji wyników S4
- Dodatek E — szablon planu — okna handlu i limity, które S6 rysuje na wykresie
- 15.10 Audyt strategii — dyscyplina wnioskowania z liczb, które ten zestaw produkuje
Źródła
- 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.
- 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.
- 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.