Kit de código del trader — Python y Pine Script

Seis fragmentos para copiar: cuatro Python (diario CSV, métricas, curva con drawdown y dimensionador ATR) y dos Pine Script (rango diario frente a ATR con alerta y ventanas de sesión del plan en el gráfico). Todos son ANALÍTICOS: calculan, dibujan y recuerdan; ninguno genera señales de compra/venta ni decide, pues eso corresponde al playbook. Los identificadores ejecutables, APIs y nombres técnicos conservan el inglés de pandas y Pine Script; los comentarios y mensajes explicativos se han traducido al español de forma compatible con el código (el mismo enfoque que el taller de IA utiliza en prompts con código); las explicaciones del curso guían paso a paso. La instalación Python y primeras ejecuciones se tratan en 11.8; los encabezados del diario están en el complemento F.

Puesto de trader y analista: editor con scripts Python de análisis del diario y gráfico con indicadores Pine Script en pantalla
Lo esencial en 60 segundos
  • Herramientas, no estrategias: el código calcula métricas con SUS datos y sigue SU plan; el límite es el mismo que «lo que no hace la IA» del complemento H, por las mismas razones
  • Formato común de datos: Python lee encabezados de la hoja F; tasa de acierto incluye operaciones a equilibrio en el denominador y esperanza usa resultado neto, igual que P5
  • Pine Script sin información futura: S5 obtiene datos diarios con lookahead=barmerge.lookahead_off; S6 no accede a otro marco temporal (solo función time ). No tener lookahead no prueba ausencia de repintado; S5 explica barras históricas y tiempo real. Son los fallos que busca revisión P3
  • El dimensionador redondea ABAJO: al convertir riesgo en lotes, redondear arriba aumenta sistemáticamente el riesgo; esa línea distingue 1% de «aproximadamente 1%»

Antes de ejecutar: tres reglas

  • Lea antes de ejecutar. Cada fragmento es deliberadamente breve: debe entender cada línea. Modifique lo que no entienda con revisión P3, no adivinando; doblemente importante para código de foros y chats.
  • Entran datos de archivos; del código no salen decisiones. Los scripts leen exportación del diario F y parámetros explícitos. Salen cifras y gráficos para revisión semanal; usted concluye siguiendo auditoría 15.10 y considerando muestra, dependencias e incertidumbre.
  • Entorno de 11.8: Python 3.11 o posterior con pandas y matplotlib (allí instalación y primer script); S1–S4 se volvieron a ejecutar el 17 de septiembre de 2026 con Python 3.12.10, pandas 3.0.5 y matplotlib 3.11.1. Otras versiones no se probaron. Pegue Pine Script en editor de indicadores; ambos usan versión 6.

Pasos mínimos: en entorno Python 3.11+, compruebe versión con python --version, instale paquetes con python -m pip install pandas==3.0.5 matplotlib==3.11.1, guarde S1+S2+S3 como analiza_dziennika.py y CSV de prueba como dziennik.csv, ejecute después python analiza_dziennika.py. S4 solo necesita biblioteca estándar. Pegue S5 y S6 por separado en Pine Editor. Ambos se revisaron frente a documentación v6; no se compilaron ni probaron sus alertas en TradingView.

Flujo: exportación CSV entra a Python; salen métricas y curva; conclusiones alimentan revisión semanal y playbook Diario CSVformato del complemento F Python: S1–S3métricas · curva · drawdown Revisión EOWmuestra, dependencia, incertidumbre Playbookversione reglas y pruebas no hay flecha «código → orden»; no es un olvido

S1 · Cargar un diario CSV

Python 3 · pandas

Entrada de S2 y S3: lee exportación F, comprueba encabezados y número de campos y limpia columna r_net . Nombres duplicados, encabezado vacío y campos de más o menos detienen importación; no elige entre dos columnas de resultado ni mueve datos al índice. Represente campo vacío con separador, por ejemplo ;A+;;0; una fila truncada es distinta. Configure separador y decimal según variante (PL: punto y coma y coma; EN: coma y punto) y codificación: el generador F usa UTF-8 y «CSV UTF-8» de Excel también puede leerse como UTF-8; exportación antigua con página polaca puede necesitar cp1250. Compruebe formato, pues idioma de interfaz no determina codificación. Un r_net vacío y un close_time_utc vacío significan operación aún abierta y se omite; hora de cierre sin resultado es registro incompleto y detiene cálculo. Valor no vacío ilegible como número (incluidos «NA» o guion) detiene y señala registro; también operación cerrada sin hora de cierre en formato F (2026-07-08 13:15), pues habría que adivinar orden de la curva.

