valori
This commit is contained in:
14
README.md
14
README.md
@@ -47,7 +47,7 @@ Se generează `data/backtest.xlsx`. Deschide-l în Excel sau LibreOffice Calc.
|
||||
- **TF** (1min / 3min / 15min — dropdown; e TF-ul de entry, vezi mai jos)
|
||||
- **Direcție** (Buy / Sell — dropdown)
|
||||
- **SL %**, **TP0 %**, **TP1 %**, **TP2 %** — distanțe față de entry, în procente (ex. 0.30 pentru 0.30%)
|
||||
- **Outcome** (SL / TP0 only / TP1 / TP2 — dropdown)
|
||||
- **Outcome** (SL / TP0 / TP1 / TP2 — dropdown)
|
||||
- **Notes** (opțional)
|
||||
4. Coloanele albastre derivate (Zi, Sesiune, R_*, $_*, Bal_* pentru cele 5 strategii) se umplu automat.
|
||||
5. Mergi la sheet-ul **Dashboard** — metricile, breakdown-urile și equity curve se actualizează live.
|
||||
@@ -85,10 +85,10 @@ Pe baza Data + Ora RO, regulile M2D (vezi `strategie_M2D.md`):
|
||||
Sheet-ul **Config** permite editarea:
|
||||
|
||||
- **Account Size Start ($)** — balanța inițială (default $10,000)
|
||||
- **Risk per Trade (%)** — % din account riscat per trade (default 1.0%)
|
||||
- **Risk reper (%)** — reper configurabil; sumele $ din coloanele `$_*` folosesc `SL % × Account Size Start`
|
||||
- Listele pentru dropdown-uri: Strategii, Indicatori, TF, Direcție, Outcome
|
||||
|
||||
La schimbarea Account Size sau Risk %, toate sumele $ din Trades și Dashboard se recalculează.
|
||||
La schimbarea Account Size, sumele $ din `$_*`, `Bal_*` și Dashboard se recalculează. Risk-ul efectiv pe trade este variabil: `SL % × Account Size Start`.
|
||||
|
||||
## Formule R-multiples (referință)
|
||||
|
||||
@@ -99,20 +99,20 @@ Tabelul de mai jos arată R-multiple-ul rezultat pentru fiecare combinație (Out
|
||||
| Outcome | TP0 only | TP1 only | TP2 only | Hybrid + BE | Hybrid no BE |
|
||||
|---------|----------|----------|----------|-------------|--------------|
|
||||
| **SL** | −1 | −1 | −1 | −1 | −1 |
|
||||
| **TP0 only** | +TP0/SL | −1 | −1 | +0.5·TP0/SL | +0.5·TP0/SL − 0.5 |
|
||||
| **TP0** | +TP0/SL | −1 | −1 | +0.5·TP0/SL | +0.5·TP0/SL − 0.5 |
|
||||
| **TP1** | +TP0/SL | +TP1/SL | −1 | +0.5·(TP0+TP1)/SL | +0.5·(TP0+TP1)/SL |
|
||||
| **TP2** | +TP0/SL | +TP1/SL | +TP2/SL | +0.5·(TP0+TP1)/SL | +0.5·(TP0+TP1)/SL |
|
||||
|
||||
**Citirea Outcome-ului**:
|
||||
- `SL` — prețul a atins SL fără să atingă vreodată TP0 (loss complet).
|
||||
- `TP0 only` — prețul a atins TP0, dar nu și TP1 (ulterior fie a venit înapoi la SL, fie a fost închis la BE pentru variantele cu BE move).
|
||||
- `TP0` — prețul a atins TP0, dar nu și TP1 (ulterior fie a venit înapoi la SL, fie a fost închis la BE pentru variantele cu BE move).
|
||||
- `TP1` — prețul a atins TP1 (a trecut prin TP0).
|
||||
- `TP2` — prețul a atins TP2 (a trecut prin TP0 și TP1).
|
||||
|
||||
**Asumpții de simulare**:
|
||||
- `TP1 only` și `TP2 only` simulează OCO pur, fără intervenție manuală. Outcome=`TP0 only` se închide la SL (presupunere worst-case).
|
||||
- `TP1 only` și `TP2 only` simulează OCO pur, fără intervenție manuală. Outcome=`TP0` se închide la SL (presupunere worst-case).
|
||||
- `TP2 only` cu Outcome=`TP1` se închide la SL (TP1 a fost atins, dar SL ar fi venit înainte de TP2).
|
||||
- Diferența dintre Hybrid + BE și Hybrid no BE apare doar când Outcome=`TP0 only`; la TP1/TP2 ambele dau identic.
|
||||
- Diferența dintre Hybrid + BE și Hybrid no BE apare doar când Outcome=`TP0`; la TP1/TP2 ambele dau identic.
|
||||
|
||||
## Regenerare template
|
||||
|
||||
|
||||
BIN
data/backtest.backup-20260521-002847.xlsx
Normal file
BIN
data/backtest.backup-20260521-002847.xlsx
Normal file
Binary file not shown.
BIN
data/backtest.corrupt-20260521-004058.xlsx
Normal file
BIN
data/backtest.corrupt-20260521-004058.xlsx
Normal file
Binary file not shown.
Binary file not shown.
164
scripts/codex_statusline.ps1
Normal file
164
scripts/codex_statusline.ps1
Normal file
@@ -0,0 +1,164 @@
|
||||
param(
|
||||
[string]$SessionFile = "",
|
||||
[string]$Workdir = "",
|
||||
[string]$CodexHome = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
function Get-DefaultCodexHome {
|
||||
if ($env:CODEX_HOME) { return $env:CODEX_HOME }
|
||||
return (Join-Path $HOME ".codex")
|
||||
}
|
||||
|
||||
function Format-Tokens([Int64]$Value) {
|
||||
if ($Value -ge 1000000) { return ("{0:0.#}M" -f ($Value / 1000000.0)) }
|
||||
if ($Value -ge 1000) { return ("{0:0.#}K" -f ($Value / 1000.0)) }
|
||||
return "$Value"
|
||||
}
|
||||
|
||||
function Format-ResetTime($EpochSeconds) {
|
||||
if (-not $EpochSeconds) { return "--" }
|
||||
try {
|
||||
$dto = [DateTimeOffset]::FromUnixTimeSeconds([Int64]$EpochSeconds).ToLocalTime()
|
||||
return $dto.ToString("ddd HH:mm")
|
||||
} catch {
|
||||
return "--"
|
||||
}
|
||||
}
|
||||
|
||||
function Format-Remaining($EpochSeconds) {
|
||||
if (-not $EpochSeconds) { return "--" }
|
||||
try {
|
||||
$remaining = [DateTimeOffset]::FromUnixTimeSeconds([Int64]$EpochSeconds) - [DateTimeOffset]::Now
|
||||
if ($remaining.TotalSeconds -le 0) { return "resetting" }
|
||||
if ($remaining.TotalDays -ge 1) { return ("{0}d {1}h" -f [int]$remaining.TotalDays, $remaining.Hours) }
|
||||
if ($remaining.TotalHours -ge 1) { return ("{0}h {1}m" -f [int]$remaining.TotalHours, $remaining.Minutes) }
|
||||
return ("{0}m" -f [Math]::Max(1, [int]$remaining.TotalMinutes))
|
||||
} catch {
|
||||
return "--"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-Number($Value, [Int64]$Default = 0) {
|
||||
if ($null -eq $Value) { return $Default }
|
||||
try { return [Int64]$Value } catch { return $Default }
|
||||
}
|
||||
|
||||
function Read-JsonLine([string]$Line) {
|
||||
if ([string]::IsNullOrWhiteSpace($Line)) { return $null }
|
||||
try { return ($Line | ConvertFrom-Json) } catch { return $null }
|
||||
}
|
||||
|
||||
function Get-TokenEventFromObject($Obj) {
|
||||
if (-not $Obj) { return $null }
|
||||
if ($Obj.type -eq "event_msg" -and $Obj.payload.type -eq "token_count") { return $Obj.payload }
|
||||
if ($Obj.type -eq "token_count") { return $Obj }
|
||||
if ($Obj.info -and $Obj.rate_limits) { return $Obj }
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-LatestSessionFile([string]$HomeDir) {
|
||||
$sessionsDir = Join-Path $HomeDir "sessions"
|
||||
if (-not (Test-Path $sessionsDir)) { return $null }
|
||||
return Get-ChildItem -Path $sessionsDir -Recurse -Filter "*.jsonl" -File |
|
||||
Where-Object { $_.Length -gt 0 } |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1
|
||||
}
|
||||
|
||||
function Get-LatestTokenEvent([string]$Path) {
|
||||
if (-not $Path -or -not (Test-Path $Path)) { return $null }
|
||||
$lines = Get-Content -LiteralPath $Path -Tail 500
|
||||
for ($i = $lines.Count - 1; $i -ge 0; $i--) {
|
||||
$obj = Read-JsonLine $lines[$i]
|
||||
$event = Get-TokenEventFromObject $obj
|
||||
if ($event) { return $event }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-SessionMeta([string]$Path) {
|
||||
if (-not $Path -or -not (Test-Path $Path)) { return $null }
|
||||
foreach ($line in (Get-Content -LiteralPath $Path -TotalCount 100)) {
|
||||
$obj = Read-JsonLine $line
|
||||
if ($obj.type -eq "session_meta") { return $obj.payload }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-ConfigModel([string]$HomeDir) {
|
||||
$config = Join-Path $HomeDir "config.toml"
|
||||
if (-not (Test-Path $config)) { return "codex" }
|
||||
$line = Get-Content -LiteralPath $config | Where-Object { $_ -match '^\s*model\s*=' } | Select-Object -First 1
|
||||
if ($line -match '=\s*"([^"]+)"') { return $Matches[1] }
|
||||
return "codex"
|
||||
}
|
||||
|
||||
if (-not $CodexHome) { $CodexHome = Get-DefaultCodexHome }
|
||||
|
||||
$stdinText = @($input) -join "`n"
|
||||
$stdinObj = Read-JsonLine $stdinText
|
||||
$event = Get-TokenEventFromObject $stdinObj
|
||||
|
||||
if (-not $SessionFile) {
|
||||
$latest = Get-LatestSessionFile $CodexHome
|
||||
if ($latest) { $SessionFile = $latest.FullName }
|
||||
}
|
||||
|
||||
if (-not $event) { $event = Get-LatestTokenEvent $SessionFile }
|
||||
$meta = Get-SessionMeta $SessionFile
|
||||
|
||||
if (-not $Workdir) {
|
||||
if ($stdinObj.workspace.current_dir) { $Workdir = $stdinObj.workspace.current_dir }
|
||||
elseif ($stdinObj.cwd) { $Workdir = $stdinObj.cwd }
|
||||
elseif ($meta.cwd) { $Workdir = $meta.cwd }
|
||||
else { $Workdir = (Get-Location).Path }
|
||||
}
|
||||
|
||||
$model = if ($stdinObj.model.display_name) { $stdinObj.model.display_name } elseif ($stdinObj.model) { $stdinObj.model } elseif ($meta.model) { $meta.model } else { Get-ConfigModel $CodexHome }
|
||||
$project = Split-Path -Path $Workdir -Leaf
|
||||
if (-not $project) { $project = "?" }
|
||||
|
||||
$branch = "-"
|
||||
if (Test-Path $Workdir) {
|
||||
$branchRaw = git -C $Workdir -c gc.auto=0 rev-parse --abbrev-ref HEAD 2>$null
|
||||
if ($LASTEXITCODE -eq 0 -and $branchRaw) {
|
||||
$branch = $branchRaw.Trim()
|
||||
$dirty = git -C $Workdir -c gc.auto=0 status --porcelain 2>$null | Select-Object -First 1
|
||||
if ($dirty) { $branch = "$branch*" }
|
||||
}
|
||||
}
|
||||
|
||||
$last = $event.info.last_token_usage
|
||||
$window = Get-Number $event.info.model_context_window
|
||||
$ctxUsed = (Get-Number $last.input_tokens) + (Get-Number $last.output_tokens)
|
||||
$ctxPct = if ($window -gt 0) { [Math]::Round(($ctxUsed * 100.0) / $window) } else { 0 }
|
||||
|
||||
$total = $event.info.total_token_usage
|
||||
$sessionTokens = Get-Number $total.total_tokens
|
||||
|
||||
$primary = $event.rate_limits.primary
|
||||
$secondary = $event.rate_limits.secondary
|
||||
$limitName = $event.rate_limits.limit_id
|
||||
if (-not $limitName) { $limitName = "codex" }
|
||||
|
||||
$primaryPct = if ($primary.used_percent -ne $null) { [Math]::Round([double]$primary.used_percent) } else { 0 }
|
||||
$primaryReset = Format-ResetTime $primary.resets_at
|
||||
$primaryRemain = Format-Remaining $primary.resets_at
|
||||
$primaryWindow = if ($primary.window_minutes) { "{0}h" -f [Math]::Round([double]$primary.window_minutes / 60.0) } else { "window" }
|
||||
|
||||
$line1 = "{0} | ctx {1}/{2} {3}% | session {4} | {5} ({6})" -f `
|
||||
$model, (Format-Tokens $ctxUsed), (Format-Tokens $window), $ctxPct, (Format-Tokens $sessionTokens), $project, $branch
|
||||
|
||||
$line2 = "limits {0} {1}%/{2} reset {3} ({4})" -f $limitName, $primaryPct, $primaryWindow, $primaryRemain, $primaryReset
|
||||
if ($secondary) {
|
||||
$secondaryPct = if ($secondary.used_percent -ne $null) { [Math]::Round([double]$secondary.used_percent) } else { 0 }
|
||||
$line2 += " | secondary {0}% reset {1}" -f $secondaryPct, (Format-Remaining $secondary.resets_at)
|
||||
}
|
||||
if ($event.rate_limits.plan_type) {
|
||||
$line2 += " | plan {0}" -f $event.rate_limits.plan_type
|
||||
}
|
||||
|
||||
Write-Output $line1
|
||||
Write-Output $line2
|
||||
@@ -55,7 +55,7 @@ SESSIONS = ["A1", "A2", "A3", "B", "C", "D", "Other"]
|
||||
INDICATORS = ["DIA", "US30", "SPY", "QQQ", "ES", "NQ"]
|
||||
TIMEFRAMES = ["1min", "3min", "15min"]
|
||||
DIRECTIONS = ["Buy", "Sell"]
|
||||
OUTCOMES = ["SL", "TP0 only", "TP1", "TP2"]
|
||||
OUTCOMES = ["SL", "TP0", "TP1", "TP2"]
|
||||
|
||||
# Cele 5 strategii de management (sufix folosit în numele coloanelor) + label friendly
|
||||
STRAT_KEYS = ["tp0only", "tp1only", "tp2only", "hybrid_be", "hybrid_nobe"]
|
||||
@@ -135,13 +135,13 @@ def build_config(wb: Workbook) -> None:
|
||||
ws["B4"] = 10000
|
||||
ws["C4"] = "Balanța inițială pentru calcule $ și HWM (model abstract)"
|
||||
|
||||
ws["A5"] = "Risk per Trade (%)"
|
||||
ws["A5"] = "Risk reper (%)"
|
||||
ws["B5"] = 1.0
|
||||
ws["C5"] = "% din account riscat per trade (= -1R)"
|
||||
ws["C5"] = "Reper opțional; $_* se calculează din SL% × Account Size Start"
|
||||
|
||||
ws["A6"] = "Risk per Trade ($)"
|
||||
ws["A6"] = "Risk reper ($)"
|
||||
ws["B6"] = "=B4*B5/100"
|
||||
ws["C6"] = "Auto — derivat din B4 și B5"
|
||||
ws["C6"] = "Auto — informativ; nu este folosit în formulele $_*"
|
||||
|
||||
for r in (4, 5):
|
||||
ws.cell(row=r, column=2).fill = INPUT_FILL
|
||||
@@ -270,7 +270,7 @@ def _f_r_tp1only(r: int) -> str:
|
||||
tp1 = f'{COL["TP1 %"]}{r}'
|
||||
return (
|
||||
f'=IF({o}="","",'
|
||||
f'IF(OR({o}="SL",{o}="TP0 only"),-1,{tp1}/{sl}))'
|
||||
f'IF(OR({o}="SL",{o}="TP0"),-1,{tp1}/{sl}))'
|
||||
)
|
||||
|
||||
|
||||
@@ -289,7 +289,7 @@ def _f_r_hybrid_be(r: int) -> str:
|
||||
return (
|
||||
f'=IF({o}="","",'
|
||||
f'IF({o}="SL",-1,'
|
||||
f'IF({o}="TP0 only",0.5*{tp0}/{sl},'
|
||||
f'IF({o}="TP0",0.5*{tp0}/{sl},'
|
||||
f'0.5*({tp0}+{tp1})/{sl})))'
|
||||
)
|
||||
|
||||
@@ -302,7 +302,7 @@ def _f_r_hybrid_nobe(r: int) -> str:
|
||||
return (
|
||||
f'=IF({o}="","",'
|
||||
f'IF({o}="SL",-1,'
|
||||
f'IF({o}="TP0 only",0.5*{tp0}/{sl}-0.5,'
|
||||
f'IF({o}="TP0",0.5*{tp0}/{sl}-0.5,'
|
||||
f'0.5*({tp0}+{tp1})/{sl})))'
|
||||
)
|
||||
|
||||
@@ -317,8 +317,10 @@ R_FN: dict[str, callable] = {
|
||||
|
||||
|
||||
def _f_dollar(r: int, r_col: str) -> str:
|
||||
"""$ P&L pe contul abstract. Variabil per trade = R × SL%/100 × Account Size."""
|
||||
rc = f"{COL[r_col]}{r}"
|
||||
return f'=IF({rc}="","",{rc}*Config!$B$6)'
|
||||
sl = f"{COL['SL %']}{r}"
|
||||
return f'=IF({rc}="","",{rc}*{sl}/100*Config!$B$4)'
|
||||
|
||||
|
||||
def _f_balance(r: int, dollar_col: str) -> str:
|
||||
@@ -566,8 +568,8 @@ METRIC_HINTS: dict[str, str] = {
|
||||
),
|
||||
"Average Loss ($)": (
|
||||
"Pierderea medie pe trade-urile negative (cifra apare cu minus).\n"
|
||||
"Cu Risk per Trade fix, ar trebui să fie aproape de −1R în dolari.\n"
|
||||
"Dacă e mult mai mare decât Risk per Trade, ai SL-uri sărite (slippage, gap-uri)."
|
||||
"În dolari reali, −1R depinde de SL%: pierdere ≈ SL% × Account Size Start.\n"
|
||||
"Dacă e mult mai mare decât riscul calculat din SL, ai SL-uri sărite (slippage, gap-uri)."
|
||||
),
|
||||
"Best Trade ($)": (
|
||||
"Cel mai mare câștig individual.\n"
|
||||
@@ -576,7 +578,7 @@ METRIC_HINTS: dict[str, str] = {
|
||||
),
|
||||
"Worst Trade ($)": (
|
||||
"Cea mai mare pierdere individuală.\n"
|
||||
"Ar trebui să fie aproximativ egală cu −1R (Risk per Trade din Config).\n"
|
||||
"Ar trebui să fie aproximativ egală cu −1R calculat din SL% × Account Size Start.\n"
|
||||
"Dacă e semnificativ mai mare, ai depășit risk-ul plănuit — SL ratat, slippage, gap overnight."
|
||||
),
|
||||
"Profit Factor": (
|
||||
@@ -590,13 +592,13 @@ METRIC_HINTS: dict[str, str] = {
|
||||
"Cu R:R mare poți avea Win Ratio mic și tot să faci bani."
|
||||
),
|
||||
"Expectancy (R)": (
|
||||
"Cât bani câștigi în medie pe UN trade (în R; 1R = Risk per Trade, default $100).\n"
|
||||
"+0.30R = câștigi $30 pe trade. Pe 100 trade-uri: +$3.000.\n"
|
||||
"−0.10R = pierzi $10 pe trade. Pe 100 trade-uri: −$1.000.\n"
|
||||
"Cât câștigi în medie pe UN trade, exprimat în R.\n"
|
||||
"+0.30R = câștigi 0.30 × riscul mediu al trade-urilor.\n"
|
||||
"−0.10R = pierzi 0.10 × riscul mediu al trade-urilor.\n"
|
||||
"Pragul de GO LIVE: +0.20R sau mai mult."
|
||||
),
|
||||
"Expectancy ($)": (
|
||||
"Aceeași expectancy convertită în dolari, folosind Risk per Trade din Config.\n"
|
||||
"Aceeași expectancy convertită în dolari, folosind SL% × Account Size Start per trade.\n"
|
||||
"Util ca să vezi cât câștigi în medie pe trade în bani reali, nu doar în R."
|
||||
),
|
||||
"Cumulative P&L ($)": (
|
||||
|
||||
Reference in New Issue
Block a user