feat(sim): 정렬 토글(오름/내림)·40일선 1등전략(v300) 복구·보유배지 📦

- sim_web 비교탭 정렬: 손익/평가/보유/거래순 클릭마다 오름↔내림 토글 (기본 내림, 평가순만 오름), 활성버튼 화살표 표시
- 40일선 1등 전략 v62 파라미터 복구 → v300 (새 계좌, 220개)
- behive_web 보유 배지: '보유 xx 주' → '📦 xx' (폰트 8px, sim웹과 통일)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hyowons
2026-06-16 12:16:08 +09:00
parent a61f1f67c5
commit 70f6b9a34d
230 changed files with 782 additions and 261 deletions
+34 -18
View File
@@ -1122,7 +1122,7 @@ def _compare_panel() -> str:
bt_row = ''
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'data-ret="{ret}" 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)}{_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> '
@@ -1155,29 +1155,45 @@ def _compare_panel() -> str:
f'</div></details>'
)
sortbar = ('<div class="cmp-sortbar" id="cmp-sortbar">'
'<button type="button" data-s="rank">손익순</button>'
'<button type="button" data-s="bt">평가순</button>'
'<button type="button" data-s="held">보유순</button>'
'<button type="button" data-s="trades">거래순</button></div>')
'<button type="button" data-s="rank" data-attr="data-ret">손익순</button>'
'<button type="button" data-s="bt" data-attr="data-bt">평가순</button>'
'<button type="button" data-s="held" data-attr="data-held">보유순</button>'
'<button type="button" data-s="trades" data-attr="data-trades">거래순</button></div>')
# 각 버튼: 한 번 누를 때마다 오름/내림 토글. 기본 방향 — 손익/보유/거래=내림(desc), 평가(백테스트순위)=오름(asc)
sort_js = """
<script>(function(){
var bar=document.getElementById('cmp-sortbar'),box=document.getElementById('cmp-rows');
if(!bar||!box)return;
var ATTR={rank:'data-ret',bt:'data-bt',held:'data-held',trades:'data-trades'};
var LBL={rank:'손익순',bt:'평가순',held:'보유순',trades:'거래순'};
var DEFDIR={rank:'desc',bt:'asc',held:'desc',trades:'desc'}; // 기본 방향
function num(el,a){return parseFloat(el.getAttribute(a))||0;}
var CMP={rank:function(a,b){return num(a,'data-rank')-num(b,'data-rank');},
bt:function(a,b){return num(a,'data-bt')-num(b,'data-bt');},
held:function(a,b){return num(b,'data-held')-num(a,'data-held');},
trades:function(a,b){return num(b,'data-trades')-num(a,'data-trades');}};
function apply(m){
var f=CMP[m]; if(!f)return;
Array.prototype.slice.call(box.children).sort(f).forEach(function(r){box.appendChild(r);});
bar.querySelectorAll('button').forEach(function(b){b.classList.toggle('active',b.dataset.s===m);});
try{sessionStorage.setItem('cmpSort',m);}catch(e){}
function apply(m,dir){
var at=ATTR[m]; if(!at)return;
var mul=(dir==='asc')?1:-1;
Array.prototype.slice.call(box.children)
.sort(function(a,b){return mul*(num(a,at)-num(b,at));})
.forEach(function(r){box.appendChild(r);});
bar.querySelectorAll('button').forEach(function(b){
var on=b.dataset.s===m;
b.classList.toggle('active',on);
b.textContent=LBL[b.dataset.s]+(on?(dir==='asc'?'':''):'');
});
try{sessionStorage.setItem('cmpSort',m);sessionStorage.setItem('cmpDir',dir);}catch(e){}
}
bar.addEventListener('click',function(e){var b=e.target.closest('button');if(b)apply(b.dataset.s);});
var m='rank';try{m=sessionStorage.getItem('cmpSort')||'rank';}catch(e){}
if(!CMP[m])m='rank';
apply(m);
bar.addEventListener('click',function(e){
var b=e.target.closest('button'); if(!b)return;
var m=b.dataset.s, cur=sessionStorage.getItem('cmpSort'), curdir=sessionStorage.getItem('cmpDir');
var dir;
if(cur===m){dir=(curdir==='asc')?'desc':'asc';} // 같은 버튼 재클릭 → 방향 토글
else{dir=DEFDIR[m];} // 다른 버튼 → 그 버튼 기본 방향
apply(m,dir);
});
var m='rank',dir;
try{m=sessionStorage.getItem('cmpSort')||'rank';dir=sessionStorage.getItem('cmpDir');}catch(e){}
if(!ATTR[m])m='rank';
if(!dir)dir=DEFDIR[m];
apply(m,dir);
})();</script>"""
fav_js = ("""
<script>(function(){