import pandas as pd

CSV_PATH = "dziennik.csv"   # exportación del diario (estructura del anexo F)
CSV_SEP  = ";"              # ";" para CSV con configuración PL, "," para EN
DECIMAL  = ","              # "," para CSV con configuración PL, "." para EN
ENCODING = "utf-8-sig"      # el generador del anexo F escribe UTF-8 (admite BOM); un CSV antiguo con página de códigos polaca puede requerir "cp1250"

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

# Leer un archivo local y validar su estructura ANTES de crear un DataFrame.
# pandas.read_csv puede renombrar cabeceras duplicadas o inferir un índice de campos adicionales.
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("No se puede leer el CSV: la cabecera está vacía.")
        if any(not name or name != name.strip() for name in header):
            raise SystemExit("Nombre de columna vacío o con espacios en los extremos — corregir la cabecera CSV.")
        duplicates = sorted({name for name in header if header.count(name) > 1})
        if duplicates:
            raise SystemExit(f"Columnas duplicadas: {duplicates} — resolverlas en el diario.")
        rows, record_indexes = [], []
        for index, row in enumerate(reader):
            if not row:                              # un registro CSV completamente vacío
                continue
            if len(row) != len(header):
                raise SystemExit(f"Registro {index + 1}: se esperaban {len(header)} campos, se obtuvieron {len(row)} "
                                 f"— revisar separadores y comillas; no se descartó ningún campo.")
            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"No se puede decodificar {CSV_PATH} como {ENCODING} — revisar UTF-8 o la antigua cp1250.") from None
except (OSError, csv.Error) as exc:
    raise SystemExit(f"No se puede leer {CSV_PATH}: {exc}") from None

missing = [c for c in REQUIRED if c not in df.columns]
if missing:
    raise SystemExit(f"Faltan columnas: {missing} — contrastar cabeceras con el anexo F y el ajuste CSV_SEP.")

raw = df["r_net"]                                   # se permite un resultado vacío solo si la hora de cierre también está vacía
txt = raw.astype("string").str.strip().str.replace(DECIMAL, ".", regex=False)   # interpretar nosotros la columna: una celda incorrecta no debe ocultar las legibles
r   = pd.to_numeric(txt, errors="coerce").astype("Float64")  # evitar desbordamientos de enteros en las sumas posteriores
r   = r.where(r.abs() != float("inf"))              # inf tampoco es un múltiplo R válido
invalid = raw.notna() & r.isna()                    # rellenado, pero no legible como número
if invalid.any():
    raise SystemExit(f"r_net no válido en los registros {(df.index[invalid] + 1).tolist()} "
                     f"(registro 1 = primera fila bajo la cabecera) — revisar valores y DECIMAL; "
                     f"NA, guiones e infinito no son números.")

blank = df.isna().all(axis=1)                       # fila totalmente vacía = fila sin usar de la hoja (exportación XLSX completa), no una operación
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"Falta r_net en los registros cerrados {(df.index[incomplete] + 1).tolist()} "
                     f"— completar el resultado neto antes de calcular las métricas.")
open_trades = int(r.isna().sum())
df = df.assign(r_net=r).dropna(subset=["r_net"])   # índice conservado = números de registro para el mensaje siguiente

# la curva y el drawdown requieren orden cronológico (anexo F: por hora de cierre); una operación cerrada sin hora de cierre legible detiene el 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)   # el anexo F escribe 2026-07-08 13:15; el formato fijo independiza la comprobación del orden de filas; utc=True evita fallos por sufijos inesperados "Z"/"+00:00"
if closed.isna().any():
    raise SystemExit(f"close_time_utc ilegible en los registros {(df.index[closed.isna()] + 1).tolist()} "
                     f"— cada operación cerrada necesita una hora de cierre en el formato del anexo F (2026-07-08 13:15); corregir el diario, no el script.")
