feat(sim): 추세선 4종·완화/즉시매수 토글·전략 4요소 이름·거래목록 달력 등 대개편
전략 차원/변이: - 추세선 10/20/40→10/20/60 (40 제거, 60 추가; 60은 중기게이트와 겹침) - 기울기 게이트 완화(완화=기울기만 면제, 중기정배열 유지) - 즉시매수 토글 — 백테스트로 불리 확인 → 라이브 확인용 3개만 유지 - 최종 219개 (비즉시 216 + 즉시 3) UI: - 전략 이름 = 4카테고리 두글자(추세·진입·매도·베팅) + 카테고리별 ⓘ 설명 - 거래목록 달력형(오늘 자동선택·거래없어도 클릭), 매수/매도 분리·익절/손절 표시 - 라벨 동적화(실제 SMA_LONG), 제외카드 4조건 표시, 약세모드/완화/즉시 칩 - 매수준비/대기 배지 분리, 탭 깜빡임 제거(서버 checked), 손절/목표 평단% 등 문서: CLAUDE.md sim 섹션 갱신 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@ BIND_HOST = ''
|
||||
BIND_PORT = 18792
|
||||
|
||||
STATE_BADGE = {
|
||||
'SELL': ('매도', '#ef4444'), 'ADD': ('추격매수', '#16a34a'), 'BUY': ('매수', '#22c55e'),
|
||||
'SELL': ('매도', '#ef4444'), 'ADD': ('추격매수', '#0f7a34'), 'BUY': ('매수', '#22c55e'),
|
||||
'HOLD': ('보유', '#3b82f6'), 'WAIT': ('대기', '#eab308'), 'SKIP': ('제외', '#6b7280'),
|
||||
'NODATA': ('데이터없음', '#374151'),
|
||||
}
|
||||
@@ -745,6 +745,56 @@ def _market_fit_words(p: dict) -> str:
|
||||
return '·'.join([m] + adj + [style])
|
||||
|
||||
|
||||
def _cat_labels(p: dict) -> tuple[str, str, str, str]:
|
||||
"""전략을 4카테고리 두글자 이름으로 — (추세, 진입, 매도, 베팅)."""
|
||||
p = p or {}
|
||||
sma = p.get('SMA_LONG', 20)
|
||||
trend = {10: '민감', 20: '표준', 40: '둔감', 60: '장기'}.get(sma, '표준')
|
||||
entry = '적극' if p.get('VOLUME_BREAKOUT_MULT', 1.5) <= 1.5 else '신중'
|
||||
rr = p.get('RR_RATIO', 2.0)
|
||||
sell = '한방' if rr >= 2.5 else '단타' if rr <= 1.5 else '균형'
|
||||
bet = '집중' if p.get('RISK_PER_TRADE_PCT', 0.01) >= 0.02 else '분산'
|
||||
return trend, entry, sell, bet
|
||||
|
||||
|
||||
def _cat_name(p: dict) -> str:
|
||||
"""4카테고리 이름 한 줄 — 예 '표준·적극·균형·분산'."""
|
||||
return '·'.join(_cat_labels(p))
|
||||
|
||||
|
||||
def _cat_detail_html(p: dict) -> str:
|
||||
"""펼침용 — 4카테고리 각각의 선택값·쉬운 설명."""
|
||||
p = p or {}
|
||||
trend, entry, sell, bet = _cat_labels(p)
|
||||
sma = p.get('SMA_LONG', 20); vol = p.get('VOLUME_BREAKOUT_MULT', 1.5)
|
||||
rr = p.get('RR_RATIO', 2.0); risk = p.get('RISK_PER_TRADE_PCT', 0.01)
|
||||
stop = p.get('STOP_ATR_MULT', 2.0); hold = p.get('MAX_HOLD_DAYS', 0)
|
||||
rows = [
|
||||
('🟦 추세', trend, f'{sma:g}일선 기준', '추세 — 며칠짜리 평균선으로 보나',
|
||||
f'추세를 며칠짜리 평균선으로 보는지예요. 이 전략은 {sma:g}일선 기준({trend}).\n\n'
|
||||
'· 민감(10일선): 빨리 반응 — 기회 빨리 잡지만 속임수(휩쏘)도 많음\n'
|
||||
'· 표준(20일선): 무난한 중간\n· 둔감(40일선): 천천히 — 늦지만 안정적\n· 장기(60일선): 큰 흐름만'),
|
||||
('🟩 진입', entry, f'돌파 거래량 {vol:g}배', '진입 — 얼마나 쉽게 사나',
|
||||
f'돌파로 살 때 거래량이 평소의 몇 배는 터져야 사는지예요. 이 전략은 {vol:g}배({entry}).\n\n'
|
||||
'· 적극(1.3배): 문턱 낮음 — 자주 삼\n· 신중(2.0배): 확실히 터질 때만 삼'),
|
||||
('🟥 매도', sell, f'손익비 {rr:g} · 손절 ATR{stop:g}배' + (f' · 시간손절 {hold}일' if hold else ''),
|
||||
'매도 — 얼마나 길게 먹나',
|
||||
f'목표 수익을 손절폭의 몇 배로 잡는지예요. 이 전략은 손익비 {rr:g}({sell}).\n\n'
|
||||
'· 한방(3.0): 길게 들고 크게 먹기\n· 균형(2.0): 중간\n· 단타(1.5): 짧게 자주 먹기\n\n'
|
||||
f'손절은 변동성(ATR)의 {stop:g}배 빠지면 실행' + (f', {hold}일 지나도 안 오르면 정리해요.' if hold else '해요.')),
|
||||
('🟨 베팅', bet, f'종목당 리스크 {risk * 100:g}%', '베팅 — 한 종목에 얼마나',
|
||||
f'한 종목에 자산의 몇 %까지 거는지예요. 이 전략은 {risk * 100:g}%({bet}).\n\n'
|
||||
'· 집중(2%): 크게 베팅 — 수익·손실 둘 다 큼\n· 분산(1%): 작게 — 안정적'),
|
||||
]
|
||||
out = ''
|
||||
for cat, name, val, title, desc in rows:
|
||||
out += (f'<div class="kv"><span class="k">{cat}</span>'
|
||||
f'<span><b>{_esc(name)}</b> <span class="muted">— {_esc(val)}</span>'
|
||||
f'<button type="button" class="chk-i" data-title="{_esc(title)}" '
|
||||
f'data-detail="{_esc(desc)}">ⓘ</button></span></div>')
|
||||
return out
|
||||
|
||||
|
||||
# 약세장 모드 단어 칩 — 비교군 행·드롭다운에서 한눈에 구분 (긴 접미사 대신).
|
||||
_BEAR_WORD = {0: '회피', 1: '안정', 2: '적극'}
|
||||
_BEAR_NAME = {0: '회피 (현금화)', 1: '안정 (돌파만·기본)', 2: '적극 (눌림허용)'}
|
||||
@@ -771,6 +821,51 @@ def _bear_chip_html(p) -> str:
|
||||
f'data-detail="{_esc(_BEAR_DESC[m] + _BEAR_LEGEND)}">{_BEAR_WORD[m]}</span>')
|
||||
|
||||
|
||||
def _gate_info(p):
|
||||
"""진입 게이트 차원 — (이름, 설명). 유형·약세장대처와 독립된 3번째 축."""
|
||||
p = p or {}
|
||||
off = []
|
||||
if not int(p.get('TREND_SLOPE_GATE', 1)):
|
||||
off.append('장기선 우상향 요구')
|
||||
if not int(p.get('MID_ARRAY_GATE', 1)):
|
||||
off.append('중기 정배열(5>60) 요구')
|
||||
if not off:
|
||||
return ('보수 (현행)',
|
||||
'추세 조건을 모두 요구해요 — 현재가>장기선·정배열·장기선 우상향·중기 정배열(5>60). '
|
||||
'안전하지만 매수 기회가 적어요(보수적).')
|
||||
return ('완화 — ' + '·'.join('장기선 기울기' if '우상향' in o else '중기 정배열' for o in off) + ' 면제',
|
||||
'추세 게이트를 완화한 변이예요. 꺼진 조건:\n· ' + '\n· '.join(off)
|
||||
+ '\n\n기본 조건(현재가>장기선·정배열)은 그대로라, 단기 반등 초입 종목도 잡아 더 적극적으로 매수해요. '
|
||||
'보수형(현행)과 라이브로 비교 중입니다.')
|
||||
|
||||
|
||||
def _relax_chip_html(p) -> str:
|
||||
"""추세 게이트 완화 칩 — 장기선 기울기/중기정배열 게이트를 끈 변이 표시. 현행(둘다 ON)이면 빈 문자열."""
|
||||
p = p or {}
|
||||
off = []
|
||||
if not int(p.get('TREND_SLOPE_GATE', 1)):
|
||||
off.append('장기선 우상향 요구')
|
||||
if not int(p.get('MID_ARRAY_GATE', 1)):
|
||||
off.append('중기 정배열(5>60) 요구')
|
||||
if not off:
|
||||
return ''
|
||||
desc = ('추세 게이트 완화 변이 — 다음 조건을 꺼서 더 적극적으로 매수해요:\n· '
|
||||
+ '\n· '.join(off) + '\n\n보수형(현행)과 라이브로 비교 중인 실험 전략입니다.')
|
||||
return (f'<span class="bic bic-relax" data-title="추세 게이트 완화" '
|
||||
f'data-detail="{_esc(desc)}">완화</span>')
|
||||
|
||||
|
||||
def _immediate_chip_html(p) -> str:
|
||||
"""즉시매수 칩 — 돌파·눌림목 안 기다리고 조건 통과 시 현재가 즉시 매수하는 변이. 아니면 빈 문자열."""
|
||||
if not int((p or {}).get('IMMEDIATE_ENTRY', 0)):
|
||||
return ''
|
||||
desc = ('즉시매수 변이 — 추세·정배열·수급·애널을 다 통과한 종목이면, 돌파나 눌림목을 안 기다리고 '
|
||||
'강세장에서 현재가에 바로 사요. 꾸준히 오르기만 하는 좋은 종목을 놓치지 않으려는 실험 전략이에요. '
|
||||
'(기다렸다 사는 기존 전략과 라이브 비교 중)')
|
||||
return (f'<span class="bic bic-now" data-title="즉시매수" '
|
||||
f'data-detail="{_esc(desc)}">즉시</span>')
|
||||
|
||||
|
||||
def _backtest_panel() -> str:
|
||||
"""백테스트 결과 — 유형별(세부 라벨) 대표(1위) 조합 요약. 비교군은 이 대표들로 자동 구성되므로
|
||||
수동 추가/제거 없이 읽기 전용 요약만 보여준다."""
|
||||
@@ -1028,12 +1123,14 @@ def _compare_panel() -> str:
|
||||
rows += (
|
||||
f'<details class="row" data-reg="{i}" data-rank="{rank_of[v["id"]]}" data-bt="{bt_attr}" '
|
||||
f'data-held="{s.get("open_positions", 0)}" data-trades="{s.get("closed_trades", 0)}"><summary>'
|
||||
f'<span class="vid">{rank_of[v["id"]]}위</span>{_bear_chip_html(mp)} <span class="muted">{_vid_disp(vid)}</span> '
|
||||
f'<span class="nm">{_esc(mf_w)}</span>{tag}'
|
||||
f'<span class="vid">{rank_of[v["id"]]}위</span>{_bear_chip_html(mp)}{_relax_chip_html(mp)}{_immediate_chip_html(mp)} <span class="muted">{_vid_disp(vid)}</span> '
|
||||
f'<span class="nm">{_esc(_cat_name(mp))}</span>{tag}'
|
||||
f'<span class="rt"><span class="muted">🛒{s.get("open_positions", 0)}</span> '
|
||||
f'<b style="color:{rc}">{ret:+g}%</b></span></summary>'
|
||||
f'<div class="body">'
|
||||
f'<div class="kv"><span class="k">유형</span><span>{_esc(mf_sum)}'
|
||||
f'<div class="kv"><span class="k">전략 4요소</span><span class="muted">추세·진입·매도·베팅</span></div>'
|
||||
f'{_cat_detail_html(mp)}'
|
||||
f'<div class="kv"><span class="k">시장적합</span><span>{_esc(mf_sum)}'
|
||||
f'<button type="button" class="chk-i" data-title="{_esc(mf_t)}" '
|
||||
f'data-detail="{_esc(mf_b)}">ⓘ</button></span></div>'
|
||||
f'<div class="kv"><span class="k">약세장대처</span><span><b>{_BEAR_NAME[bm]}</b>'
|
||||
@@ -1197,6 +1294,7 @@ h1{font-size:17px;margin:10px 0 2px}h2{font-size:14px;margin:22px 0 8px;color:#c
|
||||
.vid{background:#1e293b;color:#7dd3fc;border-radius:5px;padding:1px 6px;font-size:11px;font-weight:700}
|
||||
.bic{border-radius:5px;padding:1px 5px;font-size:10px;font-weight:700;margin-left:4px}
|
||||
.bic0{background:#334155;color:#cbd5e1}.bic1{background:#1e3a5f;color:#7dd3fc}.bic2{background:#4a2e12;color:#fbbf24}
|
||||
.bic-relax{background:#3b1d4a;color:#d8b4fe}.bic-now{background:#0c3a2e;color:#6ee7b7}
|
||||
.cmp-btlink{background:#0f2a3f;color:#7dd3fc;border:1px solid #1e3a5f;border-radius:6px;padding:3px 8px;font-size:11px;font-weight:600;display:inline-block}
|
||||
.note{white-space:pre-wrap;line-height:1.65;font-size:13px;color:#cbd5e1}
|
||||
ul.an{margin:6px 0;padding-left:18px;line-height:1.7;font-size:13px}
|
||||
@@ -1326,12 +1424,15 @@ td:first-child,th:first-child{text-align:left}
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>{_esc(vid)} 전략 상세</title>{style}</head><body><div id="page">
|
||||
{back}
|
||||
<h1><span class="vid">{rank or "—"}위</span> <span class="muted">{_vid_disp(vid)}</span> {_esc(_market_fit_words(params))}
|
||||
<span class="bic bic{bm}">{_BEAR_WORD[bm]}</span> {icon}</h1>
|
||||
<h1><span class="vid">{rank or "—"}위</span> <span class="muted">{_vid_disp(vid)}</span> {_esc(_cat_name(params))}
|
||||
<span class="bic bic{bm}">{_BEAR_WORD[bm]}</span>{_relax_chip_html(params)}{_immediate_chip_html(params)} {icon}</h1>
|
||||
<div class="sub">{_esc(_market_fit_summary(params))} · 약세장대처: {_BEAR_NAME[bm]}</div>
|
||||
|
||||
<div class="card">{notes_html}</div>
|
||||
|
||||
<h2>🧩 전략 4요소 (이름 풀이)</h2>
|
||||
<div class="card">{_cat_detail_html(params)}</div>
|
||||
|
||||
<h2>📈 수익률 추이</h2>
|
||||
<div class="card">{_ret_chart_svg(series, idx_series)}</div>
|
||||
|
||||
@@ -1380,7 +1481,9 @@ _DAY_CAL_JS = """<script>(function(){
|
||||
function render(){
|
||||
var p=cur.split('-'), y=+p[0], m=+p[1];
|
||||
var startDow=new Date(y,m-1,1).getDay(), ndays=new Date(y,m,0).getDate();
|
||||
var minYM=(TD[TD.length-1]||'').slice(0,7), maxYM=(TD[0]||'').slice(0,7);
|
||||
var allYM=TD.concat([TODAY]); // 월 이동 범위에 오늘 포함 (거래 없어도 오늘 월까지 이동 가능)
|
||||
var minYM=allYM.reduce(function(a,b){return a<b?a:b;}).slice(0,7);
|
||||
var maxYM=allYM.reduce(function(a,b){return a>b?a:b;}).slice(0,7);
|
||||
var h='<div class="cal-hd">'
|
||||
+'<button type="button" data-nav="-1"'+(cur<=minYM?' disabled':'')+'>‹</button>'
|
||||
+'<span>'+y+'.'+pad(m)+'</span>'
|
||||
@@ -1390,9 +1493,12 @@ _DAY_CAL_JS = """<script>(function(){
|
||||
for(var i=0;i<startDow;i++)h+='<div></div>';
|
||||
for(var d=1;d<=ndays;d++){
|
||||
var ds=y+'-'+pad(m)+'-'+pad(d);
|
||||
var cls='cal-d'+(ds===TODAY?' tdy':'');
|
||||
if(tset[ds]){h+='<a class="'+cls+' on'+(ds===SEL?' sel':'')+'" data-d="'+ds+'" href="/?t=lab&s=day&d='+ds+'">'+d+'</a>';}
|
||||
else{h+='<span class="'+cls+' off">'+d+'</span>';}
|
||||
var isToday=(ds===TODAY), hasTr=!!tset[ds];
|
||||
var cls='cal-d'+(isToday?' tdy':'');
|
||||
if(hasTr||isToday){ // 거래일=밝게(on) / 오늘은 거래없어도 클릭가능(today-open)
|
||||
cls+=hasTr?' on':' today-open';
|
||||
h+='<a class="'+cls+(ds===SEL?' sel':'')+'" data-d="'+ds+'" href="/?t=lab&s=day&d='+ds+'">'+d+'</a>';
|
||||
} else { h+='<span class="'+cls+' off">'+d+'</span>'; }
|
||||
}
|
||||
h+='</div>';
|
||||
box.innerHTML=h;
|
||||
@@ -1404,7 +1510,7 @@ _DAY_CAL_JS = """<script>(function(){
|
||||
cur=ny+'-'+pad(nm); render();
|
||||
});
|
||||
});
|
||||
box.querySelectorAll('.cal-d.on').forEach(function(a){
|
||||
box.querySelectorAll('.cal-d[data-d]').forEach(function(a){
|
||||
a.addEventListener('click',function(e){ e.preventDefault(); pick(a.getAttribute('data-d')); });
|
||||
});
|
||||
}
|
||||
@@ -1468,19 +1574,18 @@ def _day_trades_panel(sel_date: str | None = None) -> str:
|
||||
if d:
|
||||
all_rows.append((d, v['id'], r))
|
||||
|
||||
dates = sorted({d for d, _, _ in all_rows}, reverse=True) # 최신순
|
||||
dates = sorted({d for d, _, _ in all_rows}, reverse=True) # 거래일만, 최신순
|
||||
today = datetime.now(config.KST).strftime('%Y-%m-%d')
|
||||
selectable = set(dates) | {today} # 오늘은 거래 없어도 선택 가능
|
||||
back = '<label for="sub-cmp" class="cmp-btlink" role="button" tabindex="0">← 비교로</label>'
|
||||
if not dates:
|
||||
return (f'<h2 class="cmp-title">📋 거래목록 {back}</h2>'
|
||||
'<div class="empty">아직 거래 기록이 없어요 — 스캔이 진입 조건을 기다리는 중</div>')
|
||||
if sel_date not in dates:
|
||||
sel_date = dates[0] # 기본 = 최근 거래일
|
||||
if sel_date not in selectable:
|
||||
sel_date = today # 기본 = 오늘 (거래 없으면 '이 날은 거래 없어요' 표시)
|
||||
|
||||
# 달력형 날짜 선택 — 거래일만 밝게·클릭가능, 거래 없는 날은 어둡게. 월 이동은 JS(새로고침 X).
|
||||
is_today = sel_date == datetime.now(config.KST).strftime('%Y-%m-%d')
|
||||
is_today = sel_date == today
|
||||
cal_js = (_DAY_CAL_JS.replace('__DATES__', json.dumps(dates))
|
||||
.replace('__SEL__', sel_date)
|
||||
.replace('__TODAY__', datetime.now(config.KST).strftime('%Y-%m-%d')))
|
||||
.replace('__TODAY__', today))
|
||||
picker = (f'<details class="day-cal-wrap" id="day-cal-wrap"><summary>'
|
||||
f'📅 <b id="day-sel-label">{sel_date}{" (오늘)" if is_today else ""}</b> '
|
||||
f'<span class="muted">▾ 날짜 선택</span>'
|
||||
@@ -1513,7 +1618,9 @@ def _day_trades_panel(sel_date: str | None = None) -> str:
|
||||
a['n'] = len(a['vids'])
|
||||
|
||||
if not agg:
|
||||
return head + '<div class="empty">이 날은 거래가 없어요</div>'
|
||||
# 빈 날도 #day-sub·#day-rows 컨테이너 유지 — 다른 날로 fetch 전환 시 갱신되게
|
||||
return (head + '<div class="sub" id="day-sub">이 날은 거래가 없어요</div>'
|
||||
+ '<div id="day-rows"><div class="empty">이 날은 거래가 없어요 — 스캔이 진입 조건을 기다렸어요</div></div>')
|
||||
|
||||
# (종목, 방향) 단위로 한 아이템 — 같은 종목에 매수·매도 동시면 2개 아이템으로 분리.
|
||||
# 정렬: 매도(청산) 먼저, 그다음 전략 수 많은 순.
|
||||
@@ -1686,14 +1793,18 @@ def _trades_table(trades) -> str:
|
||||
return tr_rows or '<div class="empty">거래 없음</div>'
|
||||
|
||||
|
||||
def _decision_card(d, watch_n: int = 0, held_n: int = 0, acc_detail: str = '') -> str:
|
||||
"""관심종목 탭 종목 카드 (일반·제외목록 공용). watch_n/held_n = 이 종목을 관심/보유 중인 계좌 수.
|
||||
acc_detail 이 있으면 👁🛒 클릭 시 '어느 전략이 보는지' 목록 토스트."""
|
||||
def _decision_card(d, watch_n: int = 0, held_n: int = 0, acc_detail: str = '', buy_n: int = 0) -> str:
|
||||
"""관심종목 탭 종목 카드 (일반·제외목록 공용). watch_n=관심(대기+매수준비) 총합, buy_n=매수준비중, held_n=보유.
|
||||
acc_detail 이 있으면 배지 클릭 시 '어느 전략이 보는지' 목록 토스트."""
|
||||
chips = _chips(d.get('checks', []))
|
||||
src = '/'.join(ORIGIN_LABEL.get(s, s) for s in d.get('sources', []))
|
||||
acc = ''
|
||||
wait_n = max(0, watch_n - buy_n) # 대기중 = 관심 총합 − 매수준비중
|
||||
if watch_n or held_n:
|
||||
parts = ([f'👁{watch_n}'] if watch_n else []) + ([f'🛒{held_n}'] if held_n else [])
|
||||
# 🟢 매수준비중 · 🟡 대기중 · 🛒 보유 — 0인 항목은 생략
|
||||
parts = ([f'<span style="color:#22c55e">🟢{buy_n}</span>'] if buy_n else []) \
|
||||
+ ([f'<span style="color:#eab308">🟡{wait_n}</span>'] if wait_n else []) \
|
||||
+ ([f'🛒{held_n}'] if held_n else [])
|
||||
attrs = (f' data-title="{_esc(d.get("name"))} — 보는 전략" data-detail="{_esc(acc_detail)}"'
|
||||
if acc_detail else '')
|
||||
acc = f' <span class="muted acc-n"{attrs}>{" ".join(parts)}</span>'
|
||||
@@ -1706,7 +1817,7 @@ def _decision_card(d, watch_n: int = 0, held_n: int = 0, acc_detail: str = '') -
|
||||
f'<button type="submit" title="관찰목록에서 삭제">✕</button></form>')
|
||||
return (
|
||||
f'<div class="card"><div class="card-h">'
|
||||
f'<b>{_esc(d.get("name"))}</b> <span class="muted">{_esc(d.get("code"))} · {_esc(src)}</span>{acc}'
|
||||
f'<b>{_esc(d.get("name"))}</b> <span class="muted">{_esc(src)}</span>{acc}'
|
||||
f'<span class="price">{price_h}{pnl_h}</span>{del_btn}</div>'
|
||||
f'<div class="reason">{_esc(d.get("reason",""))}</div>'
|
||||
f'<div class="chks">{chips}</div></div>'
|
||||
@@ -1792,7 +1903,9 @@ def render_html(view: str | None = None, day_date: str | None = None,
|
||||
if ws:
|
||||
if lines:
|
||||
lines.append('')
|
||||
lines.append(f'👁 관심 {len(ws)}개 전략:')
|
||||
_nb = sum(1 for _, st, _ in ws if st in ('BUY', 'ADD'))
|
||||
_nw = len(ws) - _nb
|
||||
lines.append(f'관심 {len(ws)}개 — 🟢매수준비 {_nb} · 🟡대기 {_nw}:')
|
||||
for vid, st, rsn in ws:
|
||||
em = '🟢' if st in ('BUY', 'ADD') else '🟡'
|
||||
rk = rank_map.get(vid)
|
||||
@@ -1838,7 +1951,7 @@ def render_html(view: str | None = None, day_date: str | None = None,
|
||||
judge_html += f'<h3 class="grp"><span class="badge" style="background:{color}">{label}</span> {len(items)}</h3>'
|
||||
for d in items:
|
||||
judge_html += _decision_card(d, watch_n.get(d.get('code'), 0), held_n.get(d.get('code'), 0),
|
||||
_acc_detail(d.get('code')))
|
||||
_acc_detail(d.get('code')), buy_n.get(d.get('code'), 0))
|
||||
|
||||
# 관찰목록 중 아직 스캔 안 된(방금 추가 등) 종목 → '스캔 대기'로 즉시 표시
|
||||
wl = umod._load_watchlist()
|
||||
@@ -1854,7 +1967,7 @@ def render_html(view: str | None = None, day_date: str | None = None,
|
||||
f'<button type="submit" title="관찰목록에서 삭제">✕</button></form>')
|
||||
judge_html += (
|
||||
f'<div class="card"><div class="card-h">'
|
||||
f'<b>{_esc(e.get("name") or code)}</b> <span class="muted">{_esc(code)} · {_esc(src)}</span>'
|
||||
f'<b>{_esc(e.get("name") or code)}</b> <span class="muted">{_esc(src)}</span>'
|
||||
f'<span class="price muted">다음 스캔 대기</span>{del_btn}</div></div>'
|
||||
)
|
||||
|
||||
@@ -1866,7 +1979,7 @@ def render_html(view: str | None = None, day_date: str | None = None,
|
||||
f'<span class="bt-type-d" style="display:inline;margin-left:6px">스캔은 하되 매수 안 함</span></summary>'
|
||||
f'<div class="body">' + ''.join(
|
||||
_decision_card(d, watch_n.get(d.get('code'), 0), held_n.get(d.get('code'), 0),
|
||||
_acc_detail(d.get('code')))
|
||||
_acc_detail(d.get('code')), buy_n.get(d.get('code'), 0))
|
||||
for d in excluded) + '</div></details>'
|
||||
)
|
||||
|
||||
@@ -1879,8 +1992,10 @@ def render_html(view: str | None = None, day_date: str | None = None,
|
||||
_sel = ' selected' if view == vv['id'] else ''
|
||||
_fav = f'{icon_of[vv["id"]]} ' if vv['id'] in icon_of else ''
|
||||
_vp = vv.get('params') or {}
|
||||
_rx = ' 완화' if (not int(_vp.get('TREND_SLOPE_GATE', 1)) or not int(_vp.get('MID_ARRAY_GATE', 1))) else ''
|
||||
_im = ' 즉시' if int(_vp.get('IMMEDIATE_ENTRY', 0)) else ''
|
||||
opts += (f'<option value="{_esc(vv["id"])}"{_sel}>{_fav}{_rk(vv["id"])}{_vid_disp(vv["id"])} '
|
||||
f'[{_BEAR_WORD[_bear_mode(_vp)]}] · {_esc(_market_fit_words(_vp))}</option>')
|
||||
f'[{_BEAR_WORD[_bear_mode(_vp)]}{_rx}{_im}] · {_esc(_cat_name(_vp))}</option>')
|
||||
view_sel = (f'<div class="sub" style="margin:4px 0 8px">체크리스트 시각: '
|
||||
f'<select id="judge-view" '
|
||||
f'onchange="location.href=\'/?t=watch&view=\'+encodeURIComponent(this.value)">{opts}</select></div>')
|
||||
@@ -1935,6 +2050,7 @@ h3.grp{{font-size:13px;margin:16px 0 8px;color:#cbd5e1}}
|
||||
.chks{{display:flex;flex-wrap:wrap;gap:5px}}
|
||||
.chk{{background:#0f151c;border:1px solid #1f2937;border-radius:6px;padding:2px 7px;font-size:11px}}
|
||||
.chk .cd{{color:#6b7280;margin-left:5px}}
|
||||
.acc-n{{font-size:9px}}
|
||||
.acc-n[data-detail]{{cursor:pointer;text-decoration:underline dotted #475569;text-underline-offset:3px}}
|
||||
#chk-toast .ct-d{{max-height:55vh;overflow-y:auto}}
|
||||
.chk-i{{background:none;border:none;color:#60a5fa;font-size:11px;cursor:pointer;padding:0 0 0 4px;line-height:1;vertical-align:baseline}}
|
||||
@@ -1976,6 +2092,7 @@ h3.grp{{font-size:13px;margin:16px 0 8px;color:#cbd5e1}}
|
||||
.cal-wd{{text-align:center;font-size:9px;color:#64748b;padding:1px 0}}
|
||||
.cal-d{{position:relative;text-align:center;padding:5px 0;border-radius:5px;font-size:11px;font-variant-numeric:tabular-nums}}
|
||||
.cal-d.on{{background:#0f2a3f;color:#7dd3fc;border:1px solid #1e3a5f;font-weight:700;text-decoration:none;cursor:pointer}}
|
||||
.cal-d.today-open{{color:#7dd3fc;border:1px dashed #334155;text-decoration:none;cursor:pointer}}
|
||||
.cal-d.off{{color:#2c3848}}
|
||||
.cal-d.sel{{background:#2563eb;color:#fff;border-color:#60a5fa;box-shadow:0 0 0 1px #60a5fa}}
|
||||
.cal-d.tdy::after{{content:'';position:absolute;bottom:2px;left:50%;transform:translateX(-50%);width:3px;height:3px;border-radius:50%;background:#7dd3fc}}
|
||||
@@ -1996,6 +2113,8 @@ h3.grp{{font-size:13px;margin:16px 0 8px;color:#cbd5e1}}
|
||||
.bic0{{background:#334155;color:#cbd5e1}}
|
||||
.bic1{{background:#1e3a5f;color:#7dd3fc}}
|
||||
.bic2{{background:#4a2e12;color:#fbbf24}}
|
||||
.bic-relax{{background:#3b1d4a;color:#d8b4fe}}
|
||||
.bic-now{{background:#0c3a2e;color:#6ee7b7}}
|
||||
.cmp-sortbar{{display:flex;gap:6px;margin:0 0 10px}}
|
||||
.cmp-sortbar button{{flex:1;background:#141b24;border:1px solid #1f2937;border-radius:8px;color:#9ca3af;font-size:11px;font-weight:600;padding:7px 4px;cursor:pointer}}
|
||||
.cmp-sortbar button.active{{background:#15406e;color:#fff;border-color:#2952cc}}
|
||||
@@ -2100,7 +2219,7 @@ input[name="tab"],input[name="subtab"]{{display:none}}
|
||||
<input type="text" id="wq" placeholder="종목명 또는 6자리 코드 검색" aria-label="종목 검색" autocomplete="off">
|
||||
<button type="button" id="wq-btn" class="btn-addcmp">검색</button></div>
|
||||
<div id="wq-results" class="wsearch-results hidden"></div>
|
||||
<div class="sub">상태 그룹은 전체 전략(메인+변이) 합산 — 한 전략이라도 보유/매수대기면 그 그룹으로 · 체크리스트는 기준전략(M) 시각 · 👁관심 🛒보유 전략 수</div>
|
||||
<div class="sub">상태 그룹은 전체 전략(메인+변이) 합산 — 한 전략이라도 보유/매수대기면 그 그룹으로 · 배지: 🟢매수준비 · 🟡대기 · 🛒보유 전략 수 (탭하면 어떤 전략인지)</div>
|
||||
<div class="sub">종목명 일부만 입력해도 후보가 떠요 · 후보를 누르면 추가 · 자산(비하이브 워치·관심·보유)은 자동 편입 · 삭제는 카드의 ✕ (수동만) · 보유 중이면 삭제해도 청산까지 추적</div>
|
||||
{view_sel}
|
||||
{judge_html}
|
||||
|
||||
Reference in New Issue
Block a user