This commit is contained in:
Marius
2026-05-21 03:06:49 +03:00
parent 6057a94caa
commit 4fed6d2d59
3 changed files with 177 additions and 63 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -195,13 +195,13 @@ def build_config(wb: Workbook) -> None:
ws["B9"] = 50000 ws["B9"] = 50000
ws["C9"] = "Balanța contului de prop" ws["C9"] = "Balanța contului de prop"
ws["A10"] = "Position Usage (%)" ws["A10"] = "Contracte per trade"
ws["B10"] = 80 ws["B10"] = 1
ws["C10"] = "% din cont folosit ca notional (max contracte)" ws["C10"] = "Număr de contracte tranzacționate per semnal (TradeLocker)"
ws["A11"] = "Position Size ($)" ws["A11"] = "$ per 1% per contract"
ws["B11"] = "=B9*B10/100" ws["B11"] = 10000
ws["C11"] = "Auto — notional efectiv pe trade" ws["C11"] = "Pe DIA: 0.10% = $1000 ⇒ 1% = $10,000 (1 contract notional ≈ $1M)"
ws["A12"] = "Daily Loss Limit (%)" ws["A12"] = "Daily Loss Limit (%)"
ws["B12"] = 4.0 ws["B12"] = 4.0
@@ -219,15 +219,15 @@ def build_config(wb: Workbook) -> None:
ws["B15"] = "=B9*B14/100" ws["B15"] = "=B9*B14/100"
ws["C15"] = "Auto — derivat din B9 și B14" ws["C15"] = "Auto — derivat din B9 și B14"
for r in (9, 10, 12, 14): # inputuri galbene for r in (9, 10, 11, 12, 14): # inputuri galbene
ws.cell(row=r, column=2).fill = INPUT_FILL ws.cell(row=r, column=2).fill = INPUT_FILL
ws.cell(row=r, column=2).border = BORDER ws.cell(row=r, column=2).border = BORDER
for r in (11, 13, 15): # derived blue for r in (13, 15): # derived blue
ws.cell(row=r, column=2).fill = DERIVED_FILL ws.cell(row=r, column=2).fill = DERIVED_FILL
ws.cell(row=r, column=2).border = BORDER ws.cell(row=r, column=2).border = BORDER
ws["B9"].number_format = "$#,##0" ws["B9"].number_format = "$#,##0"
ws["B10"].number_format = '0"%"' ws["B10"].number_format = "0"
ws["B11"].number_format = "$#,##0" ws["B11"].number_format = "$#,##0"
ws["B12"].number_format = '0.0"%"' ws["B12"].number_format = '0.0"%"'
ws["B13"].number_format = "$#,##0" ws["B13"].number_format = "$#,##0"
@@ -351,20 +351,22 @@ R_FN: dict[str, callable] = {
def _f_dollar(r: int, r_col: str) -> str: def _f_dollar(r: int, r_col: str) -> str:
"""$ P&L pe contul abstract. Variabil per trade = R × SL%/100 × Account Size.""" """$ P&L per trade = R × SL% × Contracte × $/1% per contract (TradeLocker real)."""
rc = f"{COL[r_col]}{r}" rc = f"{COL[r_col]}{r}"
sl = f"{COL['SL %']}{r}" sl = f"{COL['SL %']}{r}"
return f'=IF({rc}="","",{rc}*{sl}/100*Config!$B$4)' return f'=IF({rc}="","",{rc}*{sl}*Config!$B$10*Config!$B$11)'
def _f_sl_dollar(r: int) -> str: def _f_sl_dollar(r: int) -> str:
"""SL $ = SL% × Contracte × $/1% per contract."""
sl = f"{COL['SL %']}{r}" sl = f"{COL['SL %']}{r}"
return f'=IF({sl}="","",{sl}/100*Config!$B$4)' return f'=IF({sl}="","",{sl}*Config!$B$10*Config!$B$11)'
def _f_sl_dollar_prop(r: int) -> str: def _f_sl_dollar_prop(r: int) -> str:
"""SL $ pe contul de prop — același cont real, formula identică cu SL $."""
sl = f"{COL['SL %']}{r}" sl = f"{COL['SL %']}{r}"
return f'=IF({sl}="","",{sl}/100*Config!$B$11)' return f'=IF({sl}="","",{sl}*Config!$B$10*Config!$B$11)'
def _f_balance(r: int, dollar_col: str) -> str: def _f_balance(r: int, dollar_col: str) -> str:
@@ -395,10 +397,14 @@ def _f_drawdown(r: int, peak_col: str, balance_col: str) -> str:
def _f_dollar_prop(r: int, r_col: str) -> str: def _f_dollar_prop(r: int, r_col: str) -> str:
"""$ P&L pe contul de prop. Variabil per trade = R × SL%/100 × Position Size.""" """$ P&L pe contul de prop — același calcul ca _f_dollar (cont real TradeLocker).
Diferența între cont abstract și prop e doar balanța de start; $-ul per trade
e identic pentru că reflectă realitatea contractelor tranzacționate.
"""
rc = f"{COL[r_col]}{r}" rc = f"{COL[r_col]}{r}"
sl = f"{COL['SL %']}{r}" sl = f"{COL['SL %']}{r}"
return f'=IF({rc}="","",{rc}*{sl}/100*Config!$B$11)' return f'=IF({rc}="","",{rc}*{sl}*Config!$B$10*Config!$B$11)'
def _f_balance_prop(r: int, dollar_col: str) -> str: def _f_balance_prop(r: int, dollar_col: str) -> str:
@@ -620,7 +626,7 @@ METRIC_HINTS: dict[str, str] = {
), ),
"Average Loss ($)": ( "Average Loss ($)": (
"Pierderea medie pe trade-urile negative (cifra apare cu minus).\n" "Pierderea medie pe trade-urile negative (cifra apare cu minus).\n"
"În dolari reali, 1R depinde de SL%: pierdere ≈ SL% × Account Size Start.\n" "În dolari reali, 1R depinde de SL%: pierdere ≈ SL% × Contracte × $/1% per contract.\n"
"Dacă e mult mai mare decât riscul calculat din SL, ai SL-uri sărite (slippage, gap-uri)." "Dacă e mult mai mare decât riscul calculat din SL, ai SL-uri sărite (slippage, gap-uri)."
), ),
"Best Trade ($)": ( "Best Trade ($)": (
@@ -630,8 +636,8 @@ METRIC_HINTS: dict[str, str] = {
), ),
"Worst Trade ($)": ( "Worst Trade ($)": (
"Cea mai mare pierdere individuală.\n" "Cea mai mare pierdere individuală.\n"
"Ar trebui să fie aproximativ egală cu 1R calculat din SL% × Account Size Start.\n" "Ar trebui să fie aproximativ egală cu 1R calculat din SL% × Contracte × $/1% per contract.\n"
"Dacă e semnificativ mai mare, ai depășit risk-ul plănuit — SL ratat, slippage, gap overnight." "Pe TradeLocker DIA: SL=0.30%, 1 contract → ≈ $3000. Dacă e mai mare, ai slippage/gap."
), ),
"Profit Factor": ( "Profit Factor": (
"Total bani câștigați împărțit la total bani pierduți (în valoare absolută).\n" "Total bani câștigați împărțit la total bani pierduți (în valoare absolută).\n"
@@ -650,8 +656,8 @@ METRIC_HINTS: dict[str, str] = {
"Pragul de GO LIVE: +0.20R sau mai mult." "Pragul de GO LIVE: +0.20R sau mai mult."
), ),
"Expectancy ($)": ( "Expectancy ($)": (
"Aceeași expectancy convertită în dolari, folosind SL% × Account Size Start per trade.\n" "Aceeași expectancy convertită în dolari, folosind SL% × Contracte × $/1% per contract.\n"
"Util ca să vezi cât câștigi în medie pe trade în bani reali, nu doar în R." "Util ca să vezi cât câștigi în medie pe trade în bani reali (TradeLocker), nu doar în R."
), ),
"Cumulative P&L ($)": ( "Cumulative P&L ($)": (
"Suma profitului și pierderii pe toate trade-urile logate.\n" "Suma profitului și pierderii pe toate trade-urile logate.\n"
@@ -672,13 +678,14 @@ METRIC_HINTS: dict[str, str] = {
"Editabil în Config B9." "Editabil în Config B9."
), ),
"Position Size ($)": ( "Position Size ($)": (
"Notional efectiv pe trade = Account Prop × Position Usage %.\n" "Configurare contract real TradeLocker:\n"
"Default: 80% × $50,000 = $40,000. Pe DIA, ≈ 0.8 contracte din prețul curent.\n" " • Contracte per trade (Config B10) — câte contracte tranzacționezi pe semnal.\n"
"Pierderea pe SL = SL% × Position Size (NU procent fix din cont)." " • $ per 1% per contract (Config B11) — pe DIA: 0.10% = $1000 → 1% = $10,000.\n"
"Pierderea pe SL = SL% × Contracte × $/1% per contract. Pentru SL=0.30%, 1 contract → $3000."
), ),
"Cumulative P&L Prop ($)": ( "Cumulative P&L Prop ($)": (
"Profitul total al contului de prop pe traseul logat.\n" "Profitul total al contului de prop pe traseul logat.\n"
"Reflectă $ real (SL% × Position Size per trade), NU R-multiple-ul abstract de mai sus.\n" "Reflectă $ real (SL% × Contracte × $/1% per contract), nu un procent abstract din cont.\n"
"Adunat peste $50,000 dă balanța finală reală." "Adunat peste $50,000 dă balanța finală reală."
), ),
"Final Balance Prop ($)": ( "Final Balance Prop ($)": (
@@ -949,11 +956,13 @@ def build_dashboard(wb: Workbook) -> None:
ws[f"P{row}"] = f'=IF(N{row}>Config!$B$15,"DA","NU")' ws[f"P{row}"] = f'=IF(N{row}>Config!$B$15,"DA","NU")'
ws[f"Q{row}"] = f'=IF(OR(O{row}="DA",P{row}="DA"),"CONT PIERDUT","CONFORM")' ws[f"Q{row}"] = f'=IF(OR(O{row}="DA",P{row}="DA"),"CONT PIERDUT","CONFORM")'
ws[f"R{row}"] = ( ws[f"R{row}"] = (
f'=IF(AND(E{row}>=40,G{row}>=55%,H{row}>=0.2,' f'=IF(E{row}<1,"",'
f'O{row}="NU",P{row}="NU"),"CANDIDAT","NU")' f'IF(OR(O{row}="DA",P{row}="DA"),"BREACH",'
f'IF(AND(E{row}>=40,G{row}>=55%,H{row}>=0.2),"CANDIDAT","PRE-CANDIDAT")))'
) )
ws[f"S{row}"] = ( ws[f"S{row}"] = (
f'=IF(R{row}="CANDIDAT",H{row}*100000+J{row}*1000+K{row}-L{row}-N{row}/10,-1)' f'=IF(E{row}<1,-1E+12,'
f'H{row}*100000+J{row}*1000+K{row}-L{row}-N{row}/10)'
) )
combo_rows.append(row) combo_rows.append(row)
combo_idx += 1 combo_idx += 1
@@ -991,14 +1000,26 @@ def build_dashboard(wb: Workbook) -> None:
status_rng, CellIsRule(operator="equal", formula=['"CONT PIERDUT"'], fill=fail_fill) status_rng, CellIsRule(operator="equal", formula=['"CONT PIERDUT"'], fill=fail_fill)
) )
ws.conditional_formatting.add( ws.conditional_formatting.add(
status_rng, CellIsRule(operator="equal", formula=['"NU"'], fill=warn_fill) status_rng, CellIsRule(operator="equal", formula=['"BREACH"'], fill=fail_fill)
)
ws.conditional_formatting.add(
status_rng, CellIsRule(operator="equal", formula=['"PRE-CANDIDAT"'], fill=warn_fill)
) )
top_title_row = row + 2 top_title_row = row + 2
ws[f"A{top_title_row}"] = "TOP CANDIDATE" ws[f"A{top_title_row}"] = "TOP CANDIDATE"
ws[f"A{top_title_row}"].font = SUBTITLE_FONT ws[f"A{top_title_row}"].font = SUBTITLE_FONT
ws.merge_cells(f"A{top_title_row}:J{top_title_row}") ws.merge_cells(f"A{top_title_row}:J{top_title_row}")
top_header_row = top_title_row + 1 top_note_row = top_title_row + 1
ws[f"A{top_note_row}"] = (
"Top 10 ferestre după scor compus. CANDIDAT = îndeplinește toate pragurile "
"(N≥40, WR≥55%, ExpR≥0.2, no breach). PRE-CANDIDAT = N≥1 fără breach dar sub praguri. "
"BREACH = ar fi pierdut contul prop."
)
ws[f"A{top_note_row}"].font = Font(name="Calibri", size=10, italic=True, color="595959")
ws[f"A{top_note_row}"].alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
ws.merge_cells(f"A{top_note_row}:J{top_note_row}")
top_header_row = top_note_row + 1
top_headers = [ top_headers = [
"#", "Fereastra", "Strategie", "N", "WR", "Expectancy R", "#", "Fereastra", "Strategie", "N", "WR", "Expectancy R",
"Profit Factor", "Cum P&L $", "Max DD Prop $", "Status Edge", "Profit Factor", "Cum P&L $", "Max DD Prop $", "Status Edge",
@@ -1021,7 +1042,7 @@ def build_dashboard(wb: Workbook) -> None:
["A", "D", "E", "G", "H", "J", "K", "N", "R"], ["A", "D", "E", "G", "H", "J", "K", "N", "R"],
): ):
ws[f"{target}{r}"] = ( ws[f"{target}{r}"] = (
f'=IFERROR(IF({rank_formula}<0,"",' f'=IFERROR(IF({rank_formula}<=-1E+11,"",'
f'INDEX(${source}${first_combo}:${source}${last_combo},{match_formula})),"")' f'INDEX(${source}${first_combo}:${source}${last_combo},{match_formula})),"")'
) )
for c in range(1, len(top_headers) + 1): for c in range(1, len(top_headers) + 1):
@@ -1034,56 +1055,86 @@ def build_dashboard(wb: Workbook) -> None:
ws[f"H{r}"].number_format = '"$"#,##0.00' ws[f"H{r}"].number_format = '"$"#,##0.00'
ws[f"I{r}"].number_format = '"$"#,##0.00' ws[f"I{r}"].number_format = '"$"#,##0.00'
# Helper pentru a emite un block breakdown (per Sesiune / Strategie / etc.) # CF pe coloana Status Edge din TOP CANDIDATE
def _emit_breakdown( top_status_rng = f"J{top_header_row + 1}:J{top_header_row + 10}"
ws.conditional_formatting.add(
top_status_rng, CellIsRule(operator="equal", formula=['"CANDIDAT"'], fill=pass_fill)
)
ws.conditional_formatting.add(
top_status_rng, CellIsRule(operator="equal", formula=['"PRE-CANDIDAT"'], fill=warn_fill)
)
ws.conditional_formatting.add(
top_status_rng, CellIsRule(operator="equal", formula=['"BREACH"'], fill=fail_fill)
)
# Conditional formatting reutilizabil pentru celulele Cum $
bd_green = PatternFill("solid", fgColor="C6EFCE")
bd_red = PatternFill("solid", fgColor="FFC7CE")
# Helper pentru breakdown wide: rânduri = items, coloane = 5 strategii Cum $ + N total
def _emit_breakdown_strats(
start_row: int, title: str, first_col_label: str, start_row: int, title: str, first_col_label: str,
items: list[str], item_range: str, overlay_strat: str, items: list[str], item_range: str,
) -> int: ) -> int:
# Layout: A=item, B..F=5 strategii (Cum $), G=N total
last_col_idx = 1 + len(STRAT_KEYS) + 1 # A + 5 strategii + N
last_letter = get_column_letter(last_col_idx)
ws[f"A{start_row}"] = title ws[f"A{start_row}"] = title
ws[f"A{start_row}"].font = SUBTITLE_FONT ws[f"A{start_row}"].font = SUBTITLE_FONT
ws.merge_cells(f"A{start_row}:F{start_row}") ws.merge_cells(f"A{start_row}:{last_letter}{start_row}")
headers = [first_col_label, "N", "Wins", "WR", "Expectancy R", "Cum $"] headers = [first_col_label] + [STRAT_LABELS[s] for s in STRAT_KEYS] + ["N total"]
for col_idx, h in enumerate(headers, start=1): for col_idx, h in enumerate(headers, start=1):
c = ws.cell(row=start_row + 1, column=col_idx, value=h) c = ws.cell(row=start_row + 1, column=col_idx, value=h)
c.font = HEADER_FONT c.font = HEADER_FONT
c.fill = HEADER_FILL c.fill = HEADER_FILL
c.alignment = CENTER c.alignment = CENTER
c.border = BORDER c.border = BORDER
strat_letters = [get_column_letter(2 + i) for i in range(len(STRAT_KEYS))]
n_letter = get_column_letter(last_col_idx)
for i, item in enumerate(items): for i, item in enumerate(items):
r = start_row + 2 + i r = start_row + 2 + i
ws[f"A{r}"] = item ws[f"A{r}"] = item
ws[f"B{r}"] = f'=COUNTIF({item_range},"{item}")' for idx, strat in enumerate(STRAT_KEYS):
ws[f"C{r}"] = f'=COUNTIFS({item_range},"{item}",{W[overlay_strat]},1)' cl = strat_letters[idx]
ws[f"D{r}"] = f"=IFERROR(C{r}/B{r},0)" ws[f"{cl}{r}"] = f'=SUMIFS({D[strat]},{item_range},"{item}")'
ws[f"E{r}"] = ( ws[f"{cl}{r}"].number_format = '"$"#,##0.00'
f'=IFERROR(AVERAGEIFS({R[overlay_strat]},{item_range},"{item}"),0)' ws[f"{n_letter}{r}"] = f'=COUNTIF({item_range},"{item}")'
ws[f"{n_letter}{r}"].number_format = "0"
for col_idx in range(1, last_col_idx + 1):
cell = ws.cell(row=r, column=col_idx)
cell.border = BORDER
cell.alignment = LEFT if col_idx == 1 else RIGHT
if 2 <= col_idx <= 1 + len(STRAT_KEYS):
cell.fill = DERIVED_FILL
# CF pe coloanele 5 strategii: verde >0, roșu <0
if items:
first_data_row = start_row + 2
last_data_row = start_row + 1 + len(items)
cf_rng = (
f"{strat_letters[0]}{first_data_row}:"
f"{strat_letters[-1]}{last_data_row}"
) )
ws[f"F{r}"] = f'=SUMIFS({D[overlay_strat]},{item_range},"{item}")' ws.conditional_formatting.add(
ws[f"B{r}"].number_format = "0" cf_rng, CellIsRule(operator="greaterThan", formula=["0"], fill=bd_green)
ws[f"C{r}"].number_format = "0" )
ws[f"D{r}"].number_format = "0.0%" ws.conditional_formatting.add(
ws[f"E{r}"].number_format = "+0.000;-0.000;0.000" cf_rng, CellIsRule(operator="lessThan", formula=["0"], fill=bd_red)
ws[f"F{r}"].number_format = '"$"#,##0.00' )
for c in ("A", "B", "C", "D", "E", "F"): return start_row + 1 + len(items)
ws[f"{c}{r}"].border = BORDER
ws[f"{c}{r}"].alignment = RIGHT if c != "A" else LEFT
return start_row + 2 + len(items)
# Breakdowns — toate folosesc overlay-ul Hybrid+BE (recomandat de trader) # Breakdowns — toate cele 5 strategii vizibile, Cum P&L $ per strategie
overlay = "hybrid_be"
start = top_header_row + 13 start = top_header_row + 13
after_sess = start after_strat = _emit_breakdown_strats(
after_strat = _emit_breakdown( start + 2, "PER STRATEGIE — Cum P&L $ per strategie", "Strategie",
after_sess + 2, "PER STRATEGIE (overlay: Hybrid + BE)", "Strategie", STRATEGIES, _range("Strategie"),
STRATEGIES, _range("Strategie"), overlay,
) )
after_ind = _emit_breakdown( after_ind = _emit_breakdown_strats(
after_strat + 2, "PER INDICATOR (overlay: Hybrid + BE)", "Indicator", after_strat + 2, "PER INDICATOR — Cum P&L $ per strategie", "Indicator",
INDICATORS, _range("Indicator"), overlay, INDICATORS, _range("Indicator"),
) )
after_dir = _emit_breakdown( after_dir = _emit_breakdown_strats(
after_ind + 2, "PER DIRECȚIE (overlay: Hybrid + BE)", "Direcție", after_ind + 2, "PER DIRECȚIE — Cum P&L $ per strategie", "Direcție",
DIRECTIONS, _range("Direcție"), overlay, DIRECTIONS, _range("Direcție"),
) )
# ---- PROP FIRM COMPLIANCE ---- # ---- PROP FIRM COMPLIANCE ----
@@ -1235,9 +1286,72 @@ def build_dashboard(wb: Workbook) -> None:
for r in range(prop_header_row + 1, prop_header_row + 1 + len(prop_metrics)): for r in range(prop_header_row + 1, prop_header_row + 1 + len(prop_metrics)):
ws.row_dimensions[r].height = 60 ws.row_dimensions[r].height = 60
# ---- PROP FIRM COMPLIANCE per FEREASTRĂ × STRATEGIE ----
# Reshape compliance: rânduri = combo (fereastră × strategie), coloane = metrici compliance.
# Datele sunt referențiate direct din FERESTRE CANDIDATE (cols A, D, M, N, O, P, Q).
if combo_rows:
win_prop_title_row = prop_header_row + 1 + len(prop_metrics) + 2
ws[f"A{win_prop_title_row}"] = "PROP FIRM COMPLIANCE — per FEREASTRĂ × STRATEGIE"
ws[f"A{win_prop_title_row}"].font = SUBTITLE_FONT
ws.merge_cells(f"A{win_prop_title_row}:G{win_prop_title_row}")
win_prop_note_row = win_prop_title_row + 1
ws[f"A{win_prop_note_row}"] = (
"Defalcat pe fiecare combinație de fereastră tradabilă × strategie management. "
"CONFORM = ar fi supraviețuit pe contul de prop pe acel slot."
)
ws[f"A{win_prop_note_row}"].font = Font(name="Calibri", size=10, italic=True, color="595959")
ws[f"A{win_prop_note_row}"].alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
ws.merge_cells(f"A{win_prop_note_row}:G{win_prop_note_row}")
win_prop_header_row = win_prop_note_row + 1
win_prop_headers = [
"Fereastra", "Strategie", "Worst Daily Prop $", "Max DD Prop $",
"Daily Breach", "Max Breach", "Overall Prop",
]
for col_idx, h in enumerate(win_prop_headers, start=1):
c = ws.cell(row=win_prop_header_row, column=col_idx, value=h)
c.font = HEADER_FONT
c.fill = HEADER_FILL
c.alignment = CENTER
c.border = BORDER
# source cols din FERESTRE CANDIDATE: A=Fereastra, D=Strategie, M=Worst Daily,
# N=Max DD, O=Daily Breach, P=Max Breach, Q=Overall Prop
source_cols = ["A", "D", "M", "N", "O", "P", "Q"]
for offset, combo_row in enumerate(combo_rows, start=1):
r = win_prop_header_row + offset
for col_idx, source in enumerate(source_cols, start=1):
target = get_column_letter(col_idx)
ws[f"{target}{r}"] = f"={source}{combo_row}"
cell = ws[f"{target}{r}"]
cell.border = BORDER
cell.fill = DERIVED_FILL
cell.alignment = CENTER if col_idx in (1, 2, 5, 6, 7) else RIGHT
ws[f"C{r}"].number_format = '"$"#,##0.00'
ws[f"D{r}"].number_format = '"$"#,##0.00'
# CF pe Overall Prop (col G) și pe Daily/Max Breach (cols E, F)
win_prop_first = win_prop_header_row + 1
win_prop_last = win_prop_header_row + len(combo_rows)
overall_rng_win = f"G{win_prop_first}:G{win_prop_last}"
ws.conditional_formatting.add(
overall_rng_win, CellIsRule(operator="equal", formula=['"CONFORM"'], fill=pass_fill)
)
ws.conditional_formatting.add(
overall_rng_win, CellIsRule(operator="equal", formula=['"CONT PIERDUT"'], fill=fail_fill)
)
breach_rng_win = f"E{win_prop_first}:F{win_prop_last}"
ws.conditional_formatting.add(
breach_rng_win, CellIsRule(operator="equal", formula=['"DA"'], fill=fail_fill)
)
ws.conditional_formatting.add(
breach_rng_win, CellIsRule(operator="equal", formula=['"NU"'], fill=pass_fill)
)
# Column widths # Column widths
widths = { widths = {
"A": 18, "B": 10, "C": 10, "D": 16, "E": 8, "F": 8, "G": 10, "A": 18, "B": 10, "C": 18, "D": 16, "E": 13, "F": 13, "G": 16,
"H": 13, "I": 13, "J": 12, "K": 13, "L": 15, "M": 20, "H": 13, "I": 13, "J": 12, "K": 13, "L": 15, "M": 20,
"N": 18, "O": 13, "P": 14, "Q": 15, "R": 13, "S": 8, "N": 18, "O": 13, "P": 14, "Q": 15, "R": 13, "S": 8,
} }