df = df.assign(_t=closed).sort_values("_t", kind="stable").drop(columns="_t").reset_index(drop=True)

print(f"Cargadas {len(df)} operaciones cerradas ({open_trades} fila(s) omitida(s): sin hora de cierre ni r_net).")

Obtiene: un DataFrame, df, preparado para siguientes fragmentos y ordenado por cierre (curva y drawdown siguen ese orden), más número de abiertas omitidas (fila vacía es registro enteramente vacío, como al exportar toda XLSX, y no se cuenta; con datos pero sin resultado ni cierre se omite como abierta; entrada cerrada sin resultado detiene). Si lista registros, corrija filas del diario, no el script; si afecta toda la columna revise también constante DECIMAL . Numeración empieza tras encabezado e incluye registros vacíos; un campo entre comillas puede contener separador o salto de línea y sigue siendo un registro. Cada operación debe aparecer una vez: compruebe identificadores trade_id antes de combinar exportaciones; S1 no elimina duplicados. Fecha sin hora no basta para reconstruir secuencia. Hora sin zona se trata como UTC; un desplazamiento ISO 8601 se convierte a UTC. Para horas idénticas se conserva orden del archivo; establézcalo en el diario, pues afecta al drawdown.

S2 · Métricas del diario

Python 3 · pandas

Mismas definiciones que P5 y panel de la hoja: tasa de acierto incluye equilibrio en denominador; factor de beneficio devuelve mensaje en vez de dividir por cero; esperanza es media de R neto (r_net); drawdown se mide desde curva acumulada inicial de 0 R (primera pérdida ya es drawdown, como fórmula F). Ejecute tras S1. Comparten definiciones, pero política de error difiere de P5: S1 detiene análisis por resultado inválido de cerrada; P5 ordena omitirla y contarla. Compare solo tras establecer qué operaciones incluyen ambas muestras.

import math

def metrics(r):
    "Métricas principales del diario sobre una serie de múltiplos R netos.\n    Definiciones (como en el panel del anexo F):\n    - tasa de acierto: operaciones con r > 0 entre TODAS las válidas (break-even permanece en el denominador)\n    - factor de beneficio: suma de r positivos entre abs(suma de r negativos)\n    - esperanza matemática: media aritmética de r (neta de costes)\n    - drawdown máximo: mayor caída de la curva R acumulada desde su máximo móvil,\n      con inicio en 0 R (una primera pérdida de -1 R ya supone 1 R de drawdown)"
    n = len(r)
    if n == 0:
        raise SystemExit("No hay operaciones válidas — nada que medir.")
    try:
        r = r.astype(float)
    except (TypeError, ValueError):
        raise SystemExit("Las métricas requieren valores R netos numéricos — ejecutar S1 primero.") from None
    if not r.map(math.isfinite).all():
        raise SystemExit("Las métricas requieren valores R netos finitos — ejecutar S1 primero.")
    wins   = r[r > 0]
    losses = r[r < 0]
    equity   = r.cumsum()
    peak     = equity.cummax().clip(lower=0)   # anexo F: MAX(0; máximo móvil)
    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("Rango numérico superado — revisar los valores R; no se devolvieron métricas.") 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("Rango numérico superado — revisar los valores R; no se devolvieron métricas.")
    pf = "sin pérdidas en la muestra" 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}")

Obtiene seis métricas semanales; sin pérdidas PF es mensaje, no número. Si excede rango de cálculo, detiene en lugar de devolver inf, NaN o un drawdown aparentemente cero. WR = 0.4 significa 40%. PF se calcula en R, no PLN; con riesgo monetario variable puede diferir. Costes ya incluidos en r_net ; S2 no los resta otra vez. No espere evaluación de ventaja solo de la media ni por superar 20 o 50 operaciones. Definiciones del panel en complemento F y plan de muestra, costes y validación en complemento N.

S3 · Curva de patrimonio y drawdown

Python 3 · pandas + matplotlib

Dibuja curva acumulada R y drawdown del máximo debajo y guarda PNG junto al CSV indicado por CSV_PATH (S1), independientemente de carpeta de ejecución. Revela secuencia y agrupaciones de pérdidas ocultas en agregados. Es suma de R, no saldo en PLN ni equity porcentual: con riesgo monetario variable no reproduce cambios de saldo. No incluye depósitos, retiradas ni posiciones abiertas. Ejecute tras S1.

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

