auto: 일일 백업 2026-06-19 02:00

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hyowons
2026-06-19 02:00:06 +09:00
parent 70e2251995
commit dc537f9925
358 changed files with 6184 additions and 1360 deletions
+21 -13
View File
@@ -68,25 +68,31 @@ PTR_HTML = """
# 전체 리로드여도 보던 화면이 유지되게 한다 (자동·당겨서·수동 새로고침 공통).
REFRESH_JS = """
<script>(function(){
var TABS=['lab','watch','market'],SUBS=['bt','cmp','day'];
var TABS=['lab','watch','market'],SUBS=['bt','cmp','day'],WSUBS=['mine','auto'];
var p=new URLSearchParams(location.search);
var t=p.get('t'),s=p.get('s'),vw=p.get('view');
var t=p.get('t'),s=p.get('s'),w=p.get('w'),vw=p.get('view');
if(SUBS.indexOf(t)>=0){s=t;t='lab';} /* 레거시 ?t=bt/cmp 하위호환 */
if(t==='acct'||t==='tune')t='lab'; /* 삭제된 탭 하위호환 */
if(t){var r=document.getElementById('tab-'+t); if(r)r.checked=true;}
if(s){var rs=document.getElementById('sub-'+s); if(rs)rs.checked=true;}
if(w){var rw=document.getElementById('wsub-'+w); if(rw)rw.checked=true;}
/* 탭 라디오 ↔ URL 동기화 — 어떤 새로고침(당겨서/브라우저/reload)이든 보던 탭으로 돌아오게 */
function simURL(){
var cur=TABS.find(function(x){var e=document.getElementById('tab-'+x);return e&&e.checked;});
var sub=SUBS.find(function(x){var e=document.getElementById('sub-'+x);return e&&e.checked;});
var u='/?t='+(cur||'lab')+(cur==='lab'?'&s='+(sub||'cmp'):'')+((cur==='watch'&&vw)?'&view='+encodeURIComponent(vw):'');
var u='/?t='+(cur||'lab')+(cur==='lab'?'&s='+(sub||'cmp'):'');
if(cur==='watch'){
var wc=WSUBS.find(function(x){var e=document.getElementById('wsub-'+x);return e&&e.checked;});
u+='&w='+(wc||'mine');
if(vw)u+='&view='+encodeURIComponent(vw);
}
if(cur==='lab'&&sub==='day'){var dd=new URLSearchParams(location.search).get('d');
if(dd)u+='&d='+encodeURIComponent(dd);} /* 거래목록 선택 날짜 보존 */
return u;
}
window._simURL=simURL;
function syncUrl(){try{history.replaceState(null,'',simURL());}catch(e){}}
document.querySelectorAll('input[name="tab"],input[name="subtab"]').forEach(function(r2){r2.addEventListener('change',syncUrl);});
document.querySelectorAll('input[name="tab"],input[name="subtab"],input[name="wsub"]').forEach(function(r2){r2.addEventListener('change',syncUrl);});
syncUrl();
function banner(msg,color){var b=document.createElement('div');b.className='saved-banner';
if(color)b.style.background=color;b.textContent=msg;document.body.appendChild(b);
@@ -125,9 +131,7 @@ REFRESH_JS = """
var ae=document.activeElement;
if(ae&&(ae.tagName==='INPUT'||ae.tagName==='TEXTAREA')) return; /* 입력 중 멈춤 */
saveState();
var cur=TABS.find(function(x){var e=document.getElementById('tab-'+x);return e&&e.checked;});
var sub=SUBS.find(function(x){var e=document.getElementById('sub-'+x);return e&&e.checked;});
location.href='/?t='+(cur||'lab')+(cur==='lab'?'&s='+(sub||'cmp'):'')+((cur==='watch'&&vw)?'&view='+encodeURIComponent(vw):'');
location.href=simURL();
},60000);
})();</script>
"""
@@ -1854,7 +1858,8 @@ def _decision_card(d, watch_n: int = 0, held_n: int = 0, acc_detail: str = '', b
def render_html(view: str | None = None, day_date: str | None = None,
tab: str | None = None, sub: str | None = None) -> str:
tab: str | None = None, sub: str | None = None,
wsub: str | None = None) -> str:
snap = _load(config.LAST_SCAN_PATH, {})
from . import variants as vmod
_cv = [c for c in (_load(vmod.COMPARE_PATH, {}) or {}).get('variants', [])
@@ -2050,7 +2055,8 @@ def render_html(view: str | None = None, day_date: str | None = None,
f'[{_BEAR_WORD[_bear_mode(_vp)]}{_rx}{_im}{_av}] · {_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>')
f'onchange="var _w=document.getElementById(\'wsub-auto\').checked?\'auto\':\'mine\';'
f'location.href=\'/?t=watch&w=\'+_w+\'&view=\'+encodeURIComponent(this.value)">{opts}</select></div>')
mkt = snap.get('market_ok', {})
mkt_h = ' · '.join(f'{k} {"양호" if v else "약세"}' for k, v in mkt.items()) or ''
@@ -2062,7 +2068,8 @@ def render_html(view: str | None = None, day_date: str | None = None,
# 활성 탭/서브탭을 서버에서 미리 checked — JS가 로드 후 바꾸며 생기던 깜빡임 제거.
tab = tab if tab in ('lab', 'watch', 'market') else 'lab'
sub = sub if sub in ('cmp', 'bt', 'day') else 'cmp'
ck = {f'tab-{tab}': ' checked', f'sub-{sub}': ' checked'}
wsub = wsub if wsub in ('mine', 'auto') else 'mine'
ck = {f'tab-{tab}': ' checked', f'sub-{sub}': ' checked', f'wsub-{wsub}': ' checked'}
def _ck(rid):
return ck.get(rid, '')
@@ -2270,8 +2277,8 @@ input[name="tab"],input[name="subtab"],input[name="wsub"]{{display:none}}
<input type="radio" name="subtab" id="sub-cmp"{_ck('sub-cmp')}>
<input type="radio" name="subtab" id="sub-bt"{_ck('sub-bt')}>
<input type="radio" name="subtab" id="sub-day"{_ck('sub-day')}>
<input type="radio" name="wsub" id="wsub-mine" checked>
<input type="radio" name="wsub" id="wsub-auto">
<input type="radio" name="wsub" id="wsub-mine"{_ck('wsub-mine')}>
<input type="radio" name="wsub" id="wsub-auto"{_ck('wsub-auto')}>
<header class="hd">
<h1>🪎 레이 자동매매 시뮬레이션 <span id="health" class="hdot {hcls}" data-detail="{_esc(hdetail)}" role="button" tabindex="0" title="시뮬 상태 보기"></span></h1>
<div class=sub>마지막 스캔 {scanned or ''} · 시장 {mkt_h}</div>
@@ -2346,12 +2353,13 @@ class Handler(BaseHTTPRequestHandler):
_dd = (_qs.get('d') or [None])[0]
_tab = (_qs.get('t') or [None])[0]
_sub = (_qs.get('s') or [None])[0]
_wsub = (_qs.get('w') or [None])[0]
# 레거시 ?t=bt/cmp/day (서브탭이 t로 옴) 하위호환 — REFRESH_JS와 동일 규칙
if _tab in ('bt', 'cmp', 'day'):
_sub, _tab = _tab, 'lab'
if _tab in ('acct', 'tune'):
_tab = 'lab'
body = render_html(view=_view, day_date=_dd, tab=_tab, sub=_sub).encode('utf-8')
body = render_html(view=_view, day_date=_dd, tab=_tab, sub=_sub, wsub=_wsub).encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'text/html; charset=utf-8')
# 캐시 금지 — Safari 휴리스틱 캐싱이 옛 HTML/JS를 재사용해 탭 복원 로직이 구버전으로 돌던 문제 방지