if len(df) == 0:
    raise SystemExit("No hay operaciones cerradas — nada que representar.")

equity   = df["r_net"].astype(float).cumsum()
drawdown = equity.cummax().clip(lower=0) - equity   # misma definición que S2 y el anexo F (la curva empieza en 0 R)

if not equity.map(math.isfinite).all() or not drawdown.map(math.isfinite).all():
    raise SystemExit("Rango numérico superado — revisar los valores R; no se guardó ningún gráfico.")

fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9, 6), sharex=True,
                               gridspec_kw={"height_ratios": [3, 1]})
x = [0, *(equity.index + 1)]                        # operación 0 = punto inicial de 0 R desde el que se mide el drawdown
ax1.plot(x, [0.0, *equity.values])
ax1.set_ylabel("resultado (R)")
ax1.set_title("Curva de resultados — R netos acumulados")
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("operación n.º")
ax2.grid(True, alpha=0.3)

fig.tight_layout()
PNG_PATH = Path(CSV_PATH).with_name("equity_curve.png")   # junto al archivo del diario, sea cual sea el directorio de trabajo
fig.savefig(PNG_PATH, dpi=150)
plt.close(fig)
print(f"Guardado: {PNG_PATH}")

Obtiene un equity_curve.png junto al diario; curva comienza en 0 R (operación 0), base de drawdown. El panel inferior muestra caída de R acumulado desde máximo (inicial 0 R, como S2 y hoja F) de toda la historia, no pérdida semanal. Compruebe límite semanal del plan E por separado, semana a semana, con su unidad y regla de reinicio.

Archivo de prueba y salida esperada (S1 + S2 + S3) CSV · formato PL

Guarde como dziennik.csv (UTF-8) junto al script S1+S2+S3 y ejecute. Solo incluye columnas necesarias para S1; exportación completa F (26 columnas) se carga igual. Última fila es operación abierta (r_net vacío); primeras dos cerradas pierden, por lo que drawdown parte de 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;;

Salida esperada (Python 3.12.10, pandas 3.0.5, matplotlib 3.11.1; ejecutado el 17 de septiembre de 2026):

Cargadas 5 operaciones cerradas (1 fila(s) omitida(s): sin hora de cierre ni 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
Guardado: equity_curve.png

Compruebe a mano: ganancias 1.83 + 2.40 = 4.23 R, pérdidas 1.00 + 1.08 = 2.08 R → PF 2.03; total 2.15 R / 5 = 0.43 R; curva −1.00 → −2.08 → −0.25 → −0.25 → 2.15, con máximo inicial 0 R → drawdown 2.08 R (sin punto 0 R sería 1.08 R: distinción de definición F). La salida corresponde exactamente a este archivo y versiones; si difiere, revise en orden datos (separador, decimal, codificación y filas), constantes S1, código y versiones.

S4 · Dimensionador de posición ATR

Python 3 · biblioteca estándar

Volumen en lotes para stop k × ATR: riesgo de capital y porcentaje, distancia de ATR y redondeo ABAJO al paso de lotes de su cuenta (parámetros lot_step y min_lot , ambos 0.01 por defecto), para no superar riesgo PREVISTO. Pérdida real puede ser mayor: no incluye deslizamiento, huecos, spread, comisión ni swap. Capital y valor del pip deben estar en misma divisa; compruebe aparte volumen máximo y margen en especificación. Introduzca ATR en pips (EURUSD 0.0080 → 80; USDJPY 0.80 → 80; tamaño del pip en perfil B); el valor para 1 lote está en especificación o calculadora 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):
    "Volumen en lotes para un stop de atr_mult × ATR.\n    atr_pips: ATR en PIPS, no en unidades de precio (EURUSD ATR 0.0080 -> 80; USDJPY ATR 0.80 -> 80 — tamaño de pip del par).\n    pip_value_per_lot: valor de 1 pip para 1.00 lote, en divisa de la cuenta\n    (p. ej., ~10 USD para EURUSD en cuenta USD — revisar especificaciones).\n    lot_step / min_lot: paso y mínimo de volumen de TU cuenta (0.01 es frecuente, no universal).\n    Redondea HACIA ABAJO a múltiplos de lot_step medidos desde cero. La aritmética de fracciones\n    conserva las entradas decimales sin depender de la precisión global de Decimal.\n    El riesgo planificado no es la pérdida máxima: el modelo no incluye slippage, gaps, spread ni swap."
    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("Usar valores numéricos, no texto ni booleanos (los decimales de Python usan punto).")
    decimals = tuple(Decimal(str(x)) for x in inputs)
    if not all(x.is_finite() and x > 0 for x in decimals):
        raise ValueError("Todas las entradas deben ser números finitos y positivos.")
    c, p, a, k, v, step, minimum = map(Fraction, decimals)
    if p > 100:
        raise ValueError("risk_pct es un porcentaje del 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 hay volumen admisible para este lot_step y min_lot dentro del presupuesto de riesgo."}
    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("Las entradas superan el rango numérico de salida.") from None
    if not all(math.isfinite(value) and value > 0 for value in result.values()):
        raise ValueError("Las entradas superan el rango numérico de salida.")
    return result  # flotantes para mostrar; sin redondeo adicional del stop ni de importes monetarios


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}

Obtiene lotes redondeados abajo y riesgo PREVISTO resultante (sin costes, ≤ presupuesto; pérdida real puede ser mayor). El paso se mide desde cero; confirme esa cuadrícula en su cuenta. Stop y dinero no se redondean más: ATR de 0.26 pips × 1.25 da 0.325 pips, no 0.3. Se devuelven valores float para lectura y los pasos se calculan con fracciones exactas; compruebe aparte incremento mínimo del precio. Si ajusta stop a ese incremento, recalcule volumen. No espere decisión de entrada: calcula «cuánto», nunca «si entrar».

S5 · Rango diario frente a ATR con alerta

Pine Script v6 · indicador

Herramienta de conciencia de volatilidad intradía: en barras reales muestra qué porcentaje del ATR diario ya se ha «usado» y alerta al cruzar umbral, momento de consultar plan, no perseguir precio. Obtiene datos diarios sin lookahead (sin futuro); en históricas, request.security mantiene último valor diario confirmado hasta final de siguiente periodo D y muestra el nuevo en última barra intradía. En tiempo real obtiene rango y ATR en desarrollo de vela D actual, por lo que lecturas previas pueden cambiar al recargar (repintado). Día significa vela D del proveedor, no día civil Europe/Warsaw. ATR del denominador también cambia durante el día; 100% no limita el movimiento posible. No reconstruye cruces sucesivos de hoy en barras históricas.

//@version=6
indicator("Rango diario frente a ATR (herramienta de contexto)", overlay=false)

if not timeframe.isintraday
    runtime.error("Usar un gráfico intradía temporal inferior a 1D.")

atrLen  = input.int(14, "Longitud ATR", minval=2)
warnPct = input.int(80, "Alertar cuando el rango diario alcance % del ATR", minval=10, maxval=200)

// Histórico: último valor D confirmado, actualizado al FINAL de cada periodo D.
// Tiempo real: valores D en formación; las lecturas pasadas pueden repintarse al recargar.
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, "Rango diario como % del ATR", linewidth=2)
hline(100, "100% del ATR")
hline(warnPct, "Nivel de alerta", linestyle=hline.style_dotted)

alertcondition(ta.crossover(rangePct, warnPct),
     title="Rango alcanzó % del ATR",
     message="El rango diario alcanzó tu % del ATR — releer el plan antes de cualquier nueva entrada.")

Obtiene panel inferior y condición de alerta; créela en plataforma tras añadir indicador y seleccione «Rango alcanzó % de ATR». Use velas intradía temporales estándar; Heikin Ashi/Renko pueden diferir. Lectura ausente (na) significa falta ATR o datos, no 0%. Condición exige pasar de valor ≤ umbral a > umbral; permanecer encima no repite alerta. «Una vez por cierre de barra» limita a cierre intradía, no confirma toda vela D. Recree alerta tras cambiar parámetros. No espere señal direccional: lectura alta dice «ya ocurrió movimiento»; playbook decide actuación.

S6 · Ventanas de sesión del plan en el gráfico

Pine Script v6 · indicador

Sección 3 del plan E dibujada: resalta horas en que SE PERMITE abrir y alerta al cerrar ventana. Introduzca horas Europe/Warsaw; identificador IANA incluye CET/CEST. Por defecto lunes a viernes; opción amplía a todos los días. Solo confirma horario del plan, no otras condiciones. Use velas intradía temporales estándar.

//@version=6
indicator("Ventanas de sesión del plan de trading", overlay=true)

if not timeframe.isintraday
    runtime.error("Usar un gráfico intradía temporal inferior a 1D.")

tz   = input.string("Europe/Warsaw", "Zona horaria de las ventanas")
win1 = input.session("0900-1200", "Ventana 1")
win2 = input.session("1400-1700", "Ventana 2")

weekdaysOnly = input.bool(true, "Solo de lunes a viernes")
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="Dentro de una ventana del plan")

windowJustClosed = inWin[1] and not inWin
alertcondition(windowJustClosed,
     title="Ventana del plan cerrada",
     message="Tu ventana operativa acaba de cerrarse — gestionar solo posiciones abiertas, sin nuevas entradas (plan, sección 3).")

Obtiene sombreado del horario y condición de cierre («Ventana del plan cerrada»: cree alerta en plataforma tras añadir indicador). Detecta primera vela fuera tras otra dentro, por lo que no garantiza aviso exacto al cerrar: sin vela nueva ni actualización no comprueba nada. Use marco con aperturas/cierres alineados a límites. En M15, ventana 09:00–12:00 y vela 12:00 disponible, sombreado termina 12:00 y condición activa en esa primera vela exterior. «Una vez por barra» puede avisar en primera actualización; «Una vez por cierre» solo al cerrar. Ventanas solapadas forman unión: fin de primera no cierra segunda. Recree tras cambiar horas. No bloquea órdenes: recuerda plan; lista previa lo exige.

Indicadores Pine Script v6 sobre EUR/USD en TradingView: ventanas del plan S6 y panel diario rango/ATR con alerta al 80% (S5)
Indicadores Pine Script v6 sobre EUR/USD en TradingView: ventanas del plan S6 y panel diario rango/ATR con alerta al 80% (S5)
Todo el kit en un archivo generado localmente en navegador

Seis fragmentos con títulos, instrucciones y versiones, seguidos de CSV de prueba y salida esperada, para archivar junto al diario o en repositorio propio. Mezcla lenguajes: no ejecute todo como Python. Elija S1+S2+S3, S4 independiente o un indicador Pine.

Salida real S3 de cinco operaciones de prueba: editor Python, métricas de terminal y curva de patrimonio y drawdown generados
Salida real S3 de cinco operaciones de prueba: editor Python, métricas de terminal y curva de patrimonio y drawdown generados

Preguntas frecuentes

¿Por qué conserva el código nombres técnicos en inglés?

Los nombres de APIs, funciones y parámetros conservan el inglés de la documentación pandas y Pine Script y de los errores de las bibliotecas. Aquí comentarios y mensajes explicativos están en español, manteniendo identificadores y lógica; el taller de IA sigue esta separación en prompts de código (H, P3). Las explicaciones externas aclaran QUÉ hace y cuándo usarlo; los comentarios internos, CÓMO. La excepción de comentarios polacos en la versión polaca de P5 se refiere al código puntual generado para el usuario, no a una biblioteca. La adaptación lingüística del kit conserva programas y formatos ejecutables.

¿Cómo ejecuto los fragmentos Python?

11.8 explica instalación Python, entorno y paquetes paso a paso: pandas y matplotlib bastan. Combine S1–S3 en un archivo, analiza_dziennika.py (S1 primero), y ejecute python analiza_dziennika.py en carpeta del diario; S4 va aparte. La ruta relativa CSV_PATH se refiere al directorio de trabajo del terminal. Empiece con CSV de prueba y analice copia del diario real; equity_curve.png se sobrescribe al ejecutar otra vez. Si no entiende un error, lleve código y mensaje completo a revisión P3 del complemento H, no adivine.

¿Puedo modificar y ampliar?

Para eso es un kit INICIAL. Ampliaciones naturales: métricas por setup (agrupe columna antes de función S2), filtrar error_code para ejecución limpia o curvas A+/B separadas. Dos reglas: revisión P3 para todo cambio (especialmente request.security en Pine) y versionar tanto definiciones de métricas como reglas. Corrija error de cálculo inmediatamente y recalcule periodos comparados con mismo método; documente cambio de estrategia en el protocolo de pruebas N y pruébelo con datos nuevos. Acabar trimestre no justifica por sí solo cambiar, ni estar a mitad conservar error.

¿Por qué no contiene estrategias ni EAs?

Por lo mismo que el taller de IA no genera señales: estrategia de internet tiene ventaja y supuestos desconocidos y no enlaza con su plan; ejecutar lógica ajena en su cuenta delega decisiones que usted paga. Este código refuerza proceso (medición, visualización y cumplir plan), no lo reemplaza. Para automatizar estrategia propia probada, pase por el backtest de 15.4, protocolo N y revisión P3, no copiar scripts «rentables».

Escenario ilustrativo: redondear «a un número más bonito». Dimensionador dio 0.07 lotes; trader pulsó 0.1: «diferencia cosmética y redondo». Equivale a +43% riesgo por operación sistemáticamente todo trimestre; límite diario −2R era efectivamente −2.9R y drawdown excedió plan pese a verlo cosmético. Por eso math.floor en S4 en lugar de round: máquina redondea abajo sin discutir; humano arriba con justificación.
Escenario ilustrativo: pérdidas agrupadas visibles solo en curva. Métricas trimestrales razonables: esperanza positiva y drawdown esperado. S3 revela que todo drawdown vino de una semana; filtro de hora en S2 precisa pérdidas entre 21:00 y 23:00 UTC (diario F usa UTC), con spreads amplios «porque me quedo un poco más». Coincidir con rollover depende de bróker y zona; compruebe especificación. El agregado promedia; la curva muestra CUÁNDO sangra sistema. Una línea (sin nuevas entradas tras 21:00 UTC, o 23:00 CEST / 22:00 CET: anótelo en Europe/Warsaw en plan y S6) es aquí conclusión para probar después, no mejora garantizada; justo lo que apoya el kit.
Aviso: página educativa, no asesoramiento. Ejecute herramientas analíticas bajo su responsabilidad: lea código, verifique resultados (p. ej., calculadora C) y decida según su plan. Python (S1–S4) no conecta a internet ni cuenta: lee archivos y dibuja gráficos; Pine (S5–S6) usa datos de su plataforma. Ninguno genera señales ni coloca órdenes.

Fuentes

  1. Documentación: pandas 3.0.5 y matplotlib 3.11.1 (funciones de S1–S3); Python: lectura CSV y aritmética de fracciones (validación de formato y pasos de lote exactos); S1–S4 ejecutados con datos sintéticos el 17 de septiembre de 2026 en Python 3.12.10; TradingView Pine Script v6: otros marcos y datos, sesiones, tiempo y zonas, alertas (request.security con lookahead y repintado, hline, input.session, alertcondition); S5–S6 revisados con documentación, sin compilación.
  2. Internas: 11.8 Python en trading (entorno), 15.9 (métricas y formato del diario), 15.10 (principios de conclusiones): implementación práctica condensada.
  3. Van K. Tharp, Trade Your Way to Financial Freedom, 2.ª ed., McGraw-Hill 2007: múltiplos R y esperanza, lenguaje común de S2, hoja F y prompt P5.

Jarosław Wasiński

Redactor jefe, ForexMechanics.com • Analista macro y FX

Analista independiente y profesional con mas de 20 anos de experiencia en los mercados financieros. Activo en Forex desde 2004, con enfoque en analisis fundamental, estructura del mercado OTC y gestion disciplinada del riesgo de capital.

  • Creador y redactor jefe de MyBank.pl, ofreciendo educacion financiera fiable desde 2004.
  • Autor de cientos de comentarios de mercado en profundidad, analisis estructurales y materiales educativos para traders e inversores.
  • Defensor de la transparencia del mercado, promoviendo una educacion basada en datos concretos e informes institucionales (BIS, ESMA, CFTC).

Todo el contenido tiene caracter exclusivamente educativo e informativo – no constituye asesoramiento de inversion ni recomendacion alguna. Recuerde: el trading con apalancamiento (Forex/CFD) conlleva un alto riesgo de perdida rapida de capital.