o
    ¹¬‡ju ã                   @   sT'  d Z ddlZddlZddlZddlZddlmZmZ zddlm	Z	m
Z
 dZW n ey1   dZY nw dd„ Zd	d
„ Zdd„ Zdd„ Zdd„ Zej ej e¡¡Zej ed¡Zej d¡Zej ed¡Zej ed¡Zej ed¡Zej ed¡Zej ed¡Ze edƒ�Z!e "e!¡Z#W d  ƒ n1 s•w   Y  i Z$ej %e¡rÀe edƒ�Z!e "e!¡ &di ¡Z$W d  ƒ n1 s»w   Y  i dd“dd“dd“dd“dd“d d“d!d“d"d#“d$d#“d%d#“d&d#“d'd#“d(d#“d)d#“d*d#“d+d#“d,d#“i d-d#“d.d#“d/d#“d0d#“d1d2“d3d2“d4d2“d5d2“d6d2“d7d2“d8d9“d:d9“d;d<“d=d<“d>d<“d?d<“¥Z'd@dAdBdCdDdEdFœZ(g dG¢Z)i Z*dZ+ej %e¡�re edƒ�1Z!e "e!¡Z,e, &dHi ¡Z*e, &dId¡Z+e, &dJi ¡Z-e, &dKg ¡Z.e, &dLg ¡Z/e, &dMdN¡Z0W d  ƒ n	1 �syw   Y  ni Z-g Z.g Z/dNZ0i Z1ej %e¡�r¯e edƒ�Z!e "e!¡ &dOi ¡Z1W d  ƒ n	1 �sªw   Y  g dP¢g dQ¢g dR¢g dS¢dTœZ2g dU¢g dV¢g dW¢g dX¢dYœZ3dZd[„ Z4eƒ Z5eƒ Z6g Z7e#d\ D �]}Z8e9d]d^„ e8d_ D ƒƒZ:e9d`d^„ e8d_ D ƒƒZ;e<e:e; e=e8da dbƒ dcƒZ>e>ddk�rde\Z?Z@n"e>dfk�rdg\Z?Z@ne>dhk�r"di\Z?Z@ne>djk�r,dk\Z?Z@ndl\Z?Z@e8d_ D ]ZAe5eAdm   db7  < e6eAdn   db7  < �q4e4e8d_ ƒZBe$ &e8do i ¡ZCe* &e8do i ¡ZDe- &e8do i ¡ZEe7 Fi doe8do “dpe8dp “dae8da “dqe:“dre;“dse>“dte?“due@“dve8dv “dwe<e8dw dcƒ“dxe8 &dxd¡“dyeC &dydz¡“d{eC &d{g ¡“d_eGe8d_ d|d}„ d~�“deD &d€d¡“d�eD &d‚d¡“dƒeHeDƒ“i d„eD &d…d¡“d†eD &d‡d¡“dˆeD &d‰d¡“dŠeD &d‹d¡“dŒeD &d�d¡“dŽeD &d�d�¡“d‘eD &d’d�¡“d“eD &d”d¡“d•eD &d–d—¡“d˜eD &d™dš¡“d›eD &dœd�¡“džeE &dŸd ¡“d¡eE &d¢dN¡“d£eE &d£d¡“d¤eE &d¤d¡“d¥eE &d¥d¡“d¦eE &d¦d¡“¥eE &d§d¡eE &d¨d¡eE &d©dª¡d«œ¥eB¥¡ �qÝi ZIe#d\ D ]"ZJeJd_ D ]ZAeAdm eIv�r�eAd¬ eAd­ eAd® d¯œeIeAdm < �qh�qbeGe5 K¡ d°d}„ dd±�ZLe9d²d^„ e7D ƒƒZMe9d³d^„ e7D ƒƒZNed´d^„ e7D ƒƒZOeƒ ZPe7D ]Z8e8d{ D ]ZQePeQ  db7  < �q¶�q°edµd}„ ƒZRe7D ]¾Z8e8dy ZSeSdzk�rِqÌeReS ZTeTd¶  db7  < eTdq  e8dq 7  < eTdr  e8dr 7  < eTd·  e8ds 7  < eTd¸  e8d¹ 7  < eTdº  e8dw 7  < e=eTd» e8ds ƒeTd»< eUeTd¼ e8ds ƒeTd¼< e8dž d½k�r<eTdK  db7  < e8dž d¾k�rKeTdL  db7  < e8dƒ �rleTd¿  e8dŽ 7  < eTdÀ  e8d 7  < eTdÁ  db7  < eTdÂ  Fe8do e8dp e8ds e8da e8dx e8dž e8dŽ dÜ¡ �qÌeRD ]]ZSeReS d¶ ZVe=eReS dÁ dbƒZWe<eReS d· eV dcƒeReS d·< e<eReS d¸ eV dƒeReS d¸< e<eReS dº eV dcƒeReS dº< e<eReS d¿ eW dbƒeReS d¿< e<eReS dÀ eW dbƒeReS dÀ< �q�eGeR K¡ dÄd}„ dd±�ZXdÅdƄ ZYg ZZeR K¡ D ]Í\Z[ZTe=eTd¶ dbƒZ\eTdº Z]eTd· Z>eTdK eTdL  Z^e\�r'e<eTdq e\ dǃndÈZ_eTdÁ dk�r4eTd¿ ndÉZ`eTdÁ dk�rAeTdÀ ndÉZaeYd�e]dÊ  dd˃ZbeYd�e>d�  dd˃Z8eYd�e^dÌ  dd˃ZVeYe`dd˃ZceYeadd˃Zde<dÍeb dÎe8  dÏeV  dfec  dÐed  dbƒZeeedÑk�rŒdÒZfneedÓk�r”dÔZfneedÕk�rœdÖZfn
eed×k�r¤dØZfndÙZfeZ Fe[e\e' &e[dڡe( &e[dN¡eee]e>e^e_e<ecdbƒe<eddbƒefeTdK eTdL dۜ¡ �q eZjgdÜd}„ dd±� dÝdބ eZD ƒZhdßdބ eZD ƒZidàdބ eZD ƒZjdá dâd^„ ehddã… D ƒ¡�püdäZkdá dåd^„ eiddã… D ƒ¡�pdæZldá dçd^„ ejddã… D ƒ¡�pdèZmeZ�r&eZd ndZneZ�rAdéem› dêek› dëendì dík�r;dîndï› dð�ZondñZoekelemeoen�rQe<endì dbƒnden�rYendp ndòdóœZpdôdõ„ eZD ƒZqg ZreZD ])Zser Fesdp esdö es &d÷dN¡esd¶ esdì esdø esdù esdú esdû esdü dýœ
¡ �qhdþdބ e)D ƒZteretdÿœZueeZd�d �Zveeud�d �Zweepd�d �Zxe<eMeN e=eMeN dbƒ dcƒZye�dd^„ e7D ƒƒZzi Z{e�r÷e7D ]$Z8e8dp Z[dN e	e[e
j|�d�¡Z}dN e	e[ƒ¡Z~e}�d e~ e{e8do < �qÒee{d�d �Zee7d�d �Z€eeId�d �Z�ee‚eLƒd�d �Zƒee‚eO „¡ ƒd�d �Z…ee‚eP „�d¡ƒd�d �Z†e�ddބ eXD ƒd�d �Z‡g Zˆ�zœddl‰ZŠddl‹Z‹e‹ Œe‹j�jŽ¡ eŠ �¡ Z�de�_‘e�j’ “�d�d�dœ¡ g �d	¢Z”e•ƒ Z–�d
D ]¢Z—dbdËe—dbdcdc�d�d�d�dœ	Z˜e”D ]ŒZ™ze�j&e™e˜�d�d�Zšeš ¡  &di ¡ &�dg ¡Z›e›�sŸW �qe›D ]\Zœeœ &�ddN¡Z�e�e–v �r±�q¡e– že�¡ eˆ Fe�eœ &�ddN¡e<eœ &�dd¡dcƒeœ &�dd¡dk�räeŸe<eœ &�dd¡�d dbƒƒ�d ndòeœ &�ddN¡�pîdòeœ &�ddN¡�p÷dN�dœ¡ �q¡W  n e �y   Y �qw �qkeˆjg�dd}„ dd±� e¡�de¢eˆƒ› �d�ƒ i Z£eˆd�d… D ]žZ¤e¤ &dodN¡Z¥e¤ &dpdN¡Z¦e¥�rAe¦�sC�q-z}db�ddbdbdcdc�d�de¥ �d�dœ	Z§e�j&�d e§�d�d�Z¨e¨ ¡  &di ¡ &�dg ¡�pqg Z©g Zªe©ddʅ D ]<Z«eª Fe« &�ddN¡e« &�ddN¡e<e« &�dd¡dcƒe« &�d!d¡dk�r¯eŸe<e« &�d!d¡�d dbƒƒ�d ndò�d"œ¡ �qzeª�r¾eªe£e¦< W �q- e �yË   Y �q-w e¡�d#e¢e£ƒ› �d$�ƒ W n e �yø Z¬ ze¡�d%e¬› �ƒ g Zˆi Z£W Y dZ¬[¬ndZ¬[¬ww eeˆd�d �Z­ee£d�d �Z®i Z¯�zt�d&Z°g �d	¢Z±g Z²dZ³e±D ]©Z´zœe�j&e´dbdËdbdbdcdce°�d'�d(œ�d)�d�Zµeµ ¡  &di ¡Z¶e¶ &d¶d¡Z³e·e¶ &�dg ¡�	pIg ƒZ²dbZ¸e³dk�	r¡e¢e²ƒe³k �	r¡e¸dËk�	r¡e¸db7 Z¸e�j&e´e¸dËdbdbdcdce°�d'�d(œ�d)�d�Z¹e¹ ¡  &di ¡ &�dg ¡�	p†g Zºeº�	s‹ne² »eº¡ e³dk�	r¡e¢e²ƒe³k �	r¡e¸dËk�	s^e³dk�	rµe¢e²ƒe¼e³�d* ƒk�	rµW  nW �	q e �	yÂ   Y �	qw dZ½dZ¾dZ¿dZÀdZÁdg�d+ Zd,�d-„ ZÃe²D ]\ZÄeĠ&�d.¡ZÅeŐd/k�	sïeŐd0k�	rõeÁdb7 ZÁ�	qÛeĠ&�dd¡ZÆzeÇeƃZÈW n eÉeÊf�
y   eÀdb7 ZÀY �	qÛw eÈdk�
re½db7 Z½neÈdk �
r(e¾db7 Z¾ne¿db7 Z¿eÂeÃeȃ  db7  < �	qÛg �d1¢Zːd2dބ e̐d+ƒD ƒZÍe=e³eÁ e½e¾ e¿ eÀ ƒZÎeÎe½e¾e¿eÀdde͐d3œZ¯e¡�d4eΛ �d5e½› �d6e¾› �d7e¿› �d8eÀ› �d9eÁ› �ƒ W n e �
y› Z¬ ze¡�d:e¬› �ƒ i Z¯W Y dZ¬[¬ndZ¬[¬ww ee¯d�d �ZÏg ZАzÔdb�d;dbdbdcdc�d<�d=�d(œZÑg ZҐd>D ]-ZÓz e�j&eÓeѐd�d�ZÔeԠ¡  &di ¡ &�dg ¡ZÒeҐ
rÛW  nW �
q» e �
yè   Y �
q»w eÒD �]oZ¬eŸe¬ &�ddN¡ƒZÕeŸe¬ &�ddN¡ƒZÖe¬ &�dd¡ZÈe×eÈe¼eÇfƒ�s�
qëe<eÇeȃdcƒZØee¬ &�d!d¡ƒZÙee¬ &�d?d¡ƒZÚee¬ &�d@d¡ƒZÛe֠ܡ ZÝdNZސdAeÖv �sL�dBeÝv �sLeՠߐdC¡�rP�dDZÞn°�dEeÖv �s{�dFeÖv �s{�dGeÖv �s{�dHeÖv �s{�dIeÖv �s{�dJeÖv �s{eՠߐdK¡�r�dLZÞn��dMeÖv �s©�dNeÖv �s©�dOeÖv �s©�dPeÖv �s©�dQeÖv �s©�dReÖv �s©�dSeÖv �r­�dTZÞnS�dUeÖv �sý�dVeÖv �sý�dWeÖv �sý�dXeÖv �sý�dYeÖv �sý�dZeÖv �sý�d[eÖv �sý�d\eÖv �sý�d]eÖv �sý�d^eÝv �sý�d_eÖv �sýeՠߐd`¡�sýeՠߐda¡�r �dbZÞeސrZeِdck�rZeÛdk�rZdNZàeސdLk�r�ddZàneސdbk�r$�deZàneސdDk�r.�dfZàn	eސdTk�r7�dgZàeРFeÕeÖeÛeØeŸe<eِd dbƒƒ�d eÙeڐrRe<eÚdbƒndeÞeà�dhœ	¡ �
qëeÐjg�did}„ dd±� eÐd�d)… ZÐe¡�dje¢eЃ› �dk�ƒ W n e �y— Z¬ ze¡�dle¬› �ƒ g ZÐW Y dZ¬[¬ndZ¬[¬ww eeÐd�d �Zág g g d�dmœZâz¼ej ã¡  ä�dn¡Zåg �do¢ZæeæD ]Š\ZçZèZéZêzyg dZëZìeì�dpk �r)�dqeç› �dreì› �dseé› �dteè› �dueå› �
Zíe�j&eí�d�dv�Zîeî ¡ Zïeï &di ¡ &�dwg ¡Zðeð�sün-e렻eð¡ eï &di ¡ &�dxd¡Zñeñ�re¢eëƒeñk�rne¢eðƒeék �rn
eìdb7 Zìeì�dpk �sÊeë�r5eëeâeê< deâ�dy< W �q¸ e �yB   Y �q¸w e¡�dze¢eâ�d{ ƒ› �d|e¢eâ�d} ƒ› �d~e¢eâ�d ƒ› �ƒ W n e �y~ Z¬ ze¡�d€e¬› �ƒ W Y dZ¬[¬ndZ¬[¬ww eeâd�d �Zòe×e¯e‚ƒ�r²eâ &�dy¡�r²e¢eâ &�d{g ¡ƒe¯�d�< e¢eâ &�d}g ¡ƒe¯�d‚< ee¯d�d �ZÏee‚ezƒd�d �Zóee.d�d �Zôee/d�d �Zõee+�pÎi d�d �Zöee1d�d �Z÷e9�dƒd^„ e7D ƒƒZøe<e9�d„d^„ e7D ƒƒe=eødbƒ dbƒZùe<e9�d…d^„ e7D ƒƒe=eødbƒ dbƒZúe<e9�d†d^„ e7D ƒƒe=eødbƒ dbƒZûe<e9�d‡d^„ e7D ƒƒe=eødbƒ dcƒZüe<e9�dˆd^„ e7D ƒƒe=eødbƒ dcƒZýe�d‰d}„ ƒZþe* K¡ D ]°\ZÿZDeD &�dŠi ¡�Z �e  K¡ D ]�\�Z�Zeþ�e �Z�e�d‹  �e &d¶d¡7  < �e�dŒ  �e &�d�d¡7  < �e�dŽ  F�e &�d�d¡¡ �e�d�  F�e &�d‘d¡¡ �e�d’  F�e &�d“d¡¡ �e�d”  Fe=�e &�d•d¡�e &�d–d¡�e &�d—d¡�e &�d˜d¡ƒ¡ �eeIv �röeI�e  &d®dN¡�ed®< eI�e  &d­dN¡�ed­< �qZ�qIg �Zeþ K¡ D ]Õ\�Z�Z�e�d‹ dk�r�qe<�e�dŒ �e�d‹  dË dbƒ�Z�e�dŽ �r>e<e9�e�dŽ ƒe¢�e�dŽ ƒ dcƒnd�Z�e�d� �r[e<e9�e�d� ƒe¢�e�d� ƒ dcƒndZü�e�d’ �rwe<e9�e�d’ ƒe¢�e�d’ ƒ dcƒndZý�e�d” �r“e<e9�e�d” ƒe¢�e�d” ƒ dbƒnd�Z�e F�e�ed® �ed­ �e�d‹ �e�eeUeü�d™ƒeý�e�e�dšk�rÄeü�d›k�rĐdœn�e�d�k�rΐdžn�dŸ�d œ
¡ �q�ejg�d¡d}„ dd±� e7D ]›Z8e8dƒ �sòd�e8�d¢< �qäe* &e8do i ¡ZDeD &�dŠi ¡�Z e•�d£d^„ e8d_ D ƒƒ�Zg �Z	�eD ]R�Z�e�e v �rf�e �e �Z�e &�d¤d�¡�ZeU�e &�d‘db¡�d™ƒ�Z
�e &�d“d¡�Z�edš eU�e
�d) �dƒ e=�edc �d¥ƒ �d) Ze�e	 Fee¡ �q�e	�rye<e9�e	ƒe¢�e	ƒ dbƒnd�e8�d¢< �qäg �Z�d¦dބ �eD ƒ�Z�d§dބ �eD ƒ�Z�e�r¹�eddǅ �Z�e F�d¨dá �d©d^„ �eD ƒ¡› �dª�¡ �e�rאe F�d«dá �d¬d^„ �eddǅ D ƒ¡› �d­�¡ �d®dބ e7D ƒ�Z�e�r�e F�d¯e¢�eƒ› �d°dá �d±d^„ eG�e�d²d}„ dd±�ddʅ D ƒ¡› �¡ e<e9�d³d^„ e7D ƒƒe=eødbƒ dbƒ�Z�e F�d´�e› �dµ�edík�r1�d¶n�ed�k�r:�d·n�d¸› �¡ e9�d¹d^„ e7D ƒƒ�Ze9�dºd^„ e7D ƒƒ�Ze9�d»d^„ e7D ƒƒ�Zi �Ze7D ]Z8e8d¦ dk�r��e &e8dy d¡e8d¦  �ee8dy < �qf�e�e�e�e�r™e<�ee=�edbƒ dbƒnde‚eG�e K¡ �d¼d}„ dd±�ddʅ ƒe+�r¼e+ &�d½¡�d¾k�r¼�d¿ne+�rÌe+ &�d½¡�dÀv �r̐dÁn�dÂe<�ee=e¢e7ƒdbƒ dË dbƒe<�ee=e¢e7ƒdbƒ dË dbƒe<e¢e7ƒ�e �e e=e¢e7ƒdbƒ dË dbƒe7�re=�dÃd^„ e7D ƒƒnd�e�dÄd^„ e7D ƒƒ�e�dÅd^„ e7D ƒƒ�e�dÆd^„ e7D ƒƒ�e�rBe<e=�e� ¡ ƒe=�edbƒ dË dbƒnd�dǜ�dȜ�Ze�ed�d… d�d ��Ze�ed�d ��Ze�ed�d ��Ze#�dÉ �Ze#�dÊ �Ze#�dË �Ze¢e5ƒ�Ze¡�dÌe¢e7ƒ› �dÍe¢eXƒ› �dÎe¢e.ƒ› �ƒ �dϐZ i �dАe“�dÑeŸ�eƒ“�dÒe€“�dÓe“�dÔe�“�dÕeƒ“�dÖe…“�d×e†“�dØe‡“�dÙev“�dÚew“�dÛex“�dÜeó“�dÝeô“�dÞeõ“�dßeö“�dàeeùƒ“eeúƒeeûƒeeüƒeeýƒe÷�e�e�ee­e®eÏeáeò�dáœ¥�Z!�e! K¡ D ]\�Z"�Z#�e � $�e"�e#¡�Z �q
e�j%ed�dâ� e e�dã�dä�då��Z!e!� &�e ¡ W d  ƒ n	1 �sCw   Y  e¡�dæe› �ƒ e¡�dçej� 'e¡�dè �dé›�dê�ƒ z#dd�l(�Z(ej ed¡�Z)�e(� *e�e)¡ e¡�dë�e)› �ƒ W dS  e �y© �Z+ ze¡�dì�e+› �ƒ W Y d�Z+�[+dS d�Z+�[+ww (í  u£   
专业交易员看板 v5 — 三层架构：宏观指数 → 板块分析 → 个股分析
增强：关注功能、板块详情、K线演示、个股详情优化
é    N)ÚCounterÚdefaultdict)Úlazy_pinyinÚStyleTFc                 C   s6   t | tƒrt | ¡r| dkrdS dS t | ¡rdS | S )Nr   ç�Âõ(\ÿX@ç        )Ú
isinstanceÚfloatÚmathZisinfZisnan©Úv© r   úgenerate_report_v6.pyÚ
safe_float   s   
r   c                 C   sJ   t | tƒr	t| ƒS t | tƒrdd„ |  ¡ D ƒS t | tƒr#dd„ | D ƒS | S )Nc                 S   s   i | ]	\}}|t |ƒ“qS r   ©Úsanitize_data©Ú.0Úkr   r   r   r   Ú
<dictcomp>   s    z!sanitize_data.<locals>.<dictcomp>c                 S   s   g | ]}t |ƒ‘qS r   r   ©r   Úir   r   r   Ú
<listcomp>   s    z!sanitize_data.<locals>.<listcomp>)r   r	   r   ÚdictÚitemsÚlist)Úobjr   r   r   r      s   r   c                 K   s   t jt| ƒfi |¤ŽS ©N)ÚjsonÚdumpsr   )r   Úkwr   r   r   Úsafe_json_dumps   s   r!   c                 C   s(   t | ƒ} t| tƒrtt| dƒƒS t| ƒS )Né   )r   r   r	   ÚstrÚroundr   r   r   r   Úsafe_str   s   r%   c              	   C   s$   zt | ƒW S  ttfy   Y dS w )Nr   )r	   Ú
ValueErrorÚ	TypeErrorr   r   r   r   Ú_num"   s
   
ÿr(   Údatazk~/Library/Application Support/TRAE SOLO CN/ModularData/ai-agent/work-mode-projects/6a85bc670c5aa36b3c97c23ezscan_results_filtered.jsonz stock_industry_concepts_new.jsonzbacktest_results.jsonzkline_data.jsonzashare-dashboard.htmlÚrZ
stock_infoõ   煤炭开采u   上游õ	   焦炭Ⅱu	   贵金属õ	   小金属õ   工业金属õ   化学原料u   农化制品õ   普钢u   中游õ	   特钢Ⅱõ   化学制品õ   化学制药u   生物制品u	   中药Ⅱõ   电力u	   燃气Ⅱõ   通用设备õ   汽车零部件õ   通信设备õ   航空装备Ⅱõ   食品加工õ	   白酒Ⅱõ   房地产开发u   下游õ   白色家电õ   消费电子õ   医疗器械õ   专业连锁Ⅱu   影视娱乐õ	   证券Ⅱu   金融õ   多元金融õ   港口航运u   交运õ   航运港口õ   铁路公路õ   航空机场u   券商·投行·互联网金融u   信托·保险·期货·AMCu   港口码头·装卸仓储u   港口枢纽·航运物流u   铁路运输·公路货运u   机场运营·航空客运)r@   rA   rB   rC   rD   rE   ))r+   r4   )r+   r0   )r,   r0   )r,   r1   )r/   r2   )r/   r3   )r2   r3   )r2   r>   )r.   r5   )r.   r<   )r.   r=   )r-   r8   )r-   r=   )r0   r6   )r0   r;   )r0   r<   )r1   r6   )r1   r8   )r5   r;   )r7   r=   )r9   r?   )r:   r?   )rD   rB   )rD   rC   )rB   rC   )r@   r;   )rA   r;   Zbacktest_resultsÚmarket_trendZtrading_signalsÚbuy_signalsÚsell_signalsÚbacktest_dateÚ Z
stock_data)u   出水芙蓉u   三金叉见底u   价托u   DIF上穿零线õ   底部芝麻功u   熊牛转换点)u	   多方炮u	   老鸭头u   均线多头排列u   东方红大阳升u   进入2+3区间)u   放量过头u   轻松过头u   巨量长阳u   会创新高还有新高)rK   u   天眼地量u	   压转托)u   底部反转u   趋势确认u   突破加速u   洗盘结束)u   断头铡刀u   三死叉见顶u   价压u   DIF下穿零线)u	   空方炮u   均线空头排列u   五线顺下u   倒挂老鸭头)u   一阴下穿三角压u	   死蜘蛛u   会创新低还有新低u   牛熊转换点)u   量压u   阳阴墓碑u	   托转压)u   见顶信号u   趋势恶化u   破位下跌u   主力出货c                    s4  t dd„ | D ƒƒ‰ tdd„ | D ƒƒ}tdd„ | D ƒƒ}t|| tt| ƒdƒ dƒ}‡ fdd„t ¡ D ƒ}‡ fd	d„t ¡ D ƒ}d
}|dkrX|t|ƒd |d  t|ƒd  7 }n|t|ƒd |d  t|ƒd  7 }tdtd|ƒƒ}t|ƒdko}t|ƒdk}|s„|dk r†dn|dkr’t	|ƒdkr’dnd}|dkr¤|dkr¤t| ƒdkr¤dn!|dkr´|dkr´t| ƒdkr´dn|dkr¾|dkr¾dn|dkrÄdnd}	|dkrÓ|	dkrÓd \}
}n3|d!krà|	dkràd"\}
}n&|d#krí|	dkríd$\}
}n|d%kröd&\}
}nt	|ƒdk �rd'\}
}nd(\}
}|
||||	||||d)d„ || D ƒd*œ
S )+Nc                 s   ó   � | ]}|d  V  qdS ©Ú	indicatorNr   ©r   Úsr   r   r   Ú	<genexpr>ƒ   ó   € z*analyze_trading_signals.<locals>.<genexpr>c                 s   ó    � | ]}|d  dkrdV  qdS ©Útypeu   看涨é   Nr   rO   r   r   r   rQ   „   ó   € c                 s   rS   ©rU   u   看跌rV   Nr   rO   r   r   r   rQ   …   rW   rV   r"   c                    ó>   g | ]\}}t ‡ fd d„|D ƒƒr|‡ fdd„|D ƒdœ‘qS )c                 3   ó   � | ]}|ˆ v V  qd S r   r   rO   ©Zsignal_namesr   r   rQ   ‡   rR   ú5analyze_trading_signals.<locals>.<listcomp>.<genexpr>c                    ó   g | ]}|ˆ v r|‘qS r   r   rO   r[   r   r   r   ‡   ó    ú6analyze_trading_signals.<locals>.<listcomp>.<listcomp>©ÚpatternÚsignals©Úany©r   ÚnZpsr[   r   r   r   ‡   ó   > z+analyze_trading_signals.<locals>.<listcomp>c                    rY   )c                 3   rZ   r   r   rO   r[   r   r   rQ   ˆ   rR   r\   c                    r]   r   r   rO   r[   r   r   r   ˆ   r^   r_   r`   rc   re   r[   r   r   r   ˆ   rg   é2   r   é   é   éd   é#   õ   高éF   ç      à?õ   低õ   中ç333333ã?éK   é
   é   gš™™™™™Ù?éA   é   皙™™™™É?é7   é   é-   )u   强烈关注uK   多重看涨信号共振，建议重点关注突破确认后的介入机会g333333Ó?)u   重点关注u<   看涨信号占优，建议观察量能配合，择机关注g333333ӿ)õ   注意风险uE   看跌信号主导，持仓者注意止损，未持仓者建议规避ç333333ã¿)u   建议规避u'   多重看跌信号共振，坚决回避)u   观望等待u9   多空信号交织，建议等待方向明确后再操作)u   适度关注u0   信号偏多但不够强烈，建议持续跟踪c                 S   s(   g | ]}|d  › dd  |d ¡› �‘qS )ra   z: ú,rb   )Újoin)r   Úmr   r   r   r   š   ó   ( )
ÚactionZaction_detailÚ
confidenceÚriskÚpriorityÚsentiment_scoreÚmatched_bullÚmatched_bearÚhas_conflictZpattern_descs)
ÚsetÚsumr$   ÚmaxÚlenÚBULLISH_PATTERNSr   ÚBEARISH_PATTERNSÚminÚabs)rb   ÚbullÚbearÚ	sentimentr‡   rˆ   rƒ   r‰   r„   r…   r‚   Zdetailr   r[   r   Úanalyze_trading_signals‚   s0   &$,d
þr•   Ztop50c                 c   rS   rT   r   ©r   Úsigr   r   r   rQ       rW   rQ   rb   c                 c   rS   rX   r   r–   r   r   r   rQ   ¡   rW   Úsignal_countrV   r"   rr   )u   强势看涨Zstrong_bullrx   )u   偏多r’   gš™™™™™ɿ)u   中性Zneutralr}   )u   偏空r“   )u   强势看跌Zstrong_bearrN   ÚdateÚcodeÚnameÚbullishÚbearishr†   ÚclassificationÚcls_keyZlatest_priceÚ
pct_changeÚ
mkt_cap_yiÚindustryõ   未知Zconceptsc                 C   ó   | d S )Nr™   r   ©Úxr   r   r   Ú<lambda>¶   ó    r§   )ÚkeyÚbt_win_rateZoverall_win_rateZbt_avg_returnZoverall_avg_returnÚbt_has_dataÚbt_wr_filteredZoverall_wr_filteredZbt_wr_3dZoverall_wr_3dZbt_wr_5dZoverall_wr_5dÚbt_pfZ
overall_pfÚbt_expZoverall_expÚbt_quant_scoreZquant_scorerh   Zbt_rsiZcurrent_rsiZbt_adxZcurrent_adxZbt_vol_ratioZcurrent_vol_ratiog      ð?Z	bt_bb_posZcurrent_bb_posro   Z
bt_atr_pctZcurrent_atr_pctg       @Útrade_signalÚsignalu   观望Ztrade_reasonÚreasonÚentry_priceÚ	stop_lossÚtarget_priceÚposition_sizeÚwr_adjustedÚwr_filteredÚholding_periodu   1-3天)r·   r¸   r¹   ÚsectionÚdescrU   )rº   r»   rU   c                 C   r¤   ©NrV   r   r¥   r   r   r   r§   Ë   r¨   )r©   Úreversec                 c   rL   )rœ   Nr   rO   r   r   r   rQ   Ì   rR   c                 c   rL   )r�   Nr   rO   r   r   r   rQ   Í   rR   c                 c   s$   � | ]}|d  dkr|d  V  qdS )r¢   r£   Nr   rO   r   r   r   rQ   Î   ó   €" c                   C   s"   dddddddddddg dddœS )Nr   i�ÿÿÿéc   )Útotalrœ   r�   rG   rH   Úavg_sentimentÚavg_confidenceÚavg_quant_scoreÚavg_pct_changeÚavg_win_rateÚbt_countÚstocksÚmax_sentimentÚmin_sentimentr   r   r   r   r   r§   Ó   s   
 
þrÀ   rÁ   rÂ   rƒ   rÄ   rÈ   rÉ   õ   买入u   卖出rÃ   rÅ   rÆ   rÇ   )rš   r›   r”   r˜   r¡   r°   r¯   c                 C   s   | d d | d d fS )NrV   rÁ   rG   r   r¥   r   r   r   r§   ï   r^   c                 C   s   t |t|| ƒƒS r   )rŒ   r�   )r   ÚloÚhir   r   r   Ú_clampò   s   rÍ   rz   r   g      I@ru   rk   é   gìQ¸…ëÑ?g)\�Âõ(Ì?g{®GázÄ?gìQ¸…ëÁ?éD   õ   领跑主线é8   õ   接力补涨é,   u   蓄势观察é"   õ   动能衰减õ   退潮回避u   其他)r›   rÀ   ÚstageÚsubÚscoreÚmomentumr”   Únet_buyÚbreadthÚquantÚwinrateÚtierÚbuyÚsellc                 C   r¤   )NrÙ   r   r¥   r   r   r   r§     r¨   c                 C   ó   g | ]
}|d  dkr|‘qS )rß   rÐ   r   ©r   r*   r   r   r   r     ó    r   c                 C   râ   )rß   rÒ   r   rã   r   r   r   r     rä   c                 C   s   g | ]
}|d  dv r|‘qS )rß   )rÕ   rÖ   r   rã   r   r   r   r     rä   u   、c                 c   rL   ©r›   Nr   rã   r   r   r   rQ     rR   rw   u   暂无明确领跑板块c                 c   rL   rå   r   rã   r   r   r   rQ     rR   u   暂无明确接力板块c                 c   rL   rå   r   rã   r   r   r   rQ     rR   u   暂无明确退潮板块u
   资金由[u   ]向[u   ]迁移，呈rÙ   é<   u   主线强化u   震荡分化u   轮动格局u0   板块数据不足，暂无法推演轮动方向z--)ÚleadÚfollowÚfadeÚ	directionZ	top_scoreZtop_namec                 C   s   i | ]}|d  |“qS ©r›   r   rã   r   r   r   r   %  ó    r   r×   rØ   rß   rÚ   r”   rà   rá   )
r›   r×   rØ   rÀ   rÙ   rß   rÚ   r”   rà   rá   c                 C   s*   g | ]\}}|t v r|t v r||d œ‘qS ))ÚsourceÚtarget)Úsector_by_name)r   ÚaÚbr   r   r   r   -  s   * )ZnodesZedges)Zensure_asciic                 c   rL   )r‚   Nr   rO   r   r   r   rQ   5  rR   )Zstyleú é   c                 C   s   g | ]
\}}d |i|¥‘qS rë   r   r   r   r   r   r   F  rä   zBMozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36zhttps://quote.eastmoney.com/)z
User-AgentZReferer)ú1https://push2delay.eastmoney.com/api/qt/clist/getú,https://push2.eastmoney.com/api/qt/clist/getz.http://80.push2.eastmoney.com/api/qt/clist/get)rV   r   Zf3zm:90+t:2zf12,f14,f3,f20,f128,f136,f140)	ÚpnÚpzÚpoÚnpÚflttÚinvtZfidÚfsÚfieldsé   )ÚparamsÚtimeoutZdiffZf12Zf14Zf20g    „חAu   亿Zf128Zf140)rš   r›   r    ÚamountZ
lead_stockZ	lead_codec                 C   r¤   )Nr    r   r¥   r   r   r   r§   p  r¨   u     全市场板块数据: u
    个板块é   zb:zf12,f14,f3,f6rô   Zf6)rš   r›   r    r  u     热门题材机会个股: u    个热门题材u%     全市场板块数据获取失败: z1m:0+t:6,m:0+t:80,m:1+t:2,m:1+t:23,m:0+t:81+s:2048z
f3,f2,f292)rö   r÷   rø   rù   rú   rû   rü   rý   é   gÍÌÌÌÌÌì?é   c                 C   s”   | dkrdS | dk r*| dkrdS | dkrdS | dkrdS | dkr"d	S | d
kr(dS dS | dkr0dS | dkr6dS | d	kr<dS | dkrBdS | dkrHdS dS )Nr   r  iùÿÿÿéûÿÿÿrV   éýÿÿÿr"   éþÿÿÿrz   éÿÿÿÿrw   ru   é   ri   é   rt   é	   rj   r   )Zfvr   r   r   Ú_bucket·  s   r  Zf292r	  Ú7)u   ≤-7%z-7~-5%z-5~-3%z-3~-2%z-2~-1%z-1~0%u   平盘z0~1%z1~2%z2~3%z3~5%z5~7%u   ≥7%c                 C   s   g | ]}t | t| d œ‘qS ))r›   Úvalue)Ú_hist_labelsÚ_histr   r   r   r   r   Ú  s    )rÀ   ZupZdownZflatZ	suspendedÚlimit_upÚ
limit_downZhistu     全市场涨跌: 总u    涨u    跌u    平u    停牌u    退市忽略u%     全市场涨跌数据获取失败: éÈ   z5b:MK0024,b:MK0025,b:MK0026,b:MK0027,b:MK0404,b:MK0405zf12,f14,f2,f3,f6,f8,f152)rô   rõ   Zf8Úf2u   黄金ZGOLDZ518u	   黄金ETFu   债u   货币u   短融u   国开u   国债u   政金Z511u   债券/货币ETFu   原油u   豆粕u   有色u   能源u   商品u   白银u   化工u	   商品ETFu   跨境u   纳指u   恒生u   日经u   标普u   德国u   法国u   港股u   中概ZQDIIu   海外Z513Z1596u	   跨境ETFg    ÐcAu‰   日内波段：利用债券ETF低波动特性，在盘中低点买入、高点卖出，日收益0.1-0.3%；适合大资金低风险套利u‰   隔日溢价套利：关注夜间海外市场走势，次日开盘顺势操作；若溢价>2%可做空溢价回落；波动大设2%止损uw   趋势跟踪：跟随国际金价波动，RSI<30低吸、RSI>70高抛；地缘事件驱动时可加大仓位；止损3%uŠ   事件驱动：关注大宗商品价格和库存变化，突破前日高点买入、跌破前日低点止损；趋势行情可留仓过夜)	rš   r›   Zpricer    r  Ú_amount_numZturnoverrU   Zstrategyc                 C   s   |   dd¡S )Nr  r   )Úgetr¥   r   r   r   r§      s    z  T+0 ETF: u    只u     T+0 ETF数据获取失败: )ÚztÚdtÚzbÚokz%Y%m%d))ZgetTopicZTPoolú	fbt%3Aasciô  r  )ZgetTopicDTPoolz
fund%3Aascr  r  )ZgetTopicZBPoolr  i,  r  rj   zhttps://push2ex.eastmoney.com/z;?ut=7eea3edcaed734bea9cbfc24409ed989&dpt=wz.ztzt&Pageindex=z
&pagesize=z&sort=z&date=)r   ZpoolZtcr  u     涨停池: ZT=r  z DT=r  z ZB=r  u     涨停池数据获取失败: r  r  c                 c   s   � | ]	}|d  rdV  qdS )r«   rV   Nr   rO   r   r   r   rQ   V  s   € c                 c   ó    � | ]}|d  r|d V  qdS )r«   rª   Nr   rO   r   r   r   rQ   W  rW   c                 c   r  )r«   r¬   Nr   rO   r   r   r   rQ   X  rW   c                 c   r  )r«   r¯   Nr   rO   r   r   r   rQ   Y  rW   c                 c   ó$   � | ]}|d  rt |d ƒV  qdS )r«   r­   N©r   rO   r   r   r   rQ   Z  r¾   c                 c   r  )r«   r®   Nr  rO   r   r   r   rQ   [  r¾   c                	   C   s   ddg g g g dddœS )Nr   rJ   )Útotal_triggersÚ
total_winsÚtotal_returnsÚpf_listÚexp_listÚwr_filtered_listrU   r»   r   r   r   r   r   r§   ^  rì   Zindicator_statsr  r   Zwinsr!  Ú
avg_returnr"  Úprofit_factorr#  Ú
expectancyr$  Zwr_rsi_filterZwr_adx_filterZwr_vol_filterZwr_all_filterr   ry   gÍÌÌÌÌÌô?rm   é0   rq   rp   )
r›   rU   r»   ÚtriggersÚwin_rater%  r&  r'  r¸   Zqualityc                 C   r¤   )Nr*  r   r¥   r   r   r   r§   |  r¨   Úsignal_qualityc                 c   rL   rM   r   r–   r   r   r   rQ   …  rR   r*  iìÿÿÿc                 C   s(   g | ]}|d  dkr|d dkr|‘qS )r*  ry   r)  rt   r   r   r   r   r   r   “  r�   c                 C   s(   g | ]}|d  dk r|d dkr|‘qS )r*  r{   r)  rt   r   r   r   r   r   r   ”  r�   u   高胜率指标TOP3: c                 c   ó,   � | ]}|d  d t |d ƒ d V  qdS ©r›   ú(r*  z%)N©r#   r   r   r   r   rQ   —  ó   €* u)    — 优先关注含这些指标的个股u   低胜率指标预警: c                 c   r,  r-  r/  r   r   r   r   rQ   ™  r0  u&    — 历史表现不佳，谨慎参考c                 C   s$   g | ]}|d  r|d dkr|‘qS )r«   r+  rv   r   rO   r   r   r   r   š  s   $ u   高信号质量个股(u   只): c                 c   rL   rå   r   rO   r   r   r   rQ   œ  rR   c                 C   r¤   )Nr+  r   r¥   r   r   r   r§   œ  r¨   c                 c   r  )r«   r+  Nr   rO   r   r   r   rQ   �  rW   u    全市场平均信号质量分: u    — u   优质区间u   中等区间u   偏低区间c                 c   ó    � | ]}|d  dv rdV  qdS )r°   )rÊ   u   适度买入u   小仓买入u   轻仓买入u   轻仓关注rV   Nr   rO   r   r   r   rQ   ¡  rW   c                 c   r1  )r°   )u   建议清仓u   建议减仓r|   rV   Nr   rO   r   r   r   rQ   ¢  rW   c                 c   s$   � | ]}|d  dkr|d  V  qdS )r¶   r   Nr   rO   r   r   r   rQ   £  r¾   c                 C   r¤   r¼   r   r¥   r   r   r   r§   «  r¨   Ztrendu   上行u   多头)u   下行u   单边下行u   空头u   震荡c                 c   rL   )r¶   Nr   rO   r   r   r   rQ   ³  rR   c                 c   s,   � | ]}d |d   kodkn  V  qdS )r   r¶   rk   Nr   rO   r   r   r   rQ   ´  r0  c                 c   s<   � | ]}|d  dkr|d  |d   kodkn  V  qdS )r³   r   r´   Nr   rO   r   r   r   rQ   µ  ó   €: c                 c   s<   � | ]}|d  dkr|d |d    kodkn  V  qdS )r³   r   rµ   Nr   rO   r   r   r   rQ   ¶  r2  )Zbuy_signal_pctZsell_signal_pctZ	watch_pctZmax_single_positionZposition_validZentry_stop_validZentry_target_validZsector_concentration_pct)Ztotal_buy_signalsZtotal_sell_signalsZtotal_exposureZavg_positionÚsector_concentrationZmarket_regimeZ
validationÚ	scan_dateÚtotal_scannedÚtotal_with_signalszStocks: z, Sectors: z, Buy: u]× <!DOCTYPE html>
<html lang="zh-CN" data-theme="dark" style="background:#0a0e1a;color:#e8eaf0;">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="color-scheme" content="dark">
<script>document.documentElement.setAttribute('data-theme','dark');document.documentElement.style.background='#0a0e1a';document.documentElement.style.color='#e8eaf0';try{localStorage.setItem('stock-theme','dark');}catch(e){}var __tl=true;var __to=new MutationObserver(function(){if(__tl&&document.documentElement.getAttribute('data-theme')!=='dark')document.documentElement.setAttribute('data-theme','dark');});__to.observe(document.documentElement,{attributes:true,attributeFilter:['data-theme']});setTimeout(function(){__tl=false;},3000);window.addEventListener('pageshow',function(e){if(e.persisted){__tl=true;document.documentElement.setAttribute('data-theme','dark');setTimeout(function(){__tl=false;},3000);}});</script>
<title>A股交易决策看板 — 专业交易员视角</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
:root,[data-theme="dark"]{--bg:#0a0e1a;--bg2:#131829;--bg3:#1d2340;--bg4:#283056;--ink:#e8eaf0;--muted:#7a82a3;--rule:#2a3050;--up:#ef4444;--up-rgb:239,68,68;--up-bg:rgba(239,68,68,0.12);--up-bd:rgba(239,68,68,0.3);--down:#22c55e;--down-rgb:34,197,94;--down-bg:rgba(34,197,94,0.12);--down-bd:rgba(34,197,94,0.3);--gold:#ffc83d;--blue:#4d9fff;--purple:#a855f7;--orange:#ff9500;--radius:14px;--bg-atmo:radial-gradient(1100px 700px at 85% -10%,rgba(77,159,255,.055),transparent 55%),radial-gradient(900px 600px at -5% 15%,rgba(168,85,247,.04),transparent 50%),radial-gradient(800px 500px at 50% 110%,rgba(255,200,61,.03),transparent 50%),var(--bg);--row-alt:rgba(127,137,163,.045);--heatmap-bd:rgba(255,255,255,.09)}
:root,[data-theme="dark"]{--shadow-sm:0 1px 3px rgba(0,0,0,.3),0 1px 2px rgba(0,0,0,.2);--shadow-md:0 4px 12px rgba(0,0,0,.35),0 2px 4px rgba(0,0,0,.2);--shadow-lg:0 12px 32px rgba(0,0,0,.4),0 4px 8px rgba(0,0,0,.2);--grad-card:linear-gradient(135deg,rgba(255,255,255,.03) 0%,rgba(255,255,255,0) 100%);--grad-nav:linear-gradient(180deg,rgba(19,24,41,.95) 0%,rgba(19,24,41,.85) 100%);--font-mono:'SF Mono','JetBrains Mono','Fira Code',ui-monospace,monospace;--font-sans:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'PingFang SC','Microsoft YaHei',sans-serif}
[data-theme="light"]{--bg:#f5f7fa;--bg2:#fff;--bg3:#eef1f6;--bg4:#dde3ed;--ink:#1a1f36;--muted:#6b7280;--rule:#e5e7eb;--up:#dc2626;--up-rgb:220,38,38;--up-bg:rgba(220,38,38,0.08);--up-bd:rgba(220,38,38,0.25);--down:#16a34a;--down-rgb:22,163,74;--down-bg:rgba(22,163,74,0.08);--down-bd:rgba(22,163,74,0.25);--gold:#d97706;--blue:#2563eb;--purple:#7c3aed;--orange:#ea580c;--bg-atmo:radial-gradient(1000px 600px at 85% -10%,rgba(37,99,235,.045),transparent 55%),var(--bg);--row-alt:rgba(26,31,54,.025);--heatmap-bd:rgba(255,255,255,.55)}
[data-theme="eye"]{--bg:#f0f6e8;--bg2:#e8f0dc;--bg3:#d8e5c8;--bg4:#c8dab4;--ink:#2d3b1a;--muted:#6b7d4e;--rule:#b8cca0;--up:#c41e3a;--up-rgb:196,30,58;--up-bg:rgba(196,30,58,0.1);--up-bd:rgba(196,30,58,0.25);--down:#2d8a4e;--down-rgb:45,138,78;--down-bg:rgba(45,138,78,0.1);--down-bd:rgba(45,138,78,0.25);--gold:#b8860b;--blue:#1e5fa8;--purple:#6b3fa0;--orange:#cc6600;--bg-atmo:var(--bg);--row-alt:rgba(45,59,26,.03);--heatmap-bd:rgba(255,255,255,.45)}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:var(--font-sans);background:var(--bg-atmo);color:var(--ink);line-height:1.6;overflow-x:hidden;font-feature-settings:'cv11','ss01';-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--bg4);border-radius:6px;border:2px solid transparent;background-clip:padding-box}
::-webkit-scrollbar-thumb:hover{background:var(--muted);border:2px solid transparent;background-clip:padding-box}
::selection{background:rgba(var(--up-rgb),.25)}
.container{max-width:1400px;margin:0 auto;padding:0 20px}
.top-nav{position:sticky;top:0;z-index:100;background:var(--grad-nav);border-bottom:1px solid var(--rule);padding:10px 0;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);box-shadow:var(--shadow-sm)}
.top-nav-inner{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}
.nav-brand{font-size:18px;font-weight:800;white-space:nowrap;letter-spacing:-.02em;background:linear-gradient(135deg,var(--ink) 0%,var(--up) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
.nav-brand span{color:var(--up);-webkit-text-fill-color:var(--up)}
.nav-links{display:flex;gap:4px;flex-wrap:wrap}
.nav-link{padding:6px 14px;border-radius:8px;font-size:13px;color:var(--muted);cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);text-decoration:none;display:flex;align-items:center;gap:4px;position:relative;overflow:hidden}
.nav-link:hover{background:var(--bg3);color:var(--ink);transform:translateY(-1px)}
.nav-link.active{background:var(--up-bg);color:var(--up);border:1px solid var(--up-bd);box-shadow:0 2px 8px rgba(var(--up-rgb),.15)}
.nav-link .badge-count{background:var(--up);color:#fff;border-radius:10px;padding:0 6px;font-size:10px;font-weight:700;min-width:18px;text-align:center;box-shadow:0 2px 6px rgba(var(--up-rgb),.4)}
.nav-right{display:flex;align-items:center;gap:10px}
.theme-switcher{display:flex;gap:4px}
.theme-btn{width:32px;height:32px;border-radius:8px;border:1px solid var(--rule);background:var(--bg3);cursor:pointer;font-size:14px;transition:all .25s cubic-bezier(.4,0,.2,1)}
.theme-btn:hover{transform:scale(1.1);border-color:var(--muted)}
.theme-btn.active{border-color:var(--up);background:var(--up-bg);box-shadow:0 2px 8px rgba(var(--up-rgb),.2)}
.search-box{display:flex;gap:6px}
.search-box input{background:var(--bg3);border:1px solid var(--rule);color:var(--ink);padding:6px 12px;border-radius:8px;font-size:13px;width:200px;outline:none;transition:all .25s;box-shadow:var(--shadow-sm)}
.search-box input:focus{border-color:var(--up);box-shadow:0 0 0 3px rgba(var(--up-rgb),.1)}
.search-box button{background:linear-gradient(135deg,var(--up),#dc2626);color:#fff;border:none;padding:6px 14px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;transition:all .25s;box-shadow:0 2px 6px rgba(var(--up-rgb),.3)}
.search-box button:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(var(--up-rgb),.4)}
.layer-header{display:flex;align-items:center;gap:12px;margin:32px 0 16px}
.page-section{display:none;animation:fadeIn .35s cubic-bezier(.4,0,.2,1)}
.page-section.active{display:block}
@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
.layer-num{width:36px;height:36px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:800;color:#fff;flex-shrink:0;box-shadow:var(--shadow-md)}
.l1 .layer-num{background:linear-gradient(135deg,var(--blue),var(--purple))}
.l2 .layer-num{background:linear-gradient(135deg,var(--gold),var(--orange))}
.l3 .layer-num{background:linear-gradient(135deg,var(--up),#ff6b6b)}
.l-ref .layer-num{background:var(--bg4)}
.l-watch .layer-num{background:linear-gradient(135deg,var(--gold),var(--up))}
.layer-title{font-size:22px;font-weight:800;letter-spacing:-.02em}
.layer-sub{font-size:13px;color:var(--muted);margin-top:2px;letter-spacing:.01em}
.card{background:var(--bg2);background-image:var(--grad-card);border:1px solid var(--rule);border-radius:var(--radius);padding:20px;margin-bottom:16px;box-shadow:var(--shadow-sm);transition:box-shadow .3s,border-color .3s}
.card:hover{box-shadow:var(--shadow-md),0 0 0 1px rgba(255,200,61,.1);border-color:var(--muted)}
.card-title{font-size:15px;font-weight:700;margin-bottom:14px;display:flex;align-items:center;gap:8px;letter-spacing:-.01em}
.card-title .bar{width:3px;height:16px;background:linear-gradient(180deg,var(--up),#ff6b6b);border-radius:2px}
.metric-info-btn{margin-left:auto;flex-shrink:0;width:20px;height:20px;border-radius:50%;border:1px solid var(--rule);background:var(--bg3);color:var(--muted);font-size:12px;line-height:18px;text-align:center;cursor:pointer;font-weight:700;transition:all .2s;user-select:none}
.metric-info-btn:hover{background:var(--blue);border-color:var(--blue);color:#fff;transform:scale(1.12)}
.mi-row{display:flex;gap:12px;padding:10px 0;border-top:1px solid var(--rule);font-size:12.5px;line-height:1.75;color:var(--ink)}
.mi-row:first-of-type{border-top:none}
.mi-key{flex-shrink:0;min-width:64px;font-weight:700;color:var(--blue)}
.stat-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px}
.stat-item{background:var(--bg3);background-image:var(--grad-card);border-radius:10px;padding:14px;text-align:center;border:1px solid var(--rule);transition:all .25s cubic-bezier(.4,0,.2,1)}
.stat-item:hover{transform:translateY(-2px);border-color:var(--muted);box-shadow:var(--shadow-md)}
.stat-val{font-size:24px;font-weight:800;font-family:var(--font-mono);font-variant-numeric:tabular-nums}
.stat-val.up{color:var(--up)}.stat-val.down{color:var(--down)}.stat-val.gold{color:var(--gold)}.stat-val.blue{color:var(--blue)}
.stat-lbl{font-size:11px;color:var(--muted);margin-top:4px;letter-spacing:.03em;text-transform:uppercase}
.grid-2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
@media(max-width:900px){.grid-2,.grid-3{grid-template-columns:1fr}}
.chart-c{width:100%;height:280px}
.chart-c.tall{height:360px}
table{width:100%;border-collapse:collapse;font-size:13px}
th{background:var(--bg3);padding:8px 10px;text-align:left;font-weight:600;color:var(--muted);font-size:11px;white-space:nowrap;cursor:pointer;letter-spacing:.03em;text-transform:uppercase}
td{padding:7px 10px;border-top:1px solid var(--rule);white-space:nowrap;transition:background .15s}
tbody tr:nth-child(even){background:var(--row-alt)}
tbody tr:hover{background:var(--bg3)}
.pu{color:var(--up);font-weight:600}.pd{color:var(--down);font-weight:600}
.badge{display:inline-block;padding:2px 8px;border-radius:6px;font-size:11px;font-weight:600;letter-spacing:.02em}
.badge.buy{background:var(--up-bg);color:var(--up);border:1px solid var(--up-bd)}
.badge.sell{background:var(--down-bg);color:var(--down);border:1px solid var(--down-bd)}
.badge.watch{background:var(--bg4);color:var(--muted)}
.star-btn{background:none;border:none;cursor:pointer;font-size:16px;color:var(--bg4);transition:all .25s cubic-bezier(.4,0,.2,1);padding:2px}
.star-btn:hover{transform:scale(1.25) rotate(-10deg)}
.star-btn.active{color:var(--gold);text-shadow:0 0 8px rgba(255,200,61,.4)}
.sector-bar-row{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--rule);cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1)}
.sector-bar-row:hover{background:var(--bg3);border-radius:8px;padding-left:8px;box-shadow:var(--shadow-sm)}
.sector-bar-row:last-child{border:none}
.sector-name{width:100px;font-weight:600;font-size:13px;flex-shrink:0}
.sector-bar-wrap{flex:1;height:24px;background:var(--bg3);border-radius:6px;position:relative;overflow:hidden}
.sector-bar-fill{height:100%;border-radius:6px;display:flex;align-items:center;padding:0 8px;font-size:11px;font-weight:600;color:#fff;transition:width .5s cubic-bezier(.4,0,.2,1)}
.sector-bar-fill.bull{background:linear-gradient(90deg,var(--up),rgba(var(--up-rgb),0.7))}
.sector-bar-fill.bear{background:linear-gradient(90deg,var(--down),rgba(var(--down-rgb),0.7))}
.sector-meta{width:140px;text-align:right;font-size:12px;color:var(--muted);flex-shrink:0}
.rotation-insight{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:12px;padding:12px 14px;border-radius:12px;background:linear-gradient(135deg,rgba(168,85,247,.08),rgba(77,159,255,.06));border:1px solid var(--rule)}
.ri-block{flex:1;min-width:170px}
.ri-label{font-size:11px;color:var(--muted);margin-bottom:4px}
.ri-val{font-size:13px;font-weight:600;line-height:1.5}
.ri-dir{flex-basis:100%;font-size:12px;color:var(--muted);line-height:1.6;border-top:1px dashed var(--rule);padding-top:8px}
.rot-tier{margin-bottom:10px}
.rot-tier-head{display:flex;align-items:center;gap:6px;font-size:13px;font-weight:700;margin-bottom:6px}
.rot-dot{width:8px;height:8px;border-radius:50%;display:inline-block}
.rot-tier-body{display:flex;flex-wrap:wrap;gap:6px}
.rot-chip{padding:3px 10px;border-radius:20px;border:1px solid var(--rule);font-size:12px;cursor:pointer;background:var(--bg2);transition:all .2s}
.rot-chip:hover{border-color:var(--blue);transform:translateY(-1px);box-shadow:var(--shadow-sm)}
.sig-card{background:var(--bg3);background-image:var(--grad-card);border-radius:10px;padding:14px;margin-bottom:8px;border-left:3px solid var(--up);transition:all .25s cubic-bezier(.4,0,.2,1)}
.sig-card:hover{transform:translateX(2px);box-shadow:var(--shadow-md)}
.sig-card.sell{border-left-color:var(--down)}
.sig-card-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
.sig-card-name{font-weight:700;font-size:14px;letter-spacing:-.01em}
.sig-card-body{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;font-size:12px}
.sig-card-body .lbl{color:var(--muted);font-size:10px}
.sig-card-body .val{font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums}
.matrix-wrap{overflow-x:auto}
.matrix{width:100%;border-collapse:collapse;font-size:12px;min-width:800px}
.matrix th{background:var(--bg3);padding:6px;text-align:center;font-size:10px;cursor:default;letter-spacing:.03em;text-transform:uppercase}
.matrix td{padding:5px;text-align:center}
.matrix-dot{width:12px;height:12px;border-radius:50%;display:inline-block;cursor:help;transition:transform .2s}
.matrix-dot:hover{transform:scale(1.3)}
.matrix-dot.up{background:var(--up);box-shadow:0 0 8px rgba(var(--up-rgb),0.5)}
.matrix-dot.down{background:var(--down);box-shadow:0 0 8px rgba(var(--down-rgb),0.5)}
.matrix-dot.neutral{background:var(--gold);box-shadow:0 0 8px rgba(255,200,61,0.5)}
.matrix-dot.none{background:var(--bg4)}
.heatmap-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:8px}
.heatmap-cell{border-radius:10px;padding:12px 8px;text-align:center;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1);border:1px solid var(--heatmap-bd);backdrop-filter:blur(2px)}
.heatmap-cell:hover{transform:scale(1.06);box-shadow:var(--shadow-lg);z-index:2;position:relative}
.multi-idx-table{width:100%;border-collapse:collapse;font-size:13px;min-width:680px}
.multi-idx-table th{background:var(--bg3);padding:10px 8px;text-align:center;font-size:11px;color:var(--muted);font-weight:600;border-bottom:1px solid var(--rule)}
.multi-idx-table td{padding:10px 8px;text-align:center;border-bottom:1px solid var(--rule);font-family:'SF Mono',monospace}
.multi-idx-table td.idx-name{font-weight:700;font-family:inherit;text-align:left}
.multi-idx-table tr:hover td{background:var(--bg3)}
.multi-idx-table .trend-tag{display:inline-block;padding:2px 10px;border-radius:10px;font-size:11px;font-weight:600;font-family:inherit}
.multi-idx-table .trend-up{background:rgba(255,82,82,0.15);color:var(--up)}
.multi-idx-table .trend-down{background:rgba(48,217,181,0.15);color:var(--down)}
.multi-idx-table .trend-flat{background:var(--bg4);color:var(--muted)}
.regime-badge{padding:4px 14px;border-radius:12px;font-size:12px;font-weight:700;letter-spacing:1px}
.regime-bull{background:var(--up-bg);border:1px solid var(--up-bd);color:var(--up)}
.regime-bear{background:var(--down-bg);border:1px solid var(--down-bd);color:var(--down)}
.regime-range{background:var(--bg4);border:1px solid var(--rule);color:var(--muted)}
.trade-ready{display:inline-block;padding:2px 8px;border-radius:6px;font-size:10px;font-weight:700;background:var(--up);color:#fff;letter-spacing:1px;animation:pulse 2s infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.6}}
.rr-bar{display:flex;align-items:center;gap:4px;font-size:11px}
.rr-bar .rr-fill{height:6px;border-radius:3px;background:linear-gradient(90deg,var(--down),var(--gold),var(--up))}
.sig-quality-badge{display:inline-block;padding:1px 8px;border-radius:8px;font-size:10px;font-weight:600}
.sig-q-high{background:var(--up-bg);color:var(--up)}
.sig-q-mid{background:rgba(255,200,61,0.12);color:var(--gold)}
.sig-q-low{background:var(--bg4);color:var(--muted)}
.insight-card{padding:10px 14px;background:var(--bg3);border-radius:10px;border-left:3px solid var(--gold);font-size:13px;line-height:1.7}
.insight-card .ic-label{color:var(--muted);font-size:11px;margin-bottom:2px}
.ticker-wrap{overflow:hidden;background:var(--bg2);background-image:var(--grad-card);border:1px solid var(--rule);border-radius:10px;padding:8px 0;margin-bottom:16px;position:relative;box-shadow:var(--shadow-sm)}
.ticker-wrap::before,.ticker-wrap::after{content:'';position:absolute;top:0;bottom:0;width:40px;z-index:2;pointer-events:none}
.ticker-wrap::before{left:0;background:linear-gradient(90deg,var(--bg2) 0%,transparent 100%)}
.ticker-wrap::after{right:0;background:linear-gradient(270deg,var(--bg2) 0%,transparent 100%)}
.ticker-track{display:inline-flex;gap:24px;white-space:nowrap;animation:scroll 60s linear infinite}
@keyframes scroll{0%{transform:translateX(0)}100%{transform:translateX(-50%)}}
.ticker-item{display:inline-flex;align-items:center;gap:5px;font-size:13px;cursor:pointer;transition:color .2s}
.ticker-item:hover{color:var(--gold)}
.overlay{position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:200;display:none;align-items:center;justify-content:center;padding:20px;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px)}
.overlay.active{display:flex;animation:fadeIn .25s ease}
.overlay-panel{background:var(--bg2);background-image:var(--grad-card);border:1px solid var(--rule);border-radius:16px;max-width:960px;width:100%;max-height:88vh;overflow-y:auto;padding:24px;position:relative;box-shadow:var(--shadow-lg);animation:slideUp .3s cubic-bezier(.4,0,.2,1)}
@keyframes slideUp{from{opacity:0;transform:translateY(20px) scale(.98)}to{opacity:1;transform:translateY(0) scale(1)}}
.overlay-close{position:absolute;top:12px;right:12px;width:32px;height:32px;border-radius:8px;border:1px solid var(--rule);background:var(--bg3);color:var(--ink);cursor:pointer;font-size:16px;transition:all .2s}
.overlay-close:hover{background:var(--up-bg);border-color:var(--up-bd);color:var(--up);transform:rotate(90deg)}
.filter-bar{display:flex;gap:10px;align-items:center;margin-bottom:12px;flex-wrap:wrap}
.filter-bar input,.filter-bar select{background:var(--bg3);border:1px solid var(--rule);color:var(--ink);padding:7px 12px;border-radius:8px;font-size:13px;outline:none;transition:border-color .2s;box-shadow:var(--shadow-sm)}
.filter-bar input:focus,.filter-bar select:focus{border-color:var(--up)}
.btn{background:var(--bg3);border:1px solid var(--rule);color:var(--ink);padding:7px 14px;border-radius:8px;cursor:pointer;font-size:13px;transition:all .2s}
.btn:hover{background:var(--bg4);box-shadow:var(--shadow-sm)}
.footer{text-align:center;padding:24px 20px;color:var(--muted);font-size:12px;letter-spacing:.02em}
.disclaimer{background:var(--bg2);background-image:var(--grad-card);border:1px solid var(--rule);border-radius:10px;padding:16px;margin:16px 0;font-size:12px;color:var(--muted);line-height:1.8}
::-webkit-scrollbar{width:8px;height:8px}
::-webkit-scrollbar-track{background:var(--bg)}
::-webkit-scrollbar-thumb{background:var(--bg4);border-radius:4px;transition:background .2s}
::-webkit-scrollbar-thumb:hover{background:var(--muted)}
.live-dot{width:6px;height:6px;border-radius:50%;background:var(--up);display:inline-block;animation:livePulse 1.5s ease-in-out infinite;box-shadow:0 0 6px rgba(var(--up-rgb),.5)}
@keyframes livePulse{0%,100%{opacity:1;box-shadow:0 0 6px rgba(var(--up-rgb),.5)}50%{opacity:.4;box-shadow:0 0 3px rgba(var(--up-rgb),.2)}}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.6}}
/* Detail panel sections */
.dt-section{background:var(--bg3);background-image:var(--grad-card);border-radius:10px;padding:14px;margin-top:12px;border:1px solid var(--rule)}
.dt-section h4{font-size:13px;margin-bottom:10px;display:flex;align-items:center;gap:6px;letter-spacing:-.01em}
.dt-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:8px}
.dt-cell{background:var(--bg2);border:1px solid var(--rule);border-radius:8px;padding:8px;text-align:center;transition:all .2s}
.dt-cell:hover{border-color:var(--muted);box-shadow:var(--shadow-sm)}
.dt-cell .v{font-size:16px;font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums}
.dt-cell .l{font-size:10px;color:var(--muted);margin-top:2px;letter-spacing:.02em}
.sig-timeline{display:flex;flex-direction:column;gap:6px}
.sig-tl-item{display:flex;gap:8px;align-items:center;padding:6px 8px;border-radius:6px;background:var(--bg2);font-size:12px}
.sig-tl-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.concept-tag{background:var(--bg3);border:1px solid var(--rule);padding:3px 9px;border-radius:5px;font-size:12px;color:var(--blue);display:inline-block;margin:2px}
/* Indicator ref with SVG demo */
.ref-card{background:var(--bg3);border-radius:10px;padding:14px;border-left:3px solid var(--up)}
.ref-card.bear{border-left-color:var(--down)}
.ref-card.neutral{border-left-color:var(--gold)}
.ref-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}
.ref-name{font-weight:700;font-size:14px}
.ref-count{font-size:11px;color:var(--muted);background:var(--bg2);padding:2px 8px;border-radius:6px}
.ref-desc{font-size:12px;line-height:1.5;color:var(--muted)}
.ref-meta{display:flex;gap:8px;margin-top:6px;align-items:center}
.ref-svg{margin-top:8px;border-radius:6px;background:var(--bg2);padding:4px}
/* Watchlist analysis cards */
.watch-analysis{background:var(--bg3);background-image:var(--grad-card);border-radius:10px;padding:14px;margin-bottom:10px;border-left:3px solid var(--gold);transition:box-shadow .3s}
.watch-analysis:hover{box-shadow:var(--shadow-md)}
.watch-analysis-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px;margin-top:8px}
.watch-advice{background:var(--bg2);border:1px solid var(--rule);border-radius:8px;padding:10px;margin-top:8px;font-size:12px;line-height:1.6}
/* T+0 Opportunity */
.t0-box{background:linear-gradient(135deg,rgba(255,200,61,.06),rgba(239,68,68,.06));border:1px solid rgba(255,200,61,.3);border-radius:10px;padding:12px;margin-top:10px;box-shadow:var(--shadow-sm)}
.t0-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
.t0-badge{font-size:12px;font-weight:800;padding:3px 10px;border-radius:6px;letter-spacing:.02em}
.t0-badge.buy{background:rgba(239,68,68,.15);color:#ef4444;border:1px solid rgba(239,68,68,.4)}
.t0-badge.sell{background:rgba(34,197,94,.15);color:#22c55e;border:1px solid rgba(34,197,94,.4)}
.t0-badge.wait{background:rgba(125,135,165,.15);color:#9aa4c8;border:1px solid rgba(125,135,165,.35)}
.t0-metrics{display:grid;grid-template-columns:repeat(auto-fill,minmax(90px,1fr));gap:6px;margin-bottom:8px}
.t0-metric{background:var(--bg2);border-radius:7px;padding:6px 8px;text-align:center;transition:all .2s}
.t0-metric:hover{transform:translateY(-1px);border-color:var(--muted)}
.t0-metric .v{font-size:13px;font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums}
.t0-metric .l{font-size:9px;color:var(--muted);margin-top:2px}
.t0-action{background:var(--bg2);border:1px solid var(--rule);border-radius:8px;padding:10px;font-size:12px;line-height:1.7}
.t0-action b{color:var(--ink)}
.t0-points{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;margin-top:8px}
.t0-point{text-align:center;background:var(--bg3);border-radius:6px;padding:6px;transition:all .2s}
.t0-point:hover{transform:translateY(-1px)}
.t0-point .v{font-size:13px;font-weight:700;font-family:var(--font-mono);font-variant-numeric:tabular-nums}
.t0-point .l{font-size:9px;color:var(--muted)}
.t0-chart{width:100%;height:180px;margin-top:8px}
.t0-grade{font-size:11px;font-weight:800;padding:2px 8px;border-radius:5px;letter-spacing:.02em}
.t0-grade.tg-A{background:rgba(239,68,68,.15);color:#ef4444;border:1px solid rgba(239,68,68,.4)}
.t0-grade.tg-B{background:rgba(255,200,61,.15);color:#e0a800;border:1px solid rgba(255,200,61,.4)}
.t0-grade.tg-C{background:rgba(125,135,165,.15);color:#9aa4c8;border:1px solid rgba(125,135,165,.35)}
.t0-score-wrap{margin-bottom:8px}
.t0-scorebar{height:7px;border-radius:4px;background:var(--bg4);overflow:hidden;margin-top:3px}
.t0-scorebar>div{height:100%;border-radius:4px;transition:width .6s cubic-bezier(.4,0,.2,1)}
.t0-factors{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px}
.t0-factor{font-size:10px;padding:1px 6px;border-radius:4px;background:var(--bg2);border:1px solid var(--rule)}
.t0-factor.on{border-color:rgba(239,68,68,.45);color:var(--up)}
.t0-factor.off{color:var(--muted);opacity:.55}
.t0-levels{display:grid;gap:3px;margin-bottom:8px}
.t0-level{display:flex;justify-content:space-between;align-items:center;font-size:12px;padding:3px 8px;background:var(--bg2);border:1px solid var(--rule);border-radius:5px}
.t0-level .nm{color:var(--muted);min-width:48px}
.t0-level .px{font-family:var(--font-mono);font-variant-numeric:tabular-nums}
.t0-level .dv{font-family:var(--font-mono);font-size:10px}
.t0-level.cur{background:linear-gradient(90deg,rgba(255,200,61,.12),rgba(239,68,68,.12));border-color:rgba(255,200,61,.35)}
.t0-batch{font-size:11px;color:var(--muted);margin-top:6px;padding-top:6px;border-top:1px dashed var(--rule)}
.t0-batch b{color:var(--gold)}
.t0-time{font-size:11px;color:var(--blue);margin-top:6px;line-height:1.6}
.t0-time b{color:var(--ink)}
.exec-step{display:flex;gap:8px;align-items:flex-start;font-size:12px;padding:7px 9px;background:var(--bg2);border-radius:7px;margin-top:6px;line-height:1.6}
.es-tag{font-size:10px;font-weight:700;padding:1px 8px;border-radius:4px;flex-shrink:0;min-width:34px;text-align:center}
/* ===== Sentiment Cycle (KPL-style) ===== */
.cycle-header{display:flex;justify-content:space-between;align-items:center;gap:10px}
.cycle-stage{font-size:26px;font-weight:800;padding:4px 16px;border-radius:10px}
.cycle-stage.s-bing{background:rgba(74,144,226,.15);color:#4d9fff;border:1px solid rgba(74,144,226,.4)}
.cycle-stage.s-huinuan{background:rgba(34,197,94,.12);color:#22c55e;border:1px solid rgba(34,197,94,.35)}
.cycle-stage.s-fajiao{background:rgba(255,200,61,.12);color:#ffc83d;border:1px solid rgba(255,200,61,.35)}
.cycle-stage.s-gaochao{background:rgba(239,68,68,.15);color:#ef4444;border:1px solid rgba(239,68,68,.4)}
.cycle-stage.s-fenqi{background:rgba(255,149,0,.15);color:#ff9500;border:1px solid rgba(255,149,0,.4)}
.cycle-stage.s-tuichao{background:rgba(34,197,94,.18);color:#16a34a;border:1px solid rgba(34,197,94,.45)}
.cycle-advice{margin-top:8px;padding:10px 12px;background:var(--bg2);border-radius:8px;font-size:12px;line-height:1.7;color:var(--ink)}
.cycle-metrics{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:10px}
.cycle-metric{background:var(--bg2);border-radius:8px;padding:8px;text-align:center}
.cycle-metric .v{font-size:16px;font-weight:700;font-family:'SF Mono',monospace}
.cycle-metric .l{font-size:10px;color:var(--muted);margin-top:2px}
/* Ladder */
.ladder-row{display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:6px;background:var(--bg2);margin-bottom:5px}
.ladder-lb{min-width:52px;text-align:center;font-weight:800;font-size:13px;border-radius:6px;padding:3px 6px;flex-shrink:0}
.ladder-lb.hi{background:rgba(239,68,68,.18);color:#ef4444}
.ladder-lb.mid{background:rgba(255,149,0,.15);color:#ff9500}
.ladder-lb.lo{background:rgba(125,135,165,.15);color:#9aa4c8}
.ladder-cnt{font-size:11px;color:var(--muted);min-width:44px;flex-shrink:0}
.ladder-stocks{display:flex;flex-wrap:wrap;gap:4px}
.ladder-stock{font-size:11px;padding:2px 8px;border-radius:5px;background:var(--bg3);cursor:pointer;color:var(--ink);border:1px solid transparent}
.ladder-stock:hover{border-color:var(--up)}
.ladder-stock .lb{color:#ef4444;font-weight:700}
/* Playbook */
.playbook-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:12px;margin-bottom:16px}
.pb-card{background:var(--bg2);background-image:var(--grad-card);border:1px solid var(--rule);border-radius:12px;padding:14px;transition:box-shadow .3s}
.pb-card:hover{box-shadow:var(--shadow-md)}
.pb-card h4{font-size:13px;margin-bottom:8px;display:flex;align-items:center;gap:6px;letter-spacing:-.01em}
.pb-row{display:flex;justify-content:space-between;font-size:12px;padding:4px 0;border-bottom:1px dashed var(--rule)}
.pb-row:last-child{border-bottom:none}
.pb-bar{height:8px;border-radius:4px;background:var(--bg4);overflow:hidden;margin-top:8px}
.pb-bar>div{height:100%;border-radius:4px;background:linear-gradient(90deg,#22c55e,#ffc83d,#ef4444)}
.pb-disc{font-size:12px;line-height:1.8;color:var(--muted)}
.pb-disc b{color:var(--ink)}
.pb-hot{display:flex;gap:6px;flex-wrap:wrap;margin-top:6px}
.pb-hot a{font-size:12px;color:var(--blue);cursor:pointer;background:var(--bg3);padding:3px 10px;border-radius:6px;transition:all .2s}
.pb-hot a:hover{background:var(--bg4);transform:translateY(-1px)}
/* AI engine */
.ai-box{background:linear-gradient(135deg,rgba(168,85,247,.08),rgba(77,159,255,.08));border:1px solid rgba(168,85,247,.3);border-radius:10px;padding:14px;margin-top:12px;box-shadow:var(--shadow-sm)}
.ai-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
.ai-verdict{font-size:15px;font-weight:800;padding:4px 14px;border-radius:8px;letter-spacing:.01em}
.ai-verdict.v-strong{background:rgba(239,68,68,.15);color:#ef4444;box-shadow:0 2px 8px rgba(239,68,68,.2)}
.ai-verdict.v-watch{background:rgba(255,200,61,.12);color:#ffc83d;box-shadow:0 2px 8px rgba(255,200,61,.15)}
.ai-verdict.v-wait{background:rgba(125,135,165,.15);color:#9aa4c8}
.ai-verdict.v-avoid{background:rgba(34,197,94,.15);color:#22c55e;box-shadow:0 2px 8px rgba(34,197,94,.15)}
.ai-chain{display:grid;gap:6px}
.ai-step{display:flex;gap:8px;align-items:flex-start;font-size:12px;padding:7px 9px;background:var(--bg2);border-radius:7px;line-height:1.6;transition:all .2s}
.ai-step:hover{background:var(--bg3);transform:translateX(2px)}
.ai-step .k{min-width:64px;font-weight:700;flex-shrink:0}
.ai-step .d{color:var(--muted);flex:1}
.ai-score-bar{display:flex;align-items:center;gap:8px;margin-top:10px}
.ai-score{font-size:24px;font-weight:800;font-family:var(--font-mono);font-variant-numeric:tabular-nums}
/* Confluence grades */
.grade-badge{display:inline-block;padding:2px 9px;border-radius:6px;font-size:11px;font-weight:800;letter-spacing:.02em}
.grade-A{background:rgba(239,68,68,.18);color:#ef4444;border:1px solid rgba(239,68,68,.45);box-shadow:0 2px 6px rgba(239,68,68,.15)}
.grade-B{background:rgba(255,200,61,.12);color:#ffc83d;border:1px solid rgba(255,200,61,.4)}
.grade-C{background:rgba(125,135,165,.12);color:#9aa4c8;border:1px solid rgba(125,135,165,.35)}
.confluence-factors{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}
.cf-tag{font-size:10px;padding:2px 7px;border-radius:4px;background:var(--bg2);border:1px solid var(--rule);transition:all .2s}
.cf-tag.on{border-color:rgba(239,68,68,.5);color:var(--up)}
.cf-tag.off{color:var(--muted);opacity:.6}
.sig-card.grade-C{opacity:.55}
/* Calibration */
.calib-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px;margin-top:8px}
.calib-cell{background:var(--bg2);border-radius:8px;padding:10px;text-align:center;transition:all .2s}
.calib-cell:hover{border-color:var(--muted);box-shadow:var(--shadow-sm)}
.calib-cell .v{font-size:18px;font-weight:800;font-family:var(--font-mono);font-variant-numeric:tabular-nums}
.calib-cell .l{font-size:10px;color:var(--muted);margin-top:3px}
.track-row{display:flex;gap:8px;align-items:center;font-size:12px;padding:5px 8px;background:var(--bg2);border-radius:6px;margin-bottom:4px;transition:background .2s}
.track-row:hover{background:var(--bg3)}
</style>
</head>
<body style="background:#0a0e1a !important;color:#e8eaf0 !important;">

<!-- ===== Top Nav ===== -->
<div class="top-nav">
  <div class="container top-nav-inner">
    <div class="nav-brand">A股<span>交易决策看板</span></div>
    <div class="nav-links">
      <a class="nav-link active" onclick="scrollToSec('layer1',this)">市场全景</a>
      <a class="nav-link" onclick="scrollToSec('layer0',this)">交易信号</a>
      <a class="nav-link" onclick="scrollToSec('layer2',this)">板块轮动</a>
      <a class="nav-link" onclick="scrollToSec('layer3',this)">个股分析</a>
      <a class="nav-link" onclick="scrollToSec('layer4',this)">量化学习</a>
      <a class="nav-link" onclick="scrollToSec('layer-watch',this)">★ 我关注的 <span class="badge-count" id="watchCount">0</span></a>
      <a class="nav-link" onclick="scrollToSec('layer-ref',this)">指标参考</a>
    </div>
    <div class="nav-right">
      <div class="search-box" style="position:relative;">
        <input type="text" id="diagInput" placeholder="输入代码/名称/拼音首字母..." autocomplete="off">
        <button onclick="diagnoseStock()">诊断</button>
        <div id="diagSuggest" style="position:absolute;top:100%;left:0;right:60px;background:var(--bg2);border:1px solid var(--rule);border-radius:0 0 8px 8px;max-height:260px;overflow-y:auto;display:none;z-index:50;box-shadow:0 4px 12px rgba(0,0,0,0.3);"></div>
      </div>
      <div class="theme-switcher">
        <button class="theme-btn active" data-t="dark" title="暗色">🌙</button>
        <button class="theme-btn" data-t="light" title="亮色">☀️</button>
        <button class="theme-btn" data-t="eye" title="护眼">🌿</button>
      </div>
    </div>
  </div>
</div>

<!-- ===== Ticker ===== -->
<div class="container" style="margin-top:12px;"><div class="ticker-wrap"><div class="ticker-track" id="tickerTrack"></div></div></div>

<!-- ===== L0: 交易信号台 ===== -->
<div class="container l0 page-section" id="layer0">
  <div class="layer-header">
    <div class="layer-num" style="background:var(--up);">0</div>
    <div><div class="layer-title">交易信号台</div><div class="layer-sub">实盘操作中枢 · 买入信号 · 风险警示 · 仓位管理 · R/R比</div></div>
    <div style="margin-left:auto;display:flex;gap:12px;align-items:center;">
      <div class="regime-badge" id="regimeBadge">--</div>
      <div style="font-size:12px;color:var(--muted);"><span class="live-dot"></span> <span id="l0Update">--:--</span></div>
    </div>
  </div>
  <div class="card" style="margin-bottom:16px;">
    <div class="card-title"><span class="bar" style="background:var(--purple);"></span>今日操盘指南（AI引擎综合推演 · 实盘操作纪律）<span style="font-size:11px;color:var(--muted);margin-left:8px;">基于情绪周期 × 市场状态 × 信号共振自动生成</span></div>
    <div class="playbook-grid" id="playbookGrid"></div>
  </div>
  <div class="grid-2">
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--up);"></span>买入信号 <span id="buyCount" style="color:var(--up);font-size:13px;">0只</span> <span style="font-size:11px;color:var(--muted);margin-left:8px;">入场/止损/目标/仓位/R-R</span> <label style="margin-left:auto;font-size:11px;color:var(--gold);cursor:pointer;user-select:none;"><input type="checkbox" id="onlyAB" onchange="renderSignalCenter()" style="vertical-align:-2px;margin-right:3px;">仅看A/B级共振</label></div><div id="buyList" style="max-height:520px;overflow-y:auto;"></div></div>
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--down);"></span>风险警示 <span id="sellCount" style="color:var(--down);font-size:13px;">0只</span></div><div id="sellList" style="max-height:520px;overflow-y:auto;"></div></div>
  </div>
  <div class="grid-3" style="margin-top:16px;">
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--gold);"></span>仓位与风险敞口</div><div id="riskPanel" class="chart-c" style="height:200px;"></div></div>
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--blue);"></span>信号质量分布</div><div id="chartSigQual" class="chart-c" style="height:200px;"></div></div>
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--purple);"></span>板块集中度</div><div id="chartSectorConc" class="chart-c" style="height:200px;"></div></div>
  </div>
</div>

<!-- ===== L1: 市场全景 ===== -->
<div class="container l1 page-section active" id="layer1">
  <div class="layer-header">
    <div class="layer-num">1</div>
    <div><div class="layer-title">市场全景</div><div class="layer-sub">大盘趋势 · 市场宽度 · 情绪温度</div></div>
    <div style="margin-left:auto;font-size:12px;color:var(--muted);"><span class="live-dot"></span> 实时更新 <span id="lastUpdate">--:--:--</span></div>
  </div>
  <div class="grid-2">
    <div class="card"><div class="card-title"><span class="bar"></span>多空偏向仪表盘<span style="font-size:10px;color:var(--muted);margin-left:6px;">信号(60%)+大盘(40%)</span><span class="metric-info-btn" onclick="showMetricInfo('gauge')">i</span></div><div id="chartGauge" class="chart-c"></div></div>
    <div class="card"><div class="card-title"><span class="bar"></span>市场宽度分析<span class="metric-info-btn" onclick="showMetricInfo('breadth')">i</span></div><div id="chartBreadth" class="chart-c"></div></div>
  </div>
  <div class="stat-grid" style="margin-top:16px;">
    <div class="stat-item"><div class="stat-val" id="mtIdx">--</div><div class="stat-lbl">上证指数</div></div>
    <div class="stat-item"><div class="stat-val" id="mtChg">--</div><div class="stat-lbl">日涨跌幅(%)</div></div>
    <div class="stat-item"><div class="stat-val gold" id="mtTrend">--</div><div class="stat-lbl">趋势判断</div></div>
    <div class="stat-item"><div class="stat-val blue" id="mtR20">--</div><div class="stat-lbl">20日涨跌(%)</div></div>
    <div class="stat-item"><div class="stat-val" id="mtMA5">--</div><div class="stat-lbl">MA5</div></div>
    <div class="stat-item"><div class="stat-val" id="mtMA20">--</div><div class="stat-lbl">MA20</div></div>
    <div class="stat-item"><div class="stat-val" id="mtMA60">--</div><div class="stat-lbl">MA60</div></div>
    <div class="stat-item"><div class="stat-val gold" id="mtSent">--</div><div class="stat-lbl">多空偏向</div></div>
  </div>
  <div class="grid-2" style="margin-top:16px;">
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--gold);"></span>涨停情绪周期（A股全市场 · 实时涨停数据推演）<span style="font-size:11px;color:var(--muted);margin-left:8px;" id="cycleUpdate"></span><span class="metric-info-btn" onclick="showMetricInfo('emotion')">i</span></div><div id="cycleBox"></div></div>
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--up);"></span>涨停梯队 · 连板天梯（A股全市场 · 点击股票查看）<span style="font-size:11px;color:var(--muted);margin-left:8px;">按连板高度分层</span><span class="metric-info-btn" onclick="showMetricInfo('ladder')">i</span></div><div id="ladderBox" style="max-height:420px;overflow-y:auto;"></div></div>
  </div>
  <div class="grid-3" style="margin-top:16px;">
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--up);"></span>全市场涨跌幅分布<span style="font-size:10px;color:var(--muted);margin-left:6px;">实时全A股涨跌幅区间直方图</span><span class="metric-info-btn" onclick="showMetricInfo('updown')">i</span></div><div id="upDownDist" style="padding:8px 0;min-height:280px;"></div></div>
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--gold);"></span>今天炒什么<span style="font-size:10px;color:var(--muted);margin-left:6px;">全市场热门方向</span><span class="metric-info-btn" onclick="showMetricInfo('todayhot')">i</span></div><div id="todayHot" style="padding:8px 0;max-height:280px;overflow-y:auto;"></div></div>
    <div class="card"><div class="card-title"><span class="bar" style="background:var(--blue);"></span>场内T+0 ETF机会<span style="font-size:10px;color:var(--muted);margin-left:6px;">支持当日回转交易</span><span class="metric-info-btn" onclick="showMetricInfo('t0')">i</span></div><div id="t0List" style="padding:8px 0;max-height:280px;overflow-y:auto;"></div></div>
  </div>
  <div class="grid-3" style="margin-top:16px;">
    <div class="card"><div class="card-title"><span class="bar"></span>信号分布<span class="metric-info-btn" onclick="showMetricInfo('signal')">i</span></div><div id="chartAction" class="chart-c" style="height:240px;"></div></div>
    <div class="card"><div class="card-title"><span class="bar"></span>指标触发频次 Top10 <span style="font-size:10px;color:var(--muted);margin-left:6px;">鼠标悬停查看指标说明</span><span class="metric-info-btn" onclick="showMetricInfo('indtop')">i</span></div><div id="chartInd" class="chart-c" style="height:240px;"></div></div>
    <div class="card"><div class="card-title"><span class="bar"></span>回测胜率统计<span class="metric-info-btn" onclick="showMetricInfo('backtest')">i</span></div><div class="stat-grid" style="grid-template-columns:1fr 1fr;gap:8px;">
      <div class="stat-item"><div class="stat-val blue" id="sWR">--</div><div class="stat-lbl">平均胜率(%)</div></div>
      <div class="stat-item"><div class="stat-val up" id="sWF">--</div><div class="stat-lbl">量化过滤(%)</div></div>
      <div class="stat-item"><div class="stat-val gold" id="sQI">--</div><div class="stat-lbl">胜率提升(pp)</div></div>
      <div class="stat-item"><div class="stat-val" id="sPF">--</div><div class="stat-lbl">平均盈亏比</div></div>
      <div class="stat-item"><div class="stat-val blue" id="sEXP">--</div><div class="stat-lbl">期望收益(%)</div></div>
      <div class="stat-item"><div class="stat-val gold" id="sQS">--</div><div class="stat-lbl">量化评分</div></div>
    </div></div>
  </div>
</div>

<!-- ===== L2: 板块分析 ===== -->
<div class="container l2 page-section" id="layer2">
  <div class="layer-header">
    <div class="layer-num">2</div>
    <div><div class="layer-title">板块轮动</div><div class="layer-sub">幻方量化轮动推演 · 产业链上下游 · 板块强弱排名 · 信号分布 · 热力图</div></div>
  </div>
  <div class="card">
    <div class="card-title"><span class="bar" style="background:var(--purple);"></span>幻方量化 · 板块轮动推演<span style="margin-left:8px;font-size:10px;color:var(--muted);font-weight:400;">多因子轮动模型（动量 28% · 情绪 22% · 信号净流入 16% · 量化评分 20% · 胜率 14%）</span></div>
    <div class="rotation-insight" id="rotationInsight"></div>
    <div id="chartRotation" class="chart-c" style="height:340px;"></div>
    <div id="rotationLadder"></div>
  </div>
  <div class="card" style="margin-top:16px;border-left:3px solid var(--blue);">
    <div class="card-title"><span class="bar" style="background:var(--blue);"></span>A股全市场板块轮动 <span style="font-size:10px;color:var(--muted);margin-left:6px;">东方财富实时板块数据 · 覆盖全部行业板块</span></div>
    <div id="fullMarketRotation" style="max-height:500px;overflow-y:auto;"></div>
  </div>
  <div class="card">
    <div class="card-title"><span class="bar" style="background:var(--blue);"></span>产业链上下游关系<span style="margin-left:8px;font-size:10px;color:var(--muted);font-weight:400;">上游资源 → 中游制造 → 下游消费 → 金融 / 交运服务（点击节点查看板块，支持滚轮缩放/拖拽）</span></div>
    <div class="grid-2">
      <div class="card" style="background:transparent;box-shadow:none;padding:0;"><div id="chartChain" class="chart-c" style="height:520px;"></div></div>
      <div style="display:flex;flex-direction:column;gap:12px;">
        <div style="font-size:12px;color:var(--muted);line-height:1.7;">节点颜色代表板块轮动梯队：<b style="color:var(--up);">红=领跑主线</b>，<b style="color:var(--orange);">橙=接力补涨</b>，<b style="color:var(--muted);">灰=蓄势观察</b>，<b style="color:var(--blue);">蓝=动能衰减</b>，<b style="color:var(--down);">绿=退潮回避</b>。箭头表示产业链上下游的传导关系（上游资源→中游制造→下游消费），用于推演资金在产业链内部的轮动路径。右侧新增<b>金融链</b>（证券·多元金融 → 地产融资传导）与<b>交运链</b>（铁路公路 → 港口 → 航运集疏运）细分，节点悬停可查看细分业务。</div>
        <div class="card"><div class="card-title"><span class="bar" style="background:var(--gold);"></span>轮动推演结论</div><div id="rotationSummary" style="font-size:12px;line-height:1.8;color:var(--muted);"></div></div>
      </div>
    </div>
  </div>
  <div class="card"><div class="card-title"><span class="bar"></span>板块强弱排名（点击进入板块个股）</div><div id="sectorRanking"></div></div>
  <div class="grid-2">
    <div class="card"><div class="card-title"><span class="bar"></span>板块情绪热力图（点击进入）</div><div class="heatmap-grid" id="heatmapGrid"></div></div>
    <div class="card"><div class="card-title"><span class="bar"></span>板块买卖信号分布</div><div id="chartSecSig" class="chart-c"></div></div>
  </div>
  <div class="grid-2">
    <div class="card"><div class="card-title"><span class="bar"></span>热门概念分布（前20）</div><div id="chartConcept" class="chart-c tall"></div></div>
    <div class="card"><div class="card-title"><span class="bar"></span>行业分布统计</div><div id="chartIndustry" class="chart-c tall"></div></div>
  </div>
</div>

<!-- ===== L3: 个股分析 ===== -->
<div class="container l3 page-section" id="layer3">
  <div class="layer-header">
    <div class="layer-num">3</div>
    <div><div class="layer-title">个股分析</div><div class="layer-sub">量化因子 · 信号矩阵 · 个股排行（点击个股查看详情，★添加关注）</div></div>
  </div>
  <div class="card"><div class="card-title"><span class="bar"></span>量化因子雷达分析（Top 3）</div><div class="grid-3" id="radarGrid"></div></div>
  <div class="card"><div class="card-title"><span class="bar"></span>操作指标面板（实时交易参数）</div><div id="opPanel" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;"></div></div>
  <div class="card"><div class="card-title"><span class="bar"></span>信号矩阵（个股×指标交叉验证）</div><div class="matrix-wrap"><table class="matrix" id="sigMatrix"></table></div></div>
  <div class="grid-2">
    <div class="card"><div class="card-title"><span class="bar"></span>个股信号散点图</div><div id="chartScatter" class="chart-c tall"></div></div>
    <div class="card"><div class="card-title"><span class="bar"></span>量化评分 vs 回测胜率</div><div id="chartQScatter" class="chart-c tall"></div></div>
  </div>
  <div class="card">
    <div class="card-title"><span class="bar"></span>个股交易信号排行（Top 50）</div>
    <div class="filter-bar">
      <input type="text" id="searchInput" placeholder="搜索代码/名称...">
      <select id="sortBy">
        <option value="signal_count">按指标数量</option><option value="bt_win_rate">按回测胜率</option>
        <option value="bt_wr_filtered">按量化胜率</option><option value="bt_quant_score">按量化评分</option>
        <option value="bt_pf">按盈亏比</option><option value="position_size">按建议仓位</option>
        <option value="confidence">按置信度</option><option value="sentiment_score">按情绪评分</option>
        <option value="priority">按优先级</option><option value="mkt_cap_yi">按市值</option>
        <option value="pct_change">按涨跌幅</option>
        <option value="signal_quality">按信号质量</option>
      </select>
      <button class="btn" onclick="exportCSV()">导出CSV</button>
      <span style="font-size:12px;color:var(--muted);" id="filterInfo"></span>
    </div>
    <div style="overflow-x:auto;"><table>
      <thead><tr>
        <th title="自选">★</th><th>#</th><th title="股票代码">代码</th><th title="股票名称">名称</th><th class="sortable" data-key="signal_count" title="信号触发总数">指标</th>
        <th title="所属行业板块">行业</th><th title="回测+量化综合信号">信号</th><th class="sortable" data-key="bt_win_rate" title="原始回测胜率">胜率</th>
        <th class="sortable" data-key="bt_wr_filtered" title="量化过滤后胜率">量化胜率</th><th class="sortable" data-key="bt_quant_score" title="多因子综合评分">量化分</th>
        <th class="sortable" data-key="bt_pf" title="盈亏比PF">盈亏比</th><th class="sortable" data-key="position_size" title="建议仓位%">仓位</th>
        <th class="sortable" data-key="confidence" title="信号置信度">置信度</th><th class="sortable" data-key="priority" title="操作优先级">星级</th>
        <th title="风险等级">风险</th><th class="sortable" data-key="sentiment_score" title="多空偏向值(-1~1)">情绪</th>
        <th class="sortable" data-key="signal_quality" title="信号质量SQ评分">信号质量</th>
        <th class="sortable" data-key="mkt_cap_yi" title="总市值(亿元)">市值(亿)</th><th class="sortable" data-key="latest_price" title="最新成交价">最新价</th>
        <th class="sortable" data-key="pct_change" title="当日涨跌幅%">涨跌幅</th>
      </tr></thead>
      <tbody id="stockTableBody"></tbody>
    </table></div>
  </div>
</div>

<!-- ===== L4: 量化学习中心 ===== -->
<div class="container l4 page-section" id="layer4">
  <div class="layer-header">
    <div class="layer-num" style="background:var(--purple);">4</div>
    <div><div class="layer-title">量化学习中心</div><div class="layer-sub">指标历史表现 · 自我进化 · 信号质量分层 · 学习洞察</div></div>
  </div>
  <div class="card" style="margin-bottom:16px;border-left:3px solid var(--purple);">
    <div class="card-title"><span class="bar" style="background:var(--purple);"></span>幻方量化策略看板 <span style="font-size:10px;color:var(--muted);margin-left:6px;">高胜率回测策略 · 可执行交易方案</span></div>
    <div id="quantStrategyPanel" style="display:grid;gap:12px;"></div>
  </div>
  <div class="card" style="margin-bottom:16px;">
    <div class="card-title"><span class="bar" style="background:var(--gold);"></span>学习洞察（基于全市场回测数据自动生成）</div>
    <div id="learningInsights" style="display:grid;gap:8px;"></div>
  </div>
  <div class="card" style="margin-bottom:16px;">
    <div class="card-title"><span class="bar" style="background:var(--purple);"></span>信号追踪与预测校准（自我学习引擎 · 实盘验证）<span style="font-size:11px;color:var(--muted);margin-left:8px;">记录每次A/B级信号 → 实盘兑现 → 校准共振评分权重</span></div>
    <div class="calib-grid" id="calibGrid"></div>
    <div style="margin-top:10px;max-height:260px;overflow-y:auto;" id="trackList"></div>
  </div>
  <div class="grid-2">
    <div class="card"><div class="card-title"><span class="bar"></span>指标历史胜率排行（点击查看详情）</div><div style="overflow-x:auto;max-height:420px;overflow-y:auto;"><table class="multi-idx-table" id="indPerfTable">
      <thead><tr><th title="指标名称">指标</th><th title="看涨/看跌/中性">类型</th><th title="历史触发次数">历史触发</th><th title="原始回测胜率">胜率</th><th title="量化过滤后胜率">过滤胜率</th><th title="盈亏比PF">盈亏比</th><th title="期望收益">期望</th><th title="信号质量评级">质量</th></tr></thead>
      <tbody id="indPerfBody"></tbody>
    </table></div></div>
    <div class="card"><div class="card-title"><span class="bar"></span>指标胜率 vs 盈亏比散点图</div><div id="chartIndPerf" class="chart-c" style="height:420px;"></div></div>
  </div>
</div>

<!-- ===== 我关注的 ===== -->
<div class="container l-watch page-section" id="layer-watch">
  <div class="layer-header">
    <div class="layer-num" style="background:var(--gold);">★</div>
    <div><div class="layer-title">我关注的个股</div><div class="layer-sub">关注列表 · K线走势 · 多维分析 · 实时行情</div></div>
    <div style="margin-left:auto;font-size:12px;color:var(--muted);"><span class="live-dot"></span> 实时更新</div>
  </div>
  <div id="watchPageContent"></div>
</div>

<!-- ===== 指标参考 ===== -->
<div class="container l-ref page-section" id="layer-ref">
  <div class="layer-header">
    <div class="layer-num">i</div>
    <div><div class="layer-title">指标形态参考手册</div><div class="layer-sub">共__UNIQUE_INDICATORS__种指标形态 · 含K线图演示</div></div>
  </div>
  <div class="card"><div id="indicatorRefGrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px;"></div></div>
  <div class="disclaimer"><strong>风险提示：</strong>本报告基于历史K线数据通过算法自动识别技术指标形态，所有交易建议均由程序根据信号组合自动生成，仅供参考学习用途，不构成任何投资建议。技术指标存在滞后性和误判可能，实际投资决策请结合基本面分析、市场环境及自身风险承受能力综合判断。股市有风险，投资需谨慎。</div>
  <div class="footer">数据来源：东方财富/腾讯财经API · 指标体系：《新生300天图解教程》 · 过滤条件：市值≥100亿+排除ST · 生成时间：__SCAN_DATE__</div>
</div>

<!-- ===== Overlays ===== -->
<div class="overlay" id="detailOverlay"><div class="overlay-panel" id="detailPanel"><button class="overlay-close" onclick="closeOverlay('detailOverlay')">&#10005;</button><div id="detailContent"></div></div></div>
<div class="overlay" id="sectorOverlay"><div class="overlay-panel" id="sectorPanel"><button class="overlay-close" onclick="closeOverlay('sectorOverlay')">&#10005;</button><div id="sectorContent"></div></div></div>
<div class="overlay" id="watchOverlay"><div class="overlay-panel" id="watchPanel"><button class="overlay-close" onclick="closeOverlay('watchOverlay')">&#10005;</button><div id="watchContent"></div></div></div>
<div class="overlay" id="infoOverlay"><div class="overlay-panel" style="max-width:560px;"><button class="overlay-close" onclick="closeOverlay('infoOverlay')">&#10005;</button><div id="infoContent"></div></div></div>

<script>
// ===== Data =====
const scanData=__STOCKS_JSON__;
const pinyinMap=__PINYIN_MAP_JSON__;
const indicatorInfo=__INDICATOR_INFO_JSON__;
const indicatorCounts=__INDICATOR_COUNTS_JSON__;
const industryDist=__INDUSTRY_DIST_JSON__;
const conceptDist=__CONCEPT_DIST_JSON__;
const sectorData=__SECTOR_DATA_JSON__;
const rotationData=__ROTATION_DATA_JSON__;
const chainData=__CHAIN_DATA_JSON__;
const rotationInsight=__ROTATION_INSIGHT_JSON__;
const actionDist=__ACTION_DIST_JSON__;
const buySignals=__BUY_SIGNALS_JSON__;
const sellSignals=__SELL_SIGNALS_JSON__;
const marketTrend=__MARKET_TREND_JSON__;
const klineData=__KLINE_DATA_JSON__;
const avgWinRate=__AVG_WIN_RATE__;
const avgWrFiltered=__AVG_WR_FILTERED__;
const avgQuantScore=__AVG_QUANT_SCORE__;
const avgPf=__AVG_PF__;
const avgExp=__AVG_EXP__;
const indicatorPerf=__INDICATOR_PERF_JSON__;
const learningInsights=__LEARNING_INSIGHTS_JSON__;
const riskMetrics=__RISK_METRICS_JSON__;
window._fullMarketSectors=__FULL_MARKET_SECTORS_JSON__;
window._hotSectorStocks=__HOT_SECTOR_STOCKS_JSON__;
window._marketBreadth=__MARKET_BREADTH_JSON__;
window._t0EtfList=__T0_ETF_LIST_JSON__;
window._emoPoolsData=__EMO_POOLS_JSON__;

// ===== Theme =====
let tc={up:'#ef4444',upRgb:'239,68,68',down:'#22c55e',downRgb:'34,197,94',gold:'#ffc83d',blue:'#4d9fff',purple:'#a855f7',orange:'#ff9500',bg:'#0a0e1a',bg2:'#131829',bg3:'#1d2340',bg4:'#283056',ink:'#e8eaf0',muted:'#7a82a3',rule:'#2a3050'};
const themeMap={
  dark:{up:'#ef4444',upRgb:'239,68,68',down:'#22c55e',downRgb:'34,197,94',gold:'#ffc83d',blue:'#4d9fff',purple:'#a855f7',orange:'#ff9500',bg:'#0a0e1a',bg2:'#131829',bg3:'#1d2340',bg4:'#283056',ink:'#e8eaf0',muted:'#7a82a3',rule:'#2a3050'},
  light:{up:'#dc2626',upRgb:'220,38,38',down:'#16a34a',downRgb:'22,163,74',gold:'#d97706',blue:'#2563eb',purple:'#7c3aed',orange:'#ea580c',bg:'#f5f7fa',bg2:'#fff',bg3:'#eef1f6',bg4:'#dde3ed',ink:'#1a1f36',muted:'#6b7280',rule:'#e5e7eb'},
  eye:{up:'#c41e3a',upRgb:'196,30,58',down:'#2d8a4e',downRgb:'45,138,78',gold:'#b8860b',blue:'#1e5fa8',purple:'#6b3fa0',orange:'#cc6600',bg:'#f0f6e8',bg2:'#e8f0dc',bg3:'#d8e5c8',bg4:'#c8dab4',ink:'#2d3b1a',muted:'#6b7d4e',rule:'#b8cca0'}
};
const chartInstances={};
// ===== Unified chart styling helpers =====
function hexA(hex,a){var h=(hex||'#888888').replace('#','');if(h.length===3)h=h[0]+h[0]+h[1]+h[1]+h[2]+h[2];var n=parseInt(h,16);return 'rgba('+((n>>16)&255)+','+((n>>8)&255)+','+(n&255)+','+a+')';}
function vgrad(c,a1,a2){return new echarts.graphic.LinearGradient(0,0,0,1,[{offset:0,color:hexA(c,a1==null?0.9:a1)},{offset:1,color:hexA(c,a2==null?0.22:a2)}]);}
function lgrad(c,a1,a2){return new echarts.graphic.LinearGradient(0,0,1,0,[{offset:0,color:hexA(c,a1==null?0.95:a1)},{offset:1,color:hexA(c,a2==null?0.4:a2)}]);}
function tipXL(extra){return Object.assign({backgroundColor:hexA(tc.bg2,.96),borderColor:tc.rule,borderWidth:1,padding:[10,14],textStyle:{color:tc.ink,fontSize:12},extraCssText:'border-radius:10px;box-shadow:0 10px 30px rgba(0,0,0,.35);backdrop-filter:blur(6px);line-height:1.7;'},extra||{});}
function hbar(c,extra){return Object.assign({type:'bar',barWidth:'62%',itemStyle:{color:lgrad(c),borderRadius:[0,3,3,0],shadowColor:hexA(c,.28),shadowBlur:5},label:{show:true,position:'right',color:tc.muted,fontSize:10,fontFamily:"'SF Mono',monospace"}},extra||{});}
function pieSeries(data,extra){return Object.assign({type:'pie',data:data,itemStyle:{borderRadius:6,borderColor:tc.bg2,borderWidth:2},emphasis:{scale:true,scaleSize:6,itemStyle:{shadowBlur:16,shadowColor:'rgba(0,0,0,.35)'}},animationType:'scale',animationEasing:'cubicOut'},extra||{});}
function switchTheme(t){document.documentElement.setAttribute('data-theme',t);tc={...themeMap[t]};document.querySelectorAll('.theme-btn').forEach(b=>b.classList.toggle('active',b.dataset.t===t));try{localStorage.setItem('stock-theme',t);}catch(e){}reRenderCharts();}
document.querySelectorAll('.theme-btn').forEach(b=>b.addEventListener('click',()=>switchTheme(b.dataset.t)));
try{localStorage.setItem('stock-theme','dark');}catch(e){}
document.documentElement.setAttribute('data-theme','dark');
document.querySelectorAll('.theme-btn').forEach(b=>b.classList.toggle('active',b.dataset.t==='dark'));
function reRenderCharts(){Object.values(chartInstances).forEach(c=>{if(c&&c.dispose)c.dispose()});Object.keys(chartInstances).forEach(k=>delete chartInstances[k]);renderRiskDashboard();renderL1Charts();renderUpDownDist();renderTodayHot();renderT0List();renderL2Charts();renderRotationQuadrant();renderChainGraph();renderFullMarketRotation();renderL3Charts();renderRadarCharts();renderQuantStrategy();renderLearningCenter();renderTicker();renderPlaybook();renderCalibration();renderEmotionCycle();renderLadder();}

function scrollToSec(id,el){
  document.querySelectorAll('.page-section').forEach(s=>s.classList.remove('active'));
  document.getElementById(id).classList.add('active');
  document.querySelectorAll('.nav-link').forEach(l=>l.classList.remove('active'));if(el)el.classList.add('active');
  window.scrollTo(0,0);
  Object.values(chartInstances).forEach(c=>{if(c&&c.dispose)c.dispose()});
  Object.keys(chartInstances).forEach(k=>delete chartInstances[k]);
  var renderMap={
    'layer0':function(){renderRegimeBadge();renderRiskDashboard();renderSignalCenter();renderPlaybook();},
    'layer1':function(){renderMarketTrend();renderL1Charts();renderUpDownDist();renderTodayHot();renderT0List();renderEmotionCycle();renderLadder();},
    'layer2':function(){renderRotationInsight();renderRotationSummary();renderRotationQuadrant();renderRotationLadder();renderChainGraph();renderSectorRanking();renderHeatmap();renderL2Charts();renderFullMarketRotation();},
    'layer3':function(){renderRadarCharts();renderOpPanel();renderSignalMatrix();renderL3Charts();renderTable();},
    'layer4':function(){renderQuantStrategy();renderLearningCenter();renderCalibration();},
    'layer-watch':function(){renderWatchlistPage();},
    'layer-ref':function(){renderIndicatorRef();},
  };
  var fn=renderMap[id];
  if(fn){requestAnimationFrame(function(){requestAnimationFrame(fn);});}
}
function closeOverlay(id){document.getElementById(id).classList.remove('active');}
let _overlayZ=200;
function openOverlay(id){const el=document.getElementById(id);if(!el)return;_overlayZ++;el.style.zIndex=_overlayZ;el.classList.add('active');}
document.querySelectorAll('.overlay').forEach(o=>o.addEventListener('click',e=>{if(e.target===o)o.classList.remove('active')}));

// ===== 指标说明弹窗（各看板指标） =====
const METRIC_INFO={
  'gauge':{title:'多空偏向仪表盘',def:'实时量化全市场多空力量的综合情绪仪表，读数范围 -100 ~ +100。',calc:'信号偏向(60%) + 大盘指数涨跌(40%)：信号偏向 = (看涨家数 - 看跌家数) ÷ 总家数 × 100；指数分 = 上证/深证成指/创业板指/科创50/北证50 五大指数平均涨跌幅 × 10，再映射到仪表盘刻度。',read:'读数 >0 表示多头占优、<0 表示空头占优，绝对值越大倾向越强。配套统计卡同步展示上证指数、20日涨跌、MA5/MA20/MA60 均线以辅助判断趋势方向。'},
  'breadth':{title:'市场宽度分析',def:'通过全市场上涨/下跌家数衡量涨跌参与的广度，判断行情是否健康、能否持续。',calc:'统计全A股当日上涨、下跌、平盘、停牌家数及占比，并结合涨停/跌停家数（优先取实时涨停池）。上涨家数 > 下跌家数 1.5 倍判定为多头主导；反之空头主导；介于其间为多空均衡。',read:'指数上涨而宽度不足（赚指数不赚钱）往往是顶部信号；指数回调但宽度尚可通常只是洗盘。宽度是趋势延续的必要条件。'},
  'emotion':{title:'涨停情绪周期',def:'基于A股全市场实时涨停池/跌停池/炸板池推演的短线情绪温度计（0 - 100 分）。',calc:'评分由涨停家数、连板空间高度、炸板率、连板率、上证涨跌、跌停家数加权合成。阶段依次为 冰点 → 退潮 → 回暖 → 发酵 → 高潮 → 分歧，反映市场赚钱效应的循环节律。',read:'发酵/高潮期顺势做多主线龙头，分歧期只保留核心龙头，退潮/冰点期以现金为王、严控回撤。数据覆盖沪深京全A股市场。'},
  'ladder':{title:'涨停梯队 · 连板天梯',def:'按连板高度对全A股涨停股分层，直接反映资金接力意愿与市场空间高度。',calc:'按 1板/2板/3板… 分组，展示各高度家数与代表个股（按封单资金降序）。连板高度与家数共同体现赚钱效应强弱。',read:'5 板以上（梯队高点）是主线龙头与情绪标杆；高位股集体断板往往预示情绪退潮。数据覆盖A股全市场。'},
  'updown':{title:'全市场涨跌幅分布',def:'全A股当日涨跌幅的 13 档直方图，一眼看清市场涨跌结构。',calc:'将全部个股涨跌幅归入 ≤-7% 至 ≥7% 的 13 个区间并计数，同时统计上涨/下跌/平盘/停牌家数，剔除退市整理股。',read:'右偏（多数上涨）为普涨行情、左偏为普跌。结合涨停/跌停家数可判断情绪极端程度。'},
  'todayhot':{title:'今天炒什么',def:'聚合全市场最强题材方向、最弱板块与量化信号精选的机会个股。',calc:'取全市场涨幅居前板块，匹配其成分机会个股并标注回测胜率与信号质量；同时列出弱势板块与按胜率排序的量化精选信号。',read:'题材与量化共振的个股更具持续性，胜率徽标越高代表历史回测表现越稳。'},
  't0':{title:'场内T+0 ETF机会',def:'支持当日回转交易（当日买入当日可卖出）的ETF，用幻方多因子模型淘日内做T机会。',calc:'覆盖跨境/债券/黄金/货币/商品ETF。五因子评分：振幅空间、方向确定、量能活性、波动质量、位置质量，综合给出 A/B/C 级机会、预计胜率与具体买卖点。',read:'每条机会均给出低吸/高抛价、止损价与操作依据；非交易时段或波动不足时自动降级为观望。'},
  'signal':{title:'信号分布',def:'全市场个股操作信号的分类占比，反映当前整体操作倾向。',calc:'将每只个股按综合评分归入 强烈关注/重点关注/适度关注/观望等待/注意风险/建议规避 六档并计数。',read:'强烈关注占比高说明多头机会多；建议规避占比高说明市场风险加大。'},
  'indtop':{title:'指标触发频次 Top10',def:'全市场个股当日触发最多的技术指标排行。',calc:'统计扫描池中每只个股当日触发的技术指标并累加，取触发次数前10。',read:'高频指标代表当前市场的主流交易逻辑（如 MACD 金叉、放量突破等），单个指标含义可参考「量化学习中心」的指标说明。'},
  'backtest':{title:'回测胜率统计',def:'扫描池量化策略的历史回测表现汇总。',calc:'统计所有具备回测数据个股的平均胜率、量化过滤后胜率、胜率提升、平均盈亏比、期望收益与量化评分。',read:'量化过滤后胜率高于原始胜率说明多因子过滤有效；期望收益为正才值得长期参与。'}
};
function showMetricInfo(key){
  const info=METRIC_INFO[key];if(!info)return;
  let html='<div style="font-size:20px;font-weight:800;margin-bottom:8px;">'+info.title+'</div>';
  html+='<div style="font-size:13px;color:var(--ink);line-height:1.7;margin-bottom:4px;">'+info.def+'</div>';
  html+='<div class="mi-row"><span class="mi-key">计算口径</span><span>'+info.calc+'</span></div>';
  html+='<div class="mi-row"><span class="mi-key">如何解读</span><span>'+info.read+'</span></div>';
  document.getElementById('infoContent').innerHTML=html;
  openOverlay('infoOverlay');
}

// ===== Watchlist =====
let watchList=JSON.parse(localStorage.getItem('stock-watchlist')||'[]');
function toggleWatch(code,e){if(e)e.stopPropagation();const idx=watchList.indexOf(code);if(idx>-1)watchList.splice(idx,1);else watchList.push(code);localStorage.setItem('stock-watchlist',JSON.stringify(watchList));updateWatchCount();renderTable();renderSignalCenter();}
function isWatched(code){return watchList.includes(code);}
function updateWatchCount(){document.getElementById('watchCount').textContent=watchList.length;}
updateWatchCount();

function showWatchlist(el){scrollToSec('layer-watch',el);}
function renderWatchlistPage(){
  const watchedInTop50=scanData.filter(s=>watchList.includes(s.code));
  const watchedExtraCodes=watchList.filter(c=>!scanData.find(s=>s.code===c));
  if(watchedInTop50.length===0&&watchedExtraCodes.length===0){
    document.getElementById('watchPageContent').innerHTML='<h2 style="text-align:center;padding:40px;color:var(--muted);">暂无关注的个股<br><span style="font-size:14px;">点击个股列表中的 ★ 添加关注，或搜索全市场个股添加关注</span></h2>';
    return;
  }
  const watchedCount=watchedInTop50.length+watchedExtraCodes.length;
  let html='<h2 style="margin-bottom:16px;">★ 我关注的个股（'+watchedCount+'只）</h2>';
  // Summary stats (only for Top50 stocks with data)
  const wt=watchedInTop50;
  const avgS=wt.length>0?wt.reduce((s,x)=>s+x.sentiment_score,0)/wt.length:0;
  const avgC=wt.length>0?wt.reduce((s,x)=>s+x.confidence,0)/wt.length:0;
  const avgQ=wt.length>0?wt.reduce((s,x)=>s+(x.bt_has_data?x.bt_quant_score:0),0)/wt.length:0;
  const buys=wt.filter(s=>s.trade_signal==='买入').length;
  const sells=wt.filter(s=>s.trade_signal==='卖出').length;
  html+='<div class="stat-grid" style="margin-bottom:16px;">';
  html+='<div class="stat-item"><div class="stat-val gold">'+watchedCount+'</div><div class="stat-lbl">关注数量</div></div>';
  html+='<div class="stat-item" style="font-size:11px;"><div class="stat-val" style="color:var(--muted);font-size:14px;">Top50:'+wt.length+' 全市场:'+watchedExtraCodes.length+'</div><div class="stat-lbl">来源分布</div></div>';
  html+='<div class="stat-item"><div class="stat-val '+(avgS>=0?'up':'down')+'">'+avgS.toFixed(2)+'</div><div class="stat-lbl">平均情绪</div></div>';
  html+='<div class="stat-item"><div class="stat-val blue">'+Math.round(avgC)+'</div><div class="stat-lbl">平均置信度</div></div>';
  html+='<div class="stat-item"><div class="stat-val gold">'+avgQ.toFixed(0)+'</div><div class="stat-lbl">平均量化分</div></div>';
  html+='<div class="stat-item"><div class="stat-val up">'+buys+'</div><div class="stat-lbl">买入信号</div></div>';
  html+='<div class="stat-item"><div class="stat-val down">'+sells+'</div><div class="stat-lbl">风险警示</div></div>';
  html+='</div>';

  // Individual analysis for each watched stock (Top50)
  wt.forEach(s=>{
    const pc=s.pct_change>=0?'pu':'pd';
    html+='<div class="watch-analysis">';
    html+='<div style="display:flex;justify-content:space-between;align-items:center;cursor:pointer;" onclick="showDetail(\''+s.code+'\')">';
    html+='<div><span style="font-size:18px;font-weight:800;">'+s.name+'</span> <span style="color:var(--muted);font-size:14px;">'+s.code+'</span> <span class="badge '+s.cls_key+'">'+s.classification+'</span></div>';
    html+='<div><span class="'+pc+'" id="wlPrice_'+s.code+'" style="font-size:16px;font-weight:700;">'+s.latest_price.toFixed(2)+'</span> <span class="'+pc+'" id="wlPct_'+s.code+'">'+(s.pct_change>=0?'+':'')+s.pct_change.toFixed(2)+'%</span> <button class="star-btn active" onclick="toggleWatch(\''+s.code+'\');event.stopPropagation();renderWatchlistPage();">★</button></div>';
    html+='</div>';
    // Multi-dimensional analysis grid
    html+='<div class="watch-analysis-grid">';
    // Sentiment dimension
    html+='<div class="dt-cell"><div class="v '+(s.sentiment_score>=0?'up':'down')+'">'+(s.sentiment_score>=0?'+':'')+s.sentiment_score.toFixed(2)+'</div><div class="l">情绪评分</div></div>';
    html+='<div class="dt-cell"><div class="v">'+s.bullish+'/'+s.bearish+'</div><div class="l">看涨/看跌</div></div>';
    html+='<div class="dt-cell"><div class="v">'+s.confidence+'</div><div class="l">置信度</div></div>';
    html+='<div class="dt-cell"><div class="v">'+s.priority+'星</div><div class="l">优先级</div></div>';
    // Quant dimension
    if(s.bt_has_data){
      html+='<div class="dt-cell"><div class="v gold">'+s.bt_quant_score.toFixed(0)+'</div><div class="l">量化评分</div></div>';
      html+='<div class="dt-cell"><div class="v '+(s.bt_win_rate>=50?'up':'down')+'">'+s.bt_win_rate.toFixed(1)+'%</div><div class="l">回测胜率</div></div>';
      html+='<div class="dt-cell"><div class="v '+(s.bt_wr_filtered>=50?'up':'down')+'">'+s.bt_wr_filtered.toFixed(1)+'%</div><div class="l">量化过滤胜率</div></div>';
      html+='<div class="dt-cell"><div class="v">'+s.bt_pf.toFixed(2)+'</div><div class="l">盈亏比</div></div>';
      html+='<div class="dt-cell"><div class="v">'+s.bt_rsi.toFixed(0)+'</div><div class="l">RSI(14)</div></div>';
      html+='<div class="dt-cell"><div class="v">'+s.bt_adx.toFixed(0)+'</div><div class="l">ADX(14)</div></div>';
      html+='<div class="dt-cell"><div class="v">'+s.bt_vol_ratio.toFixed(2)+'</div><div class="l">量比</div></div>';
      html+='<div class="dt-cell"><div class="v">'+(s.bt_bb_pos*100).toFixed(0)+'%</div><div class="l">布林位置</div></div>';
      html+='<div class="dt-cell"><div class="v">'+s.bt_atr_pct.toFixed(2)+'%</div><div class="l">ATR波幅</div></div>';
    }
    html+='</div>';
    // Trading signal
    if(s.trade_signal==='买入'||s.trade_signal==='卖出'){
      html+='<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:8px;font-size:12px;">';
      html+='<div><span style="color:var(--muted);">信号</span> <b class="'+(s.trade_signal==='买入'?'pu':'pd')+'">'+s.trade_signal+'</b></div>';
      if(s.entry_price)html+='<div><span style="color:var(--muted);">入场</span> <b class="pu">'+s.entry_price.toFixed(2)+'</b></div>';
      if(s.stop_loss)html+='<div><span style="color:var(--muted);">止损</span> <b class="pd">'+s.stop_loss.toFixed(2)+'</b></div>';
      if(s.target_price)html+='<div><span style="color:var(--muted);">目标</span> <b style="color:var(--gold);">'+s.target_price.toFixed(2)+'</b></div>';
      html+='</div>';
    }
    // Actionable analysis
    html+='<div class="watch-advice">';
    // Technical analysis
    let techAdvice='';
    if(s.bt_has_data){
      if(s.bt_rsi<30)techAdvice+='RSI='+s.bt_rsi.toFixed(0)+'，处于超卖区间，存在技术性反弹需求；';
      else if(s.bt_rsi>70)techAdvice+='RSI='+s.bt_rsi.toFixed(0)+'，处于超买区间，追高风险加大；';
      else techAdvice+='RSI='+s.bt_rsi.toFixed(0)+'，处于正常区间；';
      if(s.bt_adx>25)techAdvice+='ADX='+s.bt_adx.toFixed(0)+'，趋势明确，适合趋势跟踪策略；';
      else techAdvice+='ADX='+s.bt_adx.toFixed(0)+'，趋势不明朗，适合区间操作；';
      if(s.bt_vol_ratio>1.5)techAdvice+='量比='+s.bt_vol_ratio.toFixed(2)+'，放量明显，资金关注度较高；';
      else if(s.bt_vol_ratio<0.5)techAdvice+='量比='+s.bt_vol_ratio.toFixed(2)+'，缩量明显，市场参与度低；';
      if(s.bt_bb_pos>0.8)techAdvice+='布林位置='+(s.bt_bb_pos*100).toFixed(0)+'%，接近上轨，注意回调风险；';
      else if(s.bt_bb_pos<0.2)techAdvice+='布林位置='+(s.bt_bb_pos*100).toFixed(0)+'%，接近下轨，关注支撑有效性；';
    }
    // Strategy advice
    let stratAdvice='';
    if(s.sentiment_score>=0.5&&s.confidence>=65)stratAdvice='多维度信号共振偏多，建议在突破确认后分批建仓，仓位控制在'+(s.position_size||30)+'%以内，严格止损在'+(s.stop_loss?s.stop_loss.toFixed(2):'入场价-ATR')+'。';
    else if(s.sentiment_score<=-0.3)stratAdvice='信号偏空，建议持仓者注意止损，未持仓者暂时规避，等待情绪反转信号。';
    else if(s.bt_has_data&&s.bt_quant_score>=60)stratAdvice='量化评分较高('+s.bt_quant_score.toFixed(0)+')，但情绪信号不够强烈，建议小仓位试探性参与，确认方向后加仓。';
    else strAdvice=stratAdvice='信号中性，建议持续观察，等待更明确的交易信号。';
    html+='<strong style="color:var(--gold);">技术面分析：</strong>'+techAdvice+'<br>';
    html+='<strong style="color:var(--up);">实战策略：</strong>'+stratAdvice;
    if(s.trade_reason)html+='<br><strong style="color:var(--blue);">信号解读：</strong>'+s.trade_reason;
    html+='</div>';
    // T+0 opportunity container
    html+='<div id="t0Container_'+s.code+'"><div style="text-align:center;padding:20px;color:var(--muted);font-size:12px;">T+0分析加载中...</div></div>';
    // K-line chart
    const kl=klineData[s.code]||{};const klData=kl.kline||[];
    if(klData.length>0)html+='<div id="wlKline_'+s.code+'" style="width:100%;height:440px;margin-top:8px;"></div>';
    html+='</div>';
  });
  // Non-Top50 watched stocks
  if(watchedExtraCodes.length>0){
    html+='<h3 style="margin:20px 0 12px;color:var(--gold);">全市场关注（'+watchedExtraCodes.length+'只）</h3>';
    watchedExtraCodes.forEach(code=>{
      html+='<div class="watch-analysis" id="wlExtra_'+code+'">';
      html+='<div style="display:flex;justify-content:space-between;align-items:center;cursor:pointer;" id="wlHeader_'+code+'" onclick="showDetailEx(\''+code+'\',\''+code+'\','+(code.startsWith('6')?1:0)+')">';
      html+='<div><span style="font-size:18px;font-weight:800;" id="wlName_'+code+'">'+code+'</span> <span class="badge watch" style="font-size:9px;">全市场</span> <span id="wlPrice_'+code+'" style="font-size:16px;font-weight:700;color:var(--muted);">--</span> <span id="wlPct_'+code+'" style="font-size:12px;color:var(--muted);">--</span></div>';
      html+='<button class="star-btn active" onclick="toggleWatch(\''+code+'\');event.stopPropagation();renderWatchlistPage();">★</button>';
      html+='</div>';
      html+='<div id="wlKline_'+code+'" style="width:100%;height:440px;margin-top:8px;display:flex;align-items:center;justify-content:center;color:var(--muted);font-size:13px;">加载K线数据中...</div>';
      html+='<div id="t0Container_'+code+'"><div style="text-align:center;padding:20px;color:var(--muted);font-size:12px;">T+0分析加载中...</div></div>';
      html+='</div>';
    });
  }
  document.getElementById('watchPageContent').innerHTML=html;
  // Render Top50 K-line charts
  wt.forEach(s=>{
    const kl=klineData[s.code]||{};const kline=kl.kline||[];
    if(kline.length>0)setTimeout(function(){renderKlineChart(kline,'wlKline_'+s.code);},100);
  });
  // Fetch fresh K-line for Top50 watched stocks from API (replaces static data)
  wt.forEach(async s=>{
    const prefix=s.code.startsWith('6')?'1':'0';
    const secid=prefix+'.'+s.code;
    try{
      const r=await jsonpRequest('https://push2his.eastmoney.com/api/qt/stock/kline/get?secid='+secid+'&fields1=f1,f2,f3,f4,f5,f6&fields2=f51,f52,f53,f54,f55,f56,f57,f58&klt=101&fqt=1&beg=0&end=20500101&lmt=120',8000);
      if(r&&r.data&&r.data.klines){
        const kline=r.data.klines.map(function(line){const p=line.split(',');return{date:p[0],open:parseFloat(p[1]),close:parseFloat(p[2]),high:parseFloat(p[3]),low:parseFloat(p[4]),volume:parseFloat(p[5]),ma5:0,ma10:0,ma20:0};});
        for(let i=0;i<kline.length;i++){kline[i].ma5=calcMA(kline.slice(Math.max(0,i-4),i+1).map(k=>k.close),5);kline[i].ma10=calcMA(kline.slice(Math.max(0,i-9),i+1).map(k=>k.close),10);kline[i].ma20=calcMA(kline.slice(Math.max(0,i-19),i+1).map(k=>k.close),20);}
        const dom=document.getElementById('wlKline_'+s.code);
        if(dom){const old=echarts.getInstanceByDom(dom);if(old)old.dispose();renderKlineChart(kline,'wlKline_'+s.code);}
      }
    }catch(e){}
  });
  // Fetch and render non-Top50 K-line charts + stock name
  watchedExtraCodes.forEach(async code=>{
    const mkt=code.startsWith('6')?1:0;
    const prefix=mkt===1?'1':'0';
    const secid=prefix+'.'+code;
    // Fetch quote for name + price
    try{
      const q=await jsonpRequest('https://push2.eastmoney.com/api/qt/stock/get?secid='+secid+'&fltt=2&fields=f43,f58,f170',5000);
      if(q&&q.data){const d=q.data;const nm=d.f58||code;const pr=d.f43||0;const pc=d.f170||0;
        const ne=document.getElementById('wlName_'+code);if(ne)ne.textContent=nm;
        const pe=document.getElementById('wlPrice_'+code);if(pe){pe.textContent=pr.toFixed(2);pe.style.color='var(--'+(pc>=0?'up':'down')+')';}
        const pce=document.getElementById('wlPct_'+code);if(pce){pce.textContent=(pc>=0?'+':'')+pc.toFixed(2)+'%';pce.style.color='var(--'+(pc>=0?'up':'down')+')';}
        const hd=document.getElementById('wlHeader_'+code);
        if(hd)hd.setAttribute('onclick','showDetailEx(\''+code+'\',\''+nm+'\','+mkt+')');}
    }catch(e){}
    // Fetch K-line
    try{
      const r=await jsonpRequest('https://push2his.eastmoney.com/api/qt/stock/kline/get?secid='+secid+'&fields1=f1,f2,f3,f4,f5,f6&fields2=f51,f52,f53,f54,f55,f56,f57,f58&klt=101&fqt=1&beg=0&end=20500101&lmt=120',8000);
      if(r&&r.data&&r.data.klines){
        const kline=r.data.klines.map(function(line){const p=line.split(',');return{date:p[0],open:parseFloat(p[1]),close:parseFloat(p[2]),high:parseFloat(p[3]),low:parseFloat(p[4]),volume:parseFloat(p[5]),ma5:0,ma10:0,ma20:0};});
        for(let i=0;i<kline.length;i++){kline[i].ma5=calcMA(kline.slice(Math.max(0,i-4),i+1).map(k=>k.close),5);kline[i].ma10=calcMA(kline.slice(Math.max(0,i-9),i+1).map(k=>k.close),10);kline[i].ma20=calcMA(kline.slice(Math.max(0,i-19),i+1).map(k=>k.close),20);}
        const dom=document.getElementById('wlKline_'+code);
        if(dom)renderKlineChart(kline,'wlKline_'+code);
      }
    }catch(e){const dom=document.getElementById('wlKline_'+code);if(dom)dom.textContent='K线数据加载失败';}
  });
  // Load T+0 opportunity analysis
  setTimeout(loadT0ForWatchlist,500);
}

// ===== T+0 Opportunity Engine =====
let t0Data={};
let t0Intraday={};
async function fetchIntradayData(code){
  const prefix=(code.startsWith('6')||code.startsWith('5')||code.startsWith('9'))?'1':'0';
  const secid=prefix+'.'+code;
  try{
    const r=await jsonpRequest('https://push2delay.eastmoney.com/api/qt/stock/kline/get?secid='+secid+'&fields1=f1,f2,f3,f4,f5,f6&fields2=f51,f52,f53,f54,f55,f56,f57,f58&klt=1&fqt=1&beg=0&end=20500101&lmt=243',8000);
    if(r&&r.data&&r.data.klines){
      return r.data.klines.map(function(line){const p=line.split(',');return{time:p[0],open:parseFloat(p[1]),close:parseFloat(p[2]),high:parseFloat(p[3]),low:parseFloat(p[4]),volume:parseFloat(p[5])};});
    }
  }catch(e){}
  return null;
}
function analyzeT0(code,bars,rtPrice){
  if(!bars||bars.length<10)return null;
  const sd=scanData.find(x=>x.code===code)||{};
  let atrPct=sd.bt_atr_pct||0;
  const dailyVol=sd.bt_vol_ratio||1;
  var sumPV=0,sumVol=0;
  bars.forEach(function(b){sumPV+=b.close*b.volume;sumVol+=b.volume;});
  var vwap=sumVol>0?sumPV/sumVol:bars[bars.length-1].close;
  var closes=bars.map(function(b){return b.close;});
  function ma(arr,n){var a=arr.length>=n?arr.slice(arr.length-n):arr;return a.reduce(function(s,x){return s+x;},0)/a.length;}
  var ma5=ma(closes,5),ma10=ma(closes,10),ma20=ma(closes,20);
  var high=0,low=999999;
  bars.forEach(function(b){if(b.high>high)high=b.high;if(b.low<low)low=b.low;});
  var preClose=bars[0].open;
  var price=rtPrice||bars[bars.length-1].close;
  if(!(atrPct>0)){
    // 无回测ATR时（如ETF），从分时高低点估算日内真实波幅
    var trSum=0,trCnt=0;
    for(var bi=1;bi<bars.length;bi++){
      var bh=bars[bi].high,bl=bars[bi].low,bpc=bars[bi-1].close;
      trSum+=Math.max(bh-bl,Math.abs(bh-bpc),Math.abs(bl-bpc));trCnt++;
    }
    atrPct=trCnt>0?(trSum/trCnt)/(price||1)*100:2;
    if(!(atrPct>0))atrPct=2;
    atrPct=Math.min(atrPct,10);
  }
  var amplitude=preClose>0?(high-low)/preClose*100:0;
  var posInRange=(high-low)>0.0001?(price-low)/(high-low):0.5;
  var priceVsVwapPct=vwap>0?(price-vwap)/vwap*100:0;
  var tail=bars.slice(-10);
  var momentumPct=tail.length>1?(tail[tail.length-1].close-tail[0].close)/tail[0].close*100:0;
  var avgVol=sumVol/bars.length;
  var r15=bars.slice(-15);
  var recentVol=r15.reduce(function(s,b){return s+b.volume;},0)/r15.length;
  var volRatio=avgVol>0?recentVol/avgVol:1;
  var atrAmt=price*atrPct/100;
  var s1=Math.max(low,vwap*0.995);
  var s2=Math.max(low-atrAmt*0.5,preClose-atrAmt);
  var r1=Math.min(high,vwap*1.005);
  var r2=Math.min(high+atrAmt*0.5,preClose+atrAmt);
  if(s1>s2){var t=s1;s1=s2;s2=t;}
  if(r1>r2){var u=r1;r1=r2;r2=u;}
  var support=s1,resistance=r1;
  var trendUp=ma5>=ma10&&ma10>=ma20&&momentumPct>0;
  var trendDn=ma5<=ma10&&ma10<=ma20&&momentumPct<0;
  // ==== 幻方风格多因子评分 0-100 ====
  var factors=[];var score=0;
  function fac(name,on,pts){factors.push({name:name,on:on,pts:pts});if(on)score+=pts;}
  // 因子1 振幅空间
  if(amplitude>=5&&amplitude<=8){fac('振幅空间',true,25);}
  else if(amplitude>=3&&amplitude<5){fac('振幅空间',true,20);}
  else if(amplitude>=2&&amplitude<3){fac('振幅空间',true,12);}
  else if(amplitude>8){fac('振幅过大',true,14);}
  else{fac('振幅不足',false,0);}
  // 因子2 方向确定
  if((trendUp&&priceVsVwapPct>=0)||(trendDn&&priceVsVwapPct<=0)){fac('方向确定',true,22);}
  else if(trendUp||trendDn){fac('方向形成',true,14);}
  else{fac('方向不明',false,0);}
  // 因子3 量能活性
  if(volRatio>=1.2&&volRatio<=3){fac('量能活性',true,18);}
  else if(volRatio>=0.8&&volRatio<1.2){fac('量能温和',true,10);}
  else if(volRatio>3){fac('量能过热',true,8);}
  else{fac('量能枯竭',false,0);}
  // 因子4 波动质量
  if(atrPct>=2&&atrPct<=4){fac('波动适配',true,15);}
  else if(atrPct>=1.5&&atrPct<2){fac('波动偏低',true,9);}
  else if(atrPct>4&&atrPct<=6){fac('波动偏高',true,9);}
  else{fac('波动极端',false,3);}
  // 因子5 位置质量
  if((trendUp&&posInRange<=0.45)||(trendDn&&posInRange>=0.55)){fac('位置占优',true,15);}
  else if(posInRange>=0.3&&posInRange<=0.7){fac('位置中性',true,8);}
  else{fac('位置不佳',false,3);}
  var grade=score>=70?'A':(score>=50?'B':'C');
  // ==== 方向与操作指令 ====
  var signal,badge,action,entry=null,target=null,stop=null,direction='观望';
  var batches='',timePlan='';
  if(amplitude<2){
    signal='不合做T';badge='wait';direction='观望';
    action='今日振幅仅'+amplitude.toFixed(2)+'%，波动空间不足，不适合日内回转。建议观望，等待放量扩大振幅后再做T。';
    entry=s1;target=r1;stop=s2;
  }else if(trendUp&&posInRange<=0.55){
    signal='顺向做T';badge='buy';direction='顺向';
    entry=Math.max(s1,vwap*0.997);
    target=Math.min(r1,preClose*1.01);
    stop=Math.min(s2,entry-atrAmt*0.8);
    action='分时多头排列、价格贴近支撑，适合<b>顺向做T（先买后卖）</b>：在 '+entry.toFixed(2)+' 附近分批低吸，反弹至 '+target.toFixed(2)+' 附近卖出等量底仓摊低成本。';
    batches='两档低吸：首档现价下方 '+(price*0.998).toFixed(2)+'，回踩 '+(price*0.995).toFixed(2)+' 补第二档。';
    timePlan='<b>开盘</b>观察VWAP方向；<b>盘中</b>回踩不破MA10可低吸；<b>尾盘</b>若未反弹则当日止损，不隔夜加码。';
  }else if(trendDn&&posInRange>=0.45){
    signal='逆向做T';badge='sell';direction='逆向';
    entry=Math.min(r1,vwap*1.003);
    target=Math.max(s1,preClose*0.99);
    stop=Math.max(r2,entry+atrAmt*0.8);
    action='分时空头排列、价格贴近阻力，适合<b>逆向做T（先卖后买）</b>：在 '+entry.toFixed(2)+' 附近高抛底仓，回落至 '+target.toFixed(2)+' 附近买回赚取差价。';
    batches='两档高抛：首档现价上方 '+(price*1.002).toFixed(2)+'，冲高 '+(price*1.005).toFixed(2)+' 抛第二档。';
    timePlan='<b>开盘</b>冲高不追；<b>盘中</b>反弹至VWAP上方可高抛；<b>尾盘</b>回落至支撑再考虑回补。';
  }else if(amplitude>=3){
    signal='网格做T';badge='buy';direction='网格';
    var gs=Math.max(price*0.005,atrAmt*0.4);
    var gridLower=s2,gridUpper=r2;
    var gridCount=Math.floor((gridUpper-gridLower)/gs);
    gridCount=Math.max(3,Math.min(gridCount,10));
    entry=gridLower;target=gridUpper;stop=Math.max(s2*0.99,gridLower-gs);
    action='分时处于区间震荡，适合<b>网格做T</b>：在 ['+gridLower.toFixed(2)+','+gridUpper.toFixed(2)+'] 区间按约 '+gs.toFixed(2)+' 元/格（'+(gs/price*100).toFixed(1)+'%）高抛低吸，共约 '+gridCount+' 格。';
    batches='网格每格仓位≈底仓的'+(100/gridCount).toFixed(0)+'%，单格止损 '+Math.max(gs,price*0.008).toFixed(2)+' 元。';
    timePlan='<b>开盘</b>确定高低点；<b>盘中</b>区间上沿卖出、下沿买回；<b>尾盘</b>回收全部网格仓位。';
  }else{
    signal='观望';badge='wait';direction='观望';
    action='振幅'+amplitude.toFixed(2)+'%、方向与量能均不足以支撑日内回转，建议观望。支撑 '+s1.toFixed(2)+'，阻力 '+r1.toFixed(2)+'。';
    entry=s1;target=r1;stop=s2;
  }
  // ==== 幻方多因子 → 日内回转胜率概率估计 ====
  var winProb=50+(score-50)*0.55;
  if(badge==='buy'||badge==='sell')winProb+=4;
  if(amplitude>=3&&amplitude<=6)winProb+=4;
  else if(amplitude<2)winProb-=10;
  else if(amplitude>8)winProb-=4;
  if(direction==='观望'||badge==='wait')winProb=Math.min(winProb,45);
  winProb=Math.max(28,Math.min(82,Math.round(winProb)));
  var revExp=0,riskPct=0;
  if(entry&&target&&stop){
    if(badge==='buy'){revExp=(target-entry)/entry*100;riskPct=(entry-stop)/entry*100;}
    else if(badge==='sell'){revExp=(entry-target)/entry*100;riskPct=(stop-entry)/entry*100;}
    else{revExp=(target-entry)/entry*100;riskPct=0;}
  }
  return{signal:signal,badge:badge,action:action,direction:direction,score:score,grade:grade,winProb:winProb,factors:factors,
    vwap:vwap,amplitude:amplitude,momentumPct:momentumPct,volRatio:volRatio,atrPct:atrPct,
    support:support,resistance:resistance,s1:s1,s2:s2,r1:r1,r2:r2,
    preClose:preClose,high:high,low:low,price:price,priceVsVwapPct:priceVsVwapPct,
    entry:entry,target:target,stop:stop,batches:batches,timePlan:timePlan,revExp:revExp,riskPct:riskPct,
    dailyVol:dailyVol,bars:bars};
}
function renderT0Box(code,t0,prefix){
  if(!t0)return'';
  var pid=(prefix||'t0Chart_')+code;
  var scoreCol=t0.score>=70?'var(--up)':(t0.score>=50?'var(--gold)':'var(--muted)');
  var wp=t0.winProb||0;
  var wpCol=wp>=60?'var(--up)':(wp>=50?'var(--gold)':'var(--muted)');
  var h='<div class="t0-box">';
  h+='<div class="t0-head"><div><b style="color:var(--gold);">T+0回转作战</b> <span style="font-size:10px;color:var(--muted);">幻方多因子·日内回转引擎</span></div><div style="display:flex;gap:6px;align-items:center;"><span class="t0-grade tg-'+t0.grade+'">'+t0.grade+'级机会</span><span class="t0-badge '+t0.badge+'">'+t0.signal+'</span><span style="font-size:11px;font-weight:800;color:'+wpCol+';">胜率 '+wp+'%</span></div></div>';
  h+='<div class="t0-score-wrap"><div style="display:flex;justify-content:space-between;font-size:10px;color:var(--muted);"><span>做T综合评分</span><span style="font-weight:800;color:'+scoreCol+';">'+t0.score+'分</span></div><div class="t0-scorebar"><div style="width:'+t0.score+'%;background:linear-gradient(90deg,var(--down),var(--gold),var(--up));"></div></div></div>';
  h+='<div class="t0-factors">'+(t0.factors||[]).map(function(f){return '<span class="t0-factor '+(f.on?'on':'off')+'">'+f.name+(f.on?' +'+f.pts:'')+'</span>';}).join('')+'</div>';
  var cur=t0.price;
  h+='<div class="t0-levels">';
  h+='<div class="t0-level"><span class="nm">阻力2</span><span class="px" style="color:var(--up);">'+t0.r2.toFixed(2)+'</span><span class="dv" style="color:var(--up);">+'+(cur>0?((t0.r2-cur)/cur*100):0).toFixed(1)+'%</span></div>';
  h+='<div class="t0-level"><span class="nm">阻力1</span><span class="px" style="color:var(--up);">'+t0.r1.toFixed(2)+'</span><span class="dv" style="color:var(--up);">+'+(cur>0?((t0.r1-cur)/cur*100):0).toFixed(1)+'%</span></div>';
  h+='<div class="t0-level cur"><span class="nm">现价</span><span class="px" style="color:var(--ink);font-weight:800;">'+cur.toFixed(2)+'</span><span class="dv" style="color:var(--muted);">VWAP '+t0.vwap.toFixed(2)+'</span></div>';
  h+='<div class="t0-level"><span class="nm">支撑1</span><span class="px" style="color:var(--down);">'+t0.s1.toFixed(2)+'</span><span class="dv" style="color:var(--down);">'+(cur>0?((t0.s1-cur)/cur*100):0).toFixed(1)+'%</span></div>';
  h+='<div class="t0-level"><span class="nm">支撑2</span><span class="px" style="color:var(--down);">'+t0.s2.toFixed(2)+'</span><span class="dv" style="color:var(--down);">'+(cur>0?((t0.s2-cur)/cur*100):0).toFixed(1)+'%</span></div>';
  h+='</div>';
  h+='<div class="t0-metrics">';
  h+='<div class="t0-metric"><div class="v '+(t0.priceVsVwapPct>=0?'pu':'pd')+'">'+(t0.priceVsVwapPct>=0?'+':'')+t0.priceVsVwapPct.toFixed(2)+'%</div><div class="l">偏离VWAP</div></div>';
  h+='<div class="t0-metric"><div class="v" style="color:var(--gold);">'+t0.vwap.toFixed(2)+'</div><div class="l">VWAP</div></div>';
  h+='<div class="t0-metric"><div class="v">'+t0.amplitude.toFixed(2)+'%</div><div class="l">振幅</div></div>';
  h+='<div class="t0-metric"><div class="v '+(t0.momentumPct>=0?'pu':'pd')+'">'+(t0.momentumPct>=0?'+':'')+t0.momentumPct.toFixed(2)+'%</div><div class="l">动量</div></div>';
  h+='<div class="t0-metric"><div class="v '+(t0.volRatio>=1?'pu':'pd')+'">'+t0.volRatio.toFixed(2)+'</div><div class="l">分时量比</div></div>';
  h+='<div class="t0-metric"><div class="v">'+t0.atrPct.toFixed(1)+'%</div><div class="l">ATR波幅</div></div>';
  h+='</div>';
  h+='<div class="t0-action">'+t0.action;
  if(t0.entry&&t0.target&&t0.stop){
    var lb=t0.badge==='sell';
    h+='<div class="t0-points">';
    h+='<div class="t0-point"><div class="v '+(lb?'pd':'pu')+'">'+t0.entry.toFixed(2)+'</div><div class="l">'+(lb?'高抛价':'低吸价')+'</div></div>';
    h+='<div class="t0-point"><div class="v '+(lb?'pd':'pu')+'">'+t0.target.toFixed(2)+'</div><div class="l">'+(lb?'低吸价':'高抛价')+'</div></div>';
    h+='<div class="t0-point"><div class="v pd">'+t0.stop.toFixed(2)+'</div><div class="l">止损</div></div>';
    h+='<div class="t0-point"><div class="v" style="color:var(--gold);">'+(t0.revExp>=0?'+':'')+t0.revExp.toFixed(2)+'%</div><div class="l">做T空间</div></div>';
    h+='</div>';
  }
  if(t0.batches)h+='<div class="t0-batch"><b>分批：</b>'+t0.batches+'</div>';
  h+='<div class="t0-time">'+t0.timePlan+'</div>';
  h+='</div>';
  h+='<div id="'+pid+'" class="t0-chart"></div>';
  h+='</div>';
  return h;
}
function renderT0Chart(code,t0,prefix){
  if(!t0||!t0.bars)return;
  var pid=(prefix||'t0Chart_')+code;
  var dom=document.getElementById(pid);
  if(!dom)return;
  var old=echarts.getInstanceByDom(dom);if(old)old.dispose();
  var data=t0.bars;
  var vwapLine=[];
  var sumPV=0,sumVol=0;
  data.forEach(function(b,i){
    sumPV+=b.close*b.volume;sumVol+=b.volume;
    vwapLine.push(sumVol>0?[i,sumPV/sumVol]:[i,b.close]);
  });
  var priceLine=data.map(function(b,i){return[i,b.close];});
  var g=echarts.init(dom);
  chartInstances[pid]=g;
  g.setOption({
    backgroundColor:'transparent',
    tooltip:tipXL({trigger:'axis'}),
    grid:{left:50,right:10,top:10,bottom:20},
    xAxis:{type:'value',show:false},
    yAxis:{type:'value',scale:true,axisLabel:{color:tc.muted,fontSize:10,fontFamily:"'SF Mono',monospace"},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},
    series:[
      {name:'价格',type:'line',data:priceLine,showSymbol:false,lineStyle:{color:tc.up,width:1.5},areaStyle:{color:vgrad(tc.up,.22,.02)},emphasis:{focus:'series',lineStyle:{width:2.5}}},
      {name:'VWAP',type:'line',data:vwapLine,showSymbol:false,lineStyle:{color:tc.gold,width:1.5,type:'dashed'},emphasis:{focus:'series',lineStyle:{width:2.5}}}
    ]
  });
}
async function loadT0ForWatchlist(){
  var codes=watchList.slice();
  for(var i=0;i<codes.length;i++){
    var code=codes[i];
    var bars=await fetchIntradayData(code);
    if(bars&&bars.length>10){
      t0Intraday[code]=bars;
      var rt=realtimeData[code];
      var rtPrice=rt?rt.price:bars[bars.length-1].close;
      t0Data[code]=analyzeT0(code,bars,rtPrice);
      var el=document.getElementById('t0Container_'+code);
      if(el&&t0Data[code]){
        el.innerHTML=renderT0Box(code,t0Data[code]);
        renderT0Chart(code,t0Data[code]);
      }
    }
  }
}
function refreshT0Prices(){
  Object.keys(t0Data).forEach(function(code){
    var rt=realtimeData[code];if(!rt)return;
    var bars=t0Intraday[code];if(!bars)return;
    var t0=analyzeT0(code,bars,rt.price);
    if(t0){
      t0Data[code]=t0;
      var el=document.getElementById('t0Container_'+code);
      if(el){el.innerHTML=renderT0Box(code,t0);renderT0Chart(code,t0);}
    }
  });
}
async function loadT0ForDetail(code){
  var box=document.getElementById('t0DetailBox');
  if(!box)return;
  box.innerHTML='<div style="text-align:center;padding:16px;color:var(--muted);font-size:12px;">T+0日内回转分析加载中...</div>';
  var bars=t0Intraday[code]||null;
  if(!bars){bars=await fetchIntradayData(code);if(bars&&bars.length>10)t0Intraday[code]=bars;}
  var rt=realtimeData[code];var rtPrice=rt?rt.price:null;
  if(!bars||bars.length<10){box.innerHTML='<div style="text-align:center;padding:16px;color:var(--muted);font-size:12px;">分时数据不足，无法生成T+0建议（非交易时段或数据源受限）</div>';return;}
  var t0=analyzeT0(code,bars,rtPrice);
  if(t0){box.innerHTML=renderT0Box(code,t0,'t0DetailChart_');renderT0Chart(code,t0,'t0DetailChart_');}
  else box.innerHTML='<div style="text-align:center;padding:16px;color:var(--muted);font-size:12px;">分时数据不足，无法生成T+0建议</div>';
}
function execPlanHtml(s){
  if(!s.entry_price||!s.stop_loss||!s.target_price)return'';
  var e=s.entry_price,sl=s.stop_loss,tg=s.target_price;
  var riskAmt=e-sl;
  var l1=(e+sl)/2;
  var l2=sl+riskAmt*0.3;
  var t1=e+riskAmt*1.0;
  var t2=Math.min(tg,e+riskAmt*2.0);
  var tr1=Math.max(e,e+riskAmt*0.5);
  var h='<div class="dt-section" style="border-left:3px solid var(--gold);"><h4><span style="width:3px;height:14px;background:var(--gold);border-radius:2px;display:inline-block;"></span>实操执行计划（交易员视角）</h4>';
  h+='<div class="exec-step"><span class="es-tag" style="background:var(--up-bg);color:var(--up);">建仓</span><span>三档分批：'+e.toFixed(2)+' 建30% → '+l1.toFixed(2)+' 补30% → '+l2.toFixed(2)+' 补40%。不追高、不满仓。</span></div>';
  h+='<div class="exec-step"><span class="es-tag" style="background:var(--down-bg);color:var(--down);">止损</span><span>跌破 '+sl.toFixed(2)+' 无条件离场，单笔亏损≤总资金2%。若回踩未破 '+tr1.toFixed(2)+' 可持有。</span></div>';
  h+='<div class="exec-step"><span class="es-tag" style="background:rgba(255,200,61,.12);color:var(--gold);">止盈</span><span>第一目标 '+t1.toFixed(2)+' 减半仓，第二目标 '+t2.toFixed(2)+' 清仓。跌破 '+tr1.toFixed(2)+' 后止损上移至成本价。</span></div>';
  h+='</div>';
  return h;
}

// ===== L1: Macro =====
function renderMarketTrend(){
  const mt=marketTrend||{};const idx=mt.index_close||0,chg=mt.index_change||0,r20=mt.ret_20d||0;
  const el=v=>document.getElementById(v);
  el('mtIdx').textContent=idx.toFixed(2);el('mtIdx').className='stat-val '+(chg>=0?'up':'down');
  el('mtChg').textContent=(chg>=0?'+':'')+chg.toFixed(2);el('mtChg').className='stat-val '+(chg>=0?'up':'down');
  el('mtTrend').textContent=mt.trend||'--';el('mtR20').textContent=(r20>=0?'+':'')+r20.toFixed(2);
  el('mtR20').className='stat-val '+(r20>=0?'up':'down');el('mtMA5').textContent=(mt.ma5||0).toFixed(2);
  el('mtMA20').textContent=(mt.ma20||0).toFixed(2);el('mtMA60').textContent=(mt.ma60||0).toFixed(2);
  const total=scanData.length;
	// 股票级统计: bullish>bearish 的股票数 vs bearish>bullish 的股票数
	const bullStocks=scanData.filter(s=>s.bullish>s.bearish).length;
	const bearStocks=scanData.filter(s=>s.bearish>s.bullish).length;
	// 信号偏向(股票级): (bullStocks-bearStocks)/total*100,含中性股票,不放大偏差
	const sigBias=((bullStocks-bearStocks)/total*100).toFixed(1);
	// 大盘指数加权: 取5大指数平均涨跌幅×10 作为市场分
	const ix=marketTrend&&marketTrend.indices?marketTrend.indices:[];var idxAvg=0;
	if(ix.length>0){for(var i=0;i<ix.length;i++)idxAvg+=(ix[i].index_change||0);idxAvg=idxAvg/ix.length;}
	// 综合情绪 = 信号偏向(60%) + 大盘指数(40%),指数涨跌幅按×10映射到仪表盘尺度
	const mktScore=Math.round(parseFloat(sigBias)*0.6+idxAvg*10*0.4);
	el('mtSent').textContent=(mktScore>=0?'+':'')+mktScore+'%';el('mtSent').className='stat-val '+(mktScore>=0?'up':'down');
	el('sWR').textContent=avgWinRate;el('sWF').textContent=avgWrFiltered;
  el('sQI').textContent=(avgWrFiltered-avgWinRate).toFixed(1);el('sPF').textContent=avgPf;
  el('sEXP').textContent=avgExp;el('sQS').textContent=avgQuantScore;
}
function renderMultiIndices(){
  const mt=marketTrend||{};const indices=mt.indices||[];const body=document.getElementById('multiIdxBody');
  if(!body)return;
  if(!indices.length){body.innerHTML='<tr><td colspan="8" style="color:var(--muted);">暂无多指数数据</td></tr>';return;}
  const cls=v=>v>0?'up':(v<0?'down':'');
  const sign=v=>(v>0?'+':'')+v.toFixed(2);
  const trendTag=t=>{const m={'上行':'trend-up','单边下行':'trend-down','下行':'trend-down','震荡':'trend-flat'};return '<span class="trend-tag '+(m[t]||'trend-flat')+'">'+t+'</span>';};
  body.innerHTML=indices.map(it=>{
    if(it.trend==='数据不足'){return '<tr><td class="idx-name">'+it.name+'</td><td colspan="7" style="color:var(--muted);">数据不足</td></tr>';}
    const chg=it.index_change||0,r20=it.ret_20d||0;
    return '<tr><td class="idx-name">'+it.name+'</td><td class="'+(chg>=0?'up':'down')+'">'+(it.index_close||0).toFixed(2)+'</td><td class="'+cls(chg)+'">'+sign(chg)+'</td><td class="'+cls(r20)+'">'+sign(r20)+'</td><td>'+(it.ma5||0).toFixed(2)+'</td><td>'+(it.ma20||0).toFixed(2)+'</td><td>'+(it.ma60||0).toFixed(2)+'</td><td>'+trendTag(it.trend||'震荡')+'</td></tr>';
  }).join('');
}
function renderL1Charts(){
  const g=echarts.init(document.getElementById('chartGauge'));chartInstances.gauge=g;
  const bullStocks=scanData.filter(s=>s.bullish>s.bearish).length;
  const bearStocks=scanData.filter(s=>s.bearish>s.bullish).length;
  const total=scanData.length;
  const sigBias=((bullStocks-bearStocks)/total*100);
  const ix=marketTrend&&marketTrend.indices?marketTrend.indices:[];var idxAvg=0;
  if(ix.length>0){for(var i=0;i<ix.length;i++)idxAvg+=(ix[i].index_change||0);idxAvg=idxAvg/ix.length;}
  const sv=Math.round(sigBias*0.6+idxAvg*10*0.4);
  g.setOption({backgroundColor:'transparent',series:[{type:'gauge',min:-100,max:100,startAngle:205,endAngle:-25,axisLine:{lineStyle:{width:18,color:[[-0.5,tc.down],[-0.2,tc.gold],[0.2,'#888888'],[0.5,tc.gold],[1,tc.up]]}},pointer:{length:'58%',width:4,itemStyle:{color:tc.ink}},anchor:{show:true,size:10,itemStyle:{color:tc.bg4,borderColor:tc.muted,borderWidth:2}},axisTick:{distance:-16,length:4,lineStyle:{color:tc.muted,width:1}},splitLine:{distance:-16,length:14,lineStyle:{color:tc.muted,width:1}},axisLabel:{color:tc.muted,distance:-28,fontSize:9,formatter:v=>v===0?'0':(v>0?'+'+v:''+v)},detail:{valueAnimation:true,color:sv>0?tc.up:(sv<0?tc.down:tc.ink),fontSize:30,fontWeight:700,fontFamily:"'SF Mono','JetBrains Mono',monospace",offsetCenter:[0,'35%'],formatter:v=>(v>0?'+':'')+v+'%'},title:{color:tc.muted,fontSize:12,offsetCenter:[0,'62%']},data:[{value:sv,name:'多空偏向'}]}]});
  const b=echarts.init(document.getElementById('chartBreadth'));chartInstances.breadth=b;
  const acts=Object.entries(actionDist);
  b.setOption({backgroundColor:'transparent',tooltip:tipXL({trigger:'item',formatter:'{b}: {c}只 ({d}%)'}),legend:{bottom:0,textStyle:{color:tc.muted,fontSize:11},itemWidth:12,itemHeight:8,icon:'roundRect'},series:[pieSeries(acts.map(([k,v])=>({value:v,name:k,itemStyle:{color:{'强烈关注':tc.up,'重点关注':'#ff6b6b','适度关注':tc.gold,'观望等待':tc.muted,'注意风险':'#88cc88','建议规避':tc.down}[k]||tc.muted}})),{radius:['42%','68%'],center:['50%','45%'],label:{formatter:'{b}\n{c}只',color:tc.ink,fontSize:11,lineHeight:16}})]});
  const a=echarts.init(document.getElementById('chartAction'));chartInstances.action=a;
  a.setOption({backgroundColor:'transparent',tooltip:tipXL({trigger:'item',formatter:'{b}: {c}只 ({d}%)'}),series:[pieSeries(acts.map(([k,v])=>({value:v,name:k,itemStyle:{color:{'强烈关注':tc.up,'重点关注':'#ff6b6b','适度关注':tc.gold,'观望等待':tc.muted,'注意风险':'#88cc88','建议规避':tc.down}[k]||tc.muted}})),{radius:['38%','62%'],center:['50%','48%'],label:{formatter:'{b}:{c}',color:tc.ink,fontSize:10}})]});
  const ind=echarts.init(document.getElementById('chartInd'));chartInstances.ind=ind;
  const t10=Object.entries(indicatorCounts).sort((a,b)=>b[1]-a[1]).slice(0,10);
  ind.setOption({backgroundColor:'transparent',tooltip:tipXL({trigger:'axis',axisPointer:{type:'shadow',shadowStyle:{color:hexA(tc.bg4,.35)}}}),grid:{left:80,right:36,top:10,bottom:20},xAxis:{type:'value',axisLabel:{color:tc.muted},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},yAxis:{type:'category',data:t10.map(e=>e[0]).reverse(),axisLabel:{color:tc.muted,fontSize:11},axisLine:{lineStyle:{color:tc.rule}},axisTick:{show:false}},series:[hbar(tc.blue,{data:t10.map(e=>e[1]).reverse()})]});
}

// ===== L2: Sector =====
function renderSectorRanking(){
  const max=Math.max(...sectorData.map(s=>Math.abs(s.avg_sentiment)));
  document.getElementById('sectorRanking').innerHTML=sectorData.map(s=>{
    const w=Math.abs(s.avg_sentiment)/max*100,cls=s.avg_sentiment>0?'bull':'bear';
    return '<div class="sector-bar-row" onclick="showSectorDetail(\''+s.name+'\')"><div class="sector-name">'+s.name+'</div><div class="sector-bar-wrap"><div class="sector-bar-fill '+cls+'" style="width:'+Math.max(w,5)+'%;">'+(s.avg_sentiment>0?'+':'')+s.avg_sentiment+'</div></div><div class="sector-meta">'+s.total+'只 | 买'+s.buy_signals+' 卖'+s.sell_signals+' | 量化'+s.avg_quant_score+'</div></div>';
  }).join('');
}
function renderHeatmap(){
  const max=Math.max(...sectorData.map(s=>Math.abs(s.avg_sentiment)));
  document.getElementById('heatmapGrid').innerHTML=sectorData.map(s=>{
    const r=Math.abs(s.avg_sentiment)/max,o=0.3+r*0.7;
    const c=s.avg_sentiment>0?'rgba('+tc.upRgb+','+o+')':'rgba('+tc.downRgb+','+o+')';
    const t=r>0.5?'#fff':tc.ink;
    return '<div class="heatmap-cell" style="background:'+c+';color:'+t+';" onclick="showSectorDetail(\''+s.name+'\')"><div style="font-weight:700;font-size:13px;">'+s.name+'</div><div style="font-size:11px;margin-top:4px;">'+s.total+'只 | 情绪'+(s.avg_sentiment>0?'+':'')+s.avg_sentiment+'</div><div style="font-size:10px;margin-top:2px;opacity:0.8;">买'+s.buy_signals+' 卖'+s.sell_signals+'</div></div>';
  }).join('');
}
function showSectorDetail(name){
  const sd=sectorData.find(s=>s.name===name);if(!sd)return;
  // Sort stocks by "龙头": signal_count desc, then sentiment desc, then mkt_cap desc
  const stocks=sd.stocks.sort((a,b)=>b.signal_count-a.signal_count||b.sentiment-a.sentiment||b.mkt_cap_yi-a.mkt_cap_yi);
  let html='<h2 style="margin-bottom:8px;">'+name+' 板块详情</h2>';
  html+='<div class="stat-grid" style="margin-bottom:16px;">';
  html+='<div class="stat-item"><div class="stat-val gold">'+sd.total+'</div><div class="stat-lbl">入选个股</div></div>';
  html+='<div class="stat-item"><div class="stat-val '+(sd.avg_sentiment>=0?'up':'down')+'">'+(sd.avg_sentiment>=0?'+':'')+sd.avg_sentiment+'</div><div class="stat-lbl">平均情绪</div></div>';
  html+='<div class="stat-item"><div class="stat-val up">'+sd.buy_signals+'</div><div class="stat-lbl">买入信号</div></div>';
  html+='<div class="stat-item"><div class="stat-val down">'+sd.sell_signals+'</div><div class="stat-lbl">风险警示</div></div>';
  html+='<div class="stat-item"><div class="stat-val blue">'+sd.avg_quant_score+'</div><div class="stat-lbl">平均量化分</div></div>';
  html+='<div class="stat-item"><div class="stat-val gold">'+sd.avg_win_rate+'%</div><div class="stat-lbl">平均胜率</div></div>';
  html+='</div>';
  html+='<div style="font-size:13px;color:var(--muted);margin-bottom:12px;">按板块龙头排序（指标数量→情绪→市值）</div>';
  html+='<table><thead><tr><th>排名</th><th>★</th><th>代码</th><th>名称</th><th>指标数</th><th>情绪</th><th>信号</th><th>量化分</th><th>市值(亿)</th><th>操作</th></tr></thead><tbody>';
  stocks.forEach((s,i)=>{
    const st=scanData.find(x=>x.code===s.code);
    const sigCls=st&&st.trade_signal==='买入'?'buy':(st&&st.trade_signal==='卖出'?'sell':'watch');
    html+='<tr><td>'+(i+1)+'</td><td><button class="star-btn '+(isWatched(s.code)?'active':'')+'" onclick="toggleWatch(\''+s.code+'\');showSectorDetail(\''+name+'\')">'+(isWatched(s.code)?'★':'☆')+'</button></td><td>'+s.code+'</td><td style="font-weight:600;cursor:pointer;" onclick="showDetail(\''+s.code+'\')">'+s.name+'</td><td>'+s.signal_count+'</td><td class="'+(s.sentiment>=0?'pu':'pd')+'">'+(s.sentiment>=0?'+':'')+s.sentiment.toFixed(2)+'</td><td><span class="badge '+sigCls+'">'+(st?st.trade_signal:'--')+'</span></td><td>'+(st&&st.bt_has_data?st.bt_quant_score.toFixed(0):'--')+'</td><td>'+s.mkt_cap_yi.toFixed(0)+'</td><td><button class="btn" style="padding:4px 10px;font-size:12px;" onclick="showDetail(\''+s.code+'\')">详情</button></td></tr>';
  });
  html+='</tbody></table>';
  document.getElementById('sectorContent').innerHTML=html;
  openOverlay('sectorOverlay');
}
function renderL2Charts(){
  const ss=echarts.init(document.getElementById('chartSecSig'));chartInstances.secSig=ss;
  const ts=sectorData.slice(0,10);
  ss.setOption({backgroundColor:'transparent',tooltip:tipXL({trigger:'axis',axisPointer:{type:'shadow',shadowStyle:{color:hexA(tc.bg4,.35)}}}),legend:{data:['买入','卖出','观望'],textStyle:{color:tc.muted,fontSize:11},bottom:0,itemWidth:12,itemHeight:8,icon:'roundRect'},grid:{left:80,right:20,top:20,bottom:40},xAxis:{type:'value',axisLabel:{color:tc.muted},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},yAxis:{type:'category',data:ts.map(s=>s.name),axisLabel:{color:tc.muted,fontSize:11},axisLine:{lineStyle:{color:tc.rule}},axisTick:{show:false}},series:[{name:'买入',type:'bar',stack:'a',data:ts.map(s=>s.buy_signals),itemStyle:{color:tc.up,opacity:.92},emphasis:{focus:'series'}},{name:'卖出',type:'bar',stack:'a',data:ts.map(s=>s.sell_signals),itemStyle:{color:tc.down,opacity:.92},emphasis:{focus:'series'}},{name:'观望',type:'bar',stack:'a',data:ts.map(s=>s.total-s.buy_signals-s.sell_signals),itemStyle:{color:tc.bg4,borderRadius:[0,3,3,0]},emphasis:{focus:'series'}}]});
  const c=echarts.init(document.getElementById('chartConcept'));chartInstances.concept=c;
  const tc20=Object.entries(conceptDist).slice(0,20);
  c.setOption({backgroundColor:'transparent',tooltip:tipXL({trigger:'axis',axisPointer:{type:'shadow',shadowStyle:{color:hexA(tc.bg4,.35)}}}),grid:{left:100,right:36,top:10,bottom:20},xAxis:{type:'value',axisLabel:{color:tc.muted},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},yAxis:{type:'category',data:tc20.map(e=>e[0]).reverse(),axisLabel:{color:tc.muted,fontSize:10},axisLine:{lineStyle:{color:tc.rule}},axisTick:{show:false}},series:[hbar(tc.purple,{data:tc20.map(e=>e[1]).reverse()})]});
  const i=echarts.init(document.getElementById('chartIndustry'));chartInstances.industry=i;
  const ti=Object.entries(industryDist);
  i.setOption({backgroundColor:'transparent',tooltip:tipXL({trigger:'axis',axisPointer:{type:'shadow',shadowStyle:{color:hexA(tc.bg4,.35)}}}),grid:{left:100,right:36,top:10,bottom:20},xAxis:{type:'value',axisLabel:{color:tc.muted},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},yAxis:{type:'category',data:ti.map(e=>e[0]).reverse(),axisLabel:{color:tc.muted,fontSize:10},axisLine:{lineStyle:{color:tc.rule}},axisTick:{show:false}},series:[hbar(tc.gold,{data:ti.map(e=>e[1]).reverse()})]});
}

// ===== 幻方量化·板块轮动推演 + 产业链上下游 =====
function tierColor(t){return ({'领跑主线':tc.up,'接力补涨':tc.orange,'蓄势观察':tc.muted,'动能衰减':tc.blue,'退潮回避':tc.down})[t]||tc.muted;}
function renderRotationInsight(){
  const dom=document.getElementById('rotationInsight');if(!dom||!rotationInsight)return;
  const r=rotationInsight;
  dom.innerHTML='<div class="ri-block"><div class="ri-label">轮动主线（领跑板块）</div><div class="ri-val" style="color:var(--up);">'+r.lead+'</div></div>'
    +'<div class="ri-block"><div class="ri-label">接力补涨（资金流入）</div><div class="ri-val" style="color:var(--orange);">'+r.follow+'</div></div>'
    +'<div class="ri-block"><div class="ri-label">退潮回避（资金流出）</div><div class="ri-val" style="color:var(--down);">'+r.fade+'</div></div>'
    +'<div class="ri-dir">推演方向：'+r.direction+'</div>';
}
function renderRotationSummary(){
  const dom=document.getElementById('rotationSummary');if(!dom)return;
  const top6=rotationData.slice(0,6);
  let h='<div style="margin-bottom:6px;">综合轮动分 <b style="color:var(--gold);">'+rotationInsight.top_name+'</b> 居首（'+rotationInsight.top_score+'分）。</div>';
  top6.forEach(function(r,i){
    h+='<div style="display:flex;gap:8px;align-items:center;padding:3px 0;"><span style="color:var(--muted);min-width:18px;">'+(i+1)+'</span><span class="rot-chip" style="border-color:'+hexA(tierColor(r.tier),.35)+';cursor:pointer;" onclick="showSectorDetail(\''+r.name+'\')">'+r.name+'</span><span style="color:'+tierColor(r.tier)+';font-weight:700;">'+r.score+'分</span><span style="color:'+(r.momentum>=0?'var(--up)':'var(--down)')+';min-width:64px;">'+(r.momentum>=0?'+':'')+r.momentum+'%</span><span style="color:var(--muted);">'+r.tier+'</span></div>';
  });
  h+='<div style="margin-top:6px;color:var(--muted);">提示：轮动分综合动量、情绪、信号净流入、量化评分与历史胜率，用于识别主线板块与轮动节奏，非投资建议。</div>';
  dom.innerHTML=h;
}
function renderRotationLadder(){
  const dom=document.getElementById('rotationLadder');if(!dom)return;
  const tiers=['领跑主线','接力补涨','蓄势观察','动能衰减','退潮回避'];
  dom.innerHTML=tiers.map(function(t){
    const list=rotationData.filter(function(r){return r.tier===t;});
    const c=tierColor(t);
    return '<div class="rot-tier"><div class="rot-tier-head" style="color:'+c+';"><span class="rot-dot" style="background:'+c+';"></span>'+t+' <span style="color:var(--muted);font-size:11px;font-weight:400;">('+list.length+'个板块)</span></div><div class="rot-tier-body">'+list.map(function(r){return '<span class="rot-chip" style="border-color:'+hexA(c,.35)+';" onclick="showSectorDetail(\''+r.name+'\')">'+r.name+'<b style="color:'+c+';margin-left:4px;">'+r.score+'</b></span>';}).join('')+'</div></div>';
  }).join('');
}
function renderRotationQuadrant(){
  const dom=document.getElementById('chartRotation');if(!dom)return;
  const old=echarts.getInstanceByDom(dom);if(old)old.dispose();
  const ch=echarts.init(dom);chartInstances.rotation=ch;
  const data=rotationData.map(function(r){return {name:r.name,value:[r.momentum,r.sentiment,r.total],netBuy:r.net_buy,buy:r.buy,sell:r.sell,score:r.score,tier:r.tier};});
  ch.setOption({
    backgroundColor:'transparent',
    tooltip:tipXL({formatter:function(p){const d=p.data;return '<b>'+d.name+'</b><br>轮动分：<b style="color:'+tierColor(d.tier)+';">'+d.score+'</b>（'+d.tier+'）<br>动量（当日涨跌幅）：'+(d.value[0]>=0?'+':'')+d.value[0]+'%<br>情绪：'+(d.value[1]>=0?'+':'')+d.value[1]+'<br>信号净流入：'+(d.netBuy>=0?'+':'')+d.netBuy+'（买'+d.buy+' 卖'+d.sell+'）<br>覆盖个股：'+d.value[2]+'只';}}),
    grid:{left:52,right:28,top:16,bottom:44},
    xAxis:{name:'动量（当日涨跌幅）',nameTextStyle:{color:tc.muted,fontSize:10},type:'value',axisLine:{lineStyle:{color:tc.rule}},axisLabel:{color:tc.muted,fontSize:10,formatter:'{value}%'},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},
    yAxis:{name:'情绪',nameTextStyle:{color:tc.muted,fontSize:10},type:'value',axisLine:{lineStyle:{color:tc.rule}},axisLabel:{color:tc.muted,fontSize:10},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},
    series:[{
      type:'scatter',data:data,
      symbolSize:function(v){return 14+Math.min(v[2]*6,28);},
      itemStyle:{color:function(p){return tierColor(p.data.tier);},opacity:.82,shadowBlur:8,shadowColor:'rgba(0,0,0,.28)'},
      label:{show:true,position:'right',formatter:'{b}',color:tc.ink,fontSize:10},
      emphasis:{focus:'series',label:{fontWeight:700}},
      markLine:{silent:true,symbol:'none',lineStyle:{color:tc.muted,type:'dashed',opacity:.4},label:{show:false},data:[{xAxis:0},{yAxis:0}]}
    }]
  });
  ch.off('click');ch.on('click',function(p){if(p.data&&p.data.name)showSectorDetail(p.data.name);});
}
function renderChainGraph(){
  const dom=document.getElementById('chartChain');if(!dom)return;
  const old=echarts.getInstanceByDom(dom);if(old)old.dispose();
  const ch=echarts.init(dom);chartInstances.chain=ch;
  const cols={上游:6,中游:28,下游:50,金融:72,交运:90};
  const byStage={};
  rotationData.forEach(function(r){(byStage[r.stage]=byStage[r.stage]||[]).push(r);});
  Object.keys(byStage).forEach(function(k){byStage[k].sort(function(a,b){return b.score-a.score;});});
  const nodes=rotationData.map(function(r){
    const list=byStage[r.stage]||[r];
    const idx=list.indexOf(r), n=list.length;
    const y=n<=1?50:Math.round(8+idx*(84/(n-1)));
    return {name:r.name,symbolSize:16+Math.min(r.total*5,22),x:cols[r.stage]||50,y:y,itemStyle:{color:tierColor(r.tier),opacity:.92,shadowBlur:8,shadowColor:'rgba(0,0,0,.3)',borderColor:hexA(tc.bg,1),borderWidth:1},label:{show:true,position:['下游','金融','交运'].indexOf(r.stage)>=0?'left':'right',color:tc.ink,fontSize:10}};
  });
  const edges=(chainData.edges||[]).map(function(e){return {source:e.source,target:e.target};});
  ch.setOption({
    backgroundColor:'transparent',
    tooltip:tipXL({formatter:function(p){if(p.dataType==='edge')return p.data.source+' → '+p.data.target+'（产业链传导）';const r=rotationData.find(function(x){return x.name===p.data.name;});if(!r)return p.data.name;return '<b>'+p.data.name+'</b><br>环节：'+r.stage+(r.sub?' · '+r.sub:'')+'<br>轮动分：<b style="color:'+tierColor(r.tier)+';">'+r.score+'</b>（'+r.tier+'）<br>覆盖个股：'+r.total+'只<br>动量：'+(r.momentum>=0?'+':'')+r.momentum+'% ｜ 情绪：'+(r.sentiment>=0?'+':'')+r.sentiment+'<br>买'+r.buy+' / 卖'+r.sell;}}),
    series:[{
      type:'graph',layout:'none',data:nodes,links:edges,roam:true,
      lineStyle:{color:hexA(tc.muted,.35),width:1,curveness:0.08},
      edgeSymbol:['none','arrow'],edgeSymbolSize:7,
      label:{show:true,formatter:'{b}'},
      emphasis:{focus:'adjacency',lineStyle:{width:2.2,color:tc.blue},label:{fontWeight:700}},
      scaleLimit:{min:0.5,max:3}
    }]
  });
  ch.off('click');ch.on('click',function(p){if(p.dataType==='node'&&p.data&&p.data.name)showSectorDetail(p.data.name);});
}

// ===== L3: Stocks =====
function renderSignalCenter(){
  const sqFor=s=>{const sd=scanData.find(x=>x.code===s.code);return sd?sd.signal_quality||50:50;};
  const sqBadge=sq=>{const cls=sq>=65?'sig-q-high':(sq>=50?'sig-q-mid':'sig-q-low');return '<span class="sig-quality-badge '+cls+'">SQ'+sq.toFixed(0)+'</span>';};
  const graded=buySignals.map(s=>({s:s,cf:confluenceInfo(s)}));
  const onlyAB=document.getElementById('onlyAB');
  const showList=onlyAB&&onlyAB.checked?graded.filter(x=>x.cf.grade!=='C'):graded;
  showList.sort((a,b)=>{const ga=a.cf.grade==='A'?0:(a.cf.grade==='B'?1:2),gb=b.cf.grade==='A'?0:(b.cf.grade==='B'?1:2);return ga!==gb?ga-gb:b.cf.score-a.cf.score;});
  const aCount=graded.filter(x=>x.cf.grade==='A').length,bCount=graded.filter(x=>x.cf.grade==='B').length,cCount=graded.filter(x=>x.cf.grade==='C').length;
  document.getElementById('buyCount').innerHTML=showList.length+'只 <span style="font-size:11px;">(A:'+aCount+' B:'+bCount+' C:'+cCount+')</span>';
  document.getElementById('sellCount').textContent=sellSignals.length+'只';
  document.getElementById('buyList').innerHTML=showList.length?showList.map(({s:s,cf:cf})=>{
    const pot=((s.target_price-s.entry_price)/s.entry_price*100).toFixed(1);
    const risk=((s.entry_price-s.stop_loss)/s.entry_price*100).toFixed(1);
    const rr=(risk>0?(pot/risk).toFixed(2):'--');
    const w=isWatched(s.code);const sq=sqFor(s);
    const sd=scanData.find(x=>x.code===s.code);
    const ai=sd?aiAnalyze(sd):null;
    const posAdj=cycleState?Math.round(s.position_size*(cf.grade==='A'?1:(cf.grade==='B'?0.6:0.3))):s.position_size;
    const factorTags=cf.factors.map(f=>'<span class="cf-tag '+(f.on?'on':'off')+'" title="'+f.name+(f.on?' ✓'+f.pts+'分':'')+'">'+f.name+(f.on?'✓':'')+'</span>').join('');
    return '<div class="sig-card grade-'+cf.grade+'" style="'+(cf.grade==='A'?'border-left:3px solid var(--up);':'')+'"><div class="sig-card-head"><span class="sig-card-name" style="cursor:pointer;" onclick="showDetail(\''+s.code+'\')">'+s.name+' <span style="color:var(--muted);font-size:12px;">'+s.code+'</span></span><div><button class="star-btn '+(w?'active':'')+'" onclick="toggleWatch(\''+s.code+'\')">'+(w?'★':'☆')+'</button> <span class="badge buy">'+s.signal+'</span> <span class="grade-badge grade-'+cf.grade+'">'+cf.grade+'级·共振'+cf.score+'</span>'+(ai?'<span class="ai-verdict '+ai.cls+'" style="font-size:11px;padding:2px 8px;">AI:'+ai.verdict+'('+ai.score+')</span>':'')+sqBadge(sq)+'</div></div><div class="sig-card-body"><div><div class="lbl" title="建议入场价格">入场价</div><div class="val pu">'+s.entry_price.toFixed(2)+'</div></div><div><div class="lbl" title="ATR动态止损价">止损价</div><div class="val pd">'+s.stop_loss.toFixed(2)+'</div></div><div><div class="lbl" title="ATR动态目标价">目标价</div><div class="val" style="color:var(--gold);">'+s.target_price.toFixed(2)+'</div></div><div><div class="lbl" title="建议仓位%">仓位</div><div class="val">'+posAdj+'%</div></div><div><div class="lbl" title="回测胜率%">胜率</div><div class="val">'+s.win_rate.toFixed(1)+'%</div></div><div><div class="lbl" title="量化过滤后胜率%">量化胜率</div><div class="val" style="color:var(--gold);">'+(s.wr_filtered||0).toFixed(1)+'%</div></div><div><div class="lbl" title="盈亏比(收益/风险)">R/R比</div><div class="val" style="color:var(--gold);font-weight:700;">'+rr+'</div></div><div><div class="lbl" title="建议持仓周期">周期</div><div class="val" style="font-size:11px;">'+s.holding_period+'</div></div></div><div class="confluence-factors">'+factorTags+'</div><div class="rr-bar" style="margin-top:6px;"><span style="color:var(--down);font-size:10px;">风险-'+risk+'%</span><div style="flex:1;height:6px;border-radius:3px;background:var(--bg4);position:relative;overflow:hidden;"><div style="height:100%;width:'+Math.min(parseFloat(rr)/3*100,100)+'%;border-radius:3px;background:linear-gradient(90deg,var(--down),var(--gold),var(--up));"></div></div><span style="color:var(--up);font-size:10px;">收益+'+pot+'%</span></div>'+(ai?'<div class="watch-advice" style="margin-top:8px;padding:8px;border-color:rgba(168,85,247,.25);"><b style="color:var(--purple);">AI操作建议：</b>'+ai.advice+'</div>':'')+'<div style="margin-top:8px;font-size:11px;color:var(--muted);line-height:1.6;">'+s.reason+'</div></div>';
  }).join(''):'<div style="text-align:center;padding:30px 16px;color:var(--muted);font-size:13px;line-height:1.8;">'+(cycleState?('当前情绪周期 <b style="color:'+cycleState.st.color+';">'+cycleState.st.name+'</b>，'+graded.length+'个买入信号均未达到A/B级共振标准<br>（胜率保护机制：逆周期/弱共振信号自动过滤，防止低胜率交易）'):(graded.length+'个买入信号暂未完成共振评估（情绪数据加载中）'))+'<br><span style="font-size:11px;opacity:.7;">可取消勾选"仅看A/B级共振"查看全部信号</span></div>';
  document.getElementById('sellList').innerHTML=sellSignals.map(s=>{
    const w=isWatched(s.code);const sq=sqFor(s);
    return '<div class="sig-card sell"><div class="sig-card-head"><span class="sig-card-name" style="cursor:pointer;" onclick="showDetail(\''+s.code+'\')">'+s.name+' <span style="color:var(--muted);font-size:12px;">'+s.code+'</span></span><div><button class="star-btn '+(w?'active':'')+'" onclick="toggleWatch(\''+s.code+'\')">'+(w?'★':'☆')+'</button> <span class="badge sell">'+s.signal+'</span> '+sqBadge(sq)+'</div></div><div class="sig-card-body"><div><div class="lbl" title="建议入场价格">入场价</div><div class="val">'+s.entry_price.toFixed(2)+'</div></div><div><div class="lbl" title="ATR动态止损价">止损价</div><div class="val pd">'+s.stop_loss.toFixed(2)+'</div></div><div><div class="lbl" title="ATR动态目标价">目标价</div><div class="val">'+s.target_price.toFixed(2)+'</div></div><div><div class="lbl" title="建议仓位%">仓位</div><div class="val">'+s.position_size+'%</div></div><div><div class="lbl" title="回测胜率%">胜率</div><div class="val">'+s.win_rate.toFixed(1)+'%</div></div><div><div class="lbl" title="量化综合评分">量化分</div><div class="val">'+(s.quant_score||0).toFixed(0)+'</div></div><div><div class="lbl" title="盈亏比PF">盈亏比</div><div class="val">'+s.profit_factor.toFixed(2)+'</div></div><div><div class="lbl" title="建议持仓周期">持仓周期</div><div class="val" style="font-size:11px;">'+s.holding_period+'</div></div></div><div style="margin-top:8px;font-size:11px;color:var(--muted);line-height:1.6;">'+s.reason+'</div></div>';
  }).join('');
  trackSignals();
}
function renderRadarCharts(){
  const top=scanData.filter(s=>s.bt_has_data).sort((a,b)=>b.bt_quant_score-a.bt_quant_score).slice(0,3);
  const radarInfo='<div style="font-size:10px;color:var(--muted);margin-top:4px;line-height:1.6;">RSI: 相对强弱(30-65优) | ADX: 趋势强度(>20有趋势) | 量比: (1.2-3优) | 布林位置: (0-1) | ATR波幅%: 平均真实波幅 | 量化评分: 多因子综合</div>';
  document.getElementById('radarGrid').innerHTML=top.map((s,i)=>'<div><h4 style="margin-bottom:8px;">'+s.name+' ('+s.code+')</h4><div id="radar'+i+'" style="width:100%;height:260px;"></div>'+radarInfo+'</div>').join('');
  top.forEach((s,i)=>{
    const ch=echarts.init(document.getElementById('radar'+i));chartInstances['radar'+i]=ch;
    ch.setOption({backgroundColor:'transparent',tooltip:tipXL({trigger:'item'}),radar:{indicator:[{name:'RSI',max:100},{name:'ADX',max:100},{name:'量比',max:5},{name:'布林位置',max:1},{name:'ATR波幅%',max:10},{name:'量化评分',max:100}],center:['50%','52%'],radius:'68%',axisName:{color:tc.muted,fontSize:11},splitLine:{lineStyle:{color:tc.rule,type:'dashed'}},splitArea:{areaStyle:{color:[hexA(tc.bg2,.4),hexA(tc.bg3,.4)]}},axisLine:{lineStyle:{color:tc.rule}}},series:[{type:'radar',symbolSize:5,data:[{value:[s.bt_rsi||50,s.bt_adx||0,s.bt_vol_ratio||1,s.bt_bb_pos||0.5,s.bt_atr_pct||0,s.bt_quant_score||0],name:s.name,areaStyle:{color:'rgba('+tc.upRgb+',0.22)'},lineStyle:{color:tc.up,width:2.5},itemStyle:{color:tc.up,borderColor:tc.bg2,borderWidth:1.5,shadowColor:'rgba('+tc.upRgb+',.55)',shadowBlur:8}}],animationDuration:600}]});
  });
}
function renderOpPanel(){
  const buys=buySignals.slice(0,6);
  document.getElementById('opPanel').innerHTML=buys.map(s=>{
    const pot=((s.target_price-s.entry_price)/s.entry_price*100).toFixed(1);
    const risk=((s.entry_price-s.stop_loss)/s.entry_price*100).toFixed(1);
    const rr=(pot/risk).toFixed(2);
    return '<div style="background:var(--bg3);border-radius:10px;padding:14px;border-left:3px solid var(--up);"><div style="display:flex;justify-content:space-between;margin-bottom:8px;"><span style="font-weight:700;cursor:pointer;" onclick="showDetail(\''+s.code+'\')">'+s.name+' '+s.code+'</span><span class="badge buy">'+s.holding_period+'</span></div><div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;font-size:12px;"><div><span style="color:var(--muted);" title="建议入场价格">入场</span><br><b style="color:var(--up);">'+s.entry_price.toFixed(2)+'</b></div><div><span style="color:var(--muted);" title="ATR动态止损">止损</span><br><b style="color:var(--down);">'+s.stop_loss.toFixed(2)+'</b></div><div><span style="color:var(--muted);" title="ATR动态目标">目标</span><br><b style="color:var(--gold);">'+s.target_price.toFixed(2)+'</b></div><div><span style="color:var(--muted);" title="建议仓位%">仓位</span><br><b>'+s.position_size+'%</b></div><div><span style="color:var(--muted);" title="入场到止损的跌幅%">风险</span><br><b style="color:var(--down);">-'+risk+'%</b></div><div><span style="color:var(--muted);" title="入场到目标的涨幅%">收益</span><br><b style="color:var(--up);">+'+pot+'%</b></div></div><div style="margin-top:6px;font-size:11px;color:var(--muted);">盈亏比 '+rr+' | 胜率 '+s.win_rate.toFixed(0)+'% | 量化 '+(s.quant_score||0).toFixed(0)+'分</div></div>';
  }).join('');
}
function renderSignalMatrix(){
  const topInd=Object.entries(indicatorCounts).sort((a,b)=>b[1]-a[1]).slice(0,12).map(e=>e[0]);
  const topStocks=scanData.slice().sort((a,b)=>(b.signal_count||0)-(a.signal_count||0)).slice(0,15);
  let html='<thead><tr><th>股票</th>';topInd.forEach(ind=>{html+='<th style="writing-mode:vertical-lr;font-size:10px;">'+ind+'</th>';});html+='<th>合计</th></tr></thead><tbody>';
  topStocks.forEach(s=>{
    html+='<tr><td style="text-align:left;font-weight:600;cursor:pointer;" onclick="showDetail(\''+s.code+'\')">'+s.name+'</td>';let count=0;const indSet=new Set();const indType={};
    (s.signals||[]).forEach(sig=>{indSet.add(sig.indicator);indType[sig.indicator]=sig.type;});
    topInd.forEach(ind=>{const has=indSet.has(ind),st=indType[ind];let cls='none',title='';if(has){title=ind+'-'+st;cls=st==='看涨'?'up':(st==='看跌'?'down':'neutral');count++;}html+='<td title="'+title+'"><span class="matrix-dot '+cls+'"></span></td>';});
    html+='<td style="font-weight:700;color:var(--gold);">'+count+'</td></tr>';
  });
  html+='</tbody>';document.getElementById('sigMatrix').innerHTML=html;
}

// ===== L0: Trading Signal Board =====
function renderRegimeBadge(){
  const rm=riskMetrics||{};const regime=rm.market_regime||'震荡';
  const badge=document.getElementById('regimeBadge');
  if(!badge)return;
  const cls=regime==='多头'?'regime-bull':(regime==='空头'?'regime-bear':'regime-range');
  badge.className='regime-badge '+cls;
  badge.textContent='市场'+regime;
  const now=new Date();
  document.getElementById('l0Update').textContent=now.toLocaleTimeString('zh-CN');
}
function renderRiskDashboard(){
  // 数据校验
  const v=riskMetrics.validation||{};
  const oldVb=document.getElementById('riskValidBox');
  if(oldVb)oldVb.remove();
  if(Object.keys(v).length>0){
    const issues=[];
    if(!v.position_valid)issues.push('仓位数据异常');
    if(!v.entry_stop_valid)issues.push('止损价≥入场价');
    if(!v.entry_target_valid)issues.push('目标价≤入场价');
    if(v.sector_concentration_pct>40)issues.push('行业集中度'+v.sector_concentration_pct+'%偏高');
    const validBox=document.createElement('div');
    validBox.id='riskValidBox';
    validBox.style.cssText='font-size:11px;color:'+(issues.length?'var(--up)':'var(--down)')+';margin-top:8px;padding:6px 10px;border-radius:6px;background:rgba(255,255,255,.03);';
    validBox.innerHTML='<b>数据校验:</b> '+(issues.length?'⚠ '+issues.join(' · '):'✓ 全部通过')+
      ' | 买入占比'+v.buy_signal_pct+'% | 最大仓位'+v.max_single_position+'% | 行业集中度'+v.sector_concentration_pct+'%';
    const rp=document.getElementById('riskPanel');
    if(rp&&rp.parentNode){rp.parentNode.insertBefore(validBox,rp.nextSibling);}
  }
  const rm=riskMetrics||{};
  // Risk panel: gauge showing total exposure
  const rp=echarts.init(document.getElementById('riskPanel'));chartInstances.riskPanel=rp;
  const expo=rm.total_exposure||0;const maxExpo=300;
  rp.setOption({backgroundColor:'transparent',series:[{type:'gauge',min:0,max:maxExpo,startAngle:205,endAngle:-25,axisLine:{lineStyle:{width:16,color:[[0.3,tc.down],[0.6,tc.gold],[1,tc.up]]}},pointer:{length:'58%',width:4,itemStyle:{color:tc.ink}},anchor:{show:true,size:8,itemStyle:{color:tc.bg4,borderColor:tc.muted,borderWidth:2}},axisTick:{distance:-13,length:3,lineStyle:{color:tc.muted,width:1}},splitLine:{distance:-13,length:10,lineStyle:{color:tc.muted,width:1}},axisLabel:{color:tc.muted,distance:-24,fontSize:9},detail:{valueAnimation:true,color:expo>60?tc.up:(expo>30?tc.gold:tc.down),fontSize:24,fontWeight:700,fontFamily:"'SF Mono','JetBrains Mono',monospace",offsetCenter:[0,'38%'],formatter:'{value}%'},title:{color:tc.muted,fontSize:11,offsetCenter:[0,'62%']},data:[{value:expo,name:'总仓位敞口'}]}]});
  // Signal quality distribution
  const sq=echarts.init(document.getElementById('chartSigQual'));chartInstances.sigQual=sq;
  const buckets={'高(≥65)':0,'中(50-65)':0,'低(<50)':0};
  scanData.forEach(s=>{const v=s.signal_quality||50;if(v>=65)buckets['高(≥65)']++;else if(v>=50)buckets['中(50-65)']++;else buckets['低(<50)']++;});
  sq.setOption({backgroundColor:'transparent',tooltip:tipXL({trigger:'item',formatter:'{b}: {c}只 ({d}%)'}),legend:{bottom:0,textStyle:{color:tc.muted,fontSize:10},itemWidth:12,itemHeight:8,icon:'roundRect'},series:[pieSeries(Object.entries(buckets).map(([k,v])=>({value:v,name:k,itemStyle:{color:{'高(≥65)':tc.up,'中(50-65)':tc.gold,'低(<50)':tc.muted}[k]||tc.muted}})),{radius:['38%','62%'],center:['50%','45%'],label:{formatter:'{b}\n{c}只',color:tc.ink,fontSize:10,lineHeight:14}})]});
  // Sector concentration
  const sc=echarts.init(document.getElementById('chartSectorConc'));chartInstances.sectorConc=sc;
  const conc=Object.entries(rm.sector_concentration||{});
  sc.setOption({backgroundColor:'transparent',tooltip:tipXL({trigger:'axis',axisPointer:{type:'shadow',shadowStyle:{color:hexA(tc.bg4,.35)}}}),grid:{left:80,right:36,top:10,bottom:20},xAxis:{type:'value',axisLabel:{color:tc.muted,fontSize:9},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},yAxis:{type:'category',data:conc.map(e=>e[0]),axisLabel:{color:tc.muted,fontSize:10},axisLine:{lineStyle:{color:tc.rule}},axisTick:{show:false}},series:[hbar(tc.purple,{data:conc.map(e=>e[1])})]});
}

// ===== L4: Quantitative Learning Center =====
function renderLearningCenter(){
  // Learning insights
  const insights=learningInsights||[];
  document.getElementById('learningInsights').innerHTML=insights.map((t,i)=>'<div class="insight-card"><div class="ic-label">洞察 #'+(i+1)+'</div>'+t+'</div>').join('');
  // Indicator performance table
  const perf=indicatorPerf||[];
  const qTag=q=>{const c={'高':'trend-up','中':'trend-flat','低':'trend-down'}[q]||'trend-flat';return '<span class="trend-tag '+c+'">'+q+'</span>';};
  document.getElementById('indPerfBody').innerHTML=perf.map(p=>{
    return '<tr><td class="idx-name">'+p.name+'</td><td style="color:'+(p.type==='看涨'?'var(--up)':(p.type==='看跌'?'var(--down)':'var(--muted)'))+';">'+p.type+'</td><td>'+p.triggers+'</td><td class="'+(p.win_rate>=50?'up':'down')+'">'+p.win_rate+'%</td><td class="'+(p.wr_filtered>=50?'up':'down')+'">'+p.wr_filtered+'%</td><td>'+(p.profit_factor>=1.3?'<b style="color:var(--gold);">':'')+(p.profit_factor||0).toFixed(2)+(p.profit_factor>=1.3?'</b>':'')+'</td><td class="'+(p.expectancy>=0?'up':'down')+'">'+(p.expectancy>=0?'+':'')+(p.expectancy||0).toFixed(2)+'</td><td>'+qTag(p.quality)+'</td></tr>';
  }).join('');
  // Scatter: win rate vs profit factor
  const ip=echarts.init(document.getElementById('chartIndPerf'));chartInstances.indPerf=ip;
  ip.setOption({backgroundColor:'transparent',tooltip:tipXL({formatter:p=>p.data[2]+'<br/>胜率:'+p.data[0]+'% | 盈亏比:'+p.data[1]+'<br/>触发:'+p.data[3]+'次'}),grid:{left:50,right:30,top:30,bottom:40},xAxis:{type:'value',name:'胜率(%)',nameTextStyle:{color:tc.muted,fontSize:10},axisLabel:{color:tc.muted},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},yAxis:{type:'value',name:'盈亏比',nameTextStyle:{color:tc.muted,fontSize:10},axisLabel:{color:tc.muted},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},series:[{type:'scatter',symbolSize:p=>Math.sqrt(p[3])*3,data:perf.map(p=>{var c=p.win_rate>=55?tc.up:(p.win_rate>=48?tc.gold:tc.down);return{value:[p.win_rate,p.profit_factor,p.name,p.triggers],itemStyle:{color:c,opacity:.85,borderColor:hexA(c,.4),borderWidth:1,shadowColor:hexA(c,.5),shadowBlur:8}};}),label:{show:true,position:'right',formatter:p=>p.data[2],color:tc.muted,fontSize:9}}]});
}

function renderL3Charts(){
  const sc=echarts.init(document.getElementById('chartScatter'));chartInstances.scatter=sc;
  sc.setOption({backgroundColor:'transparent',tooltip:tipXL({formatter:p=>p.data[2]+'<br/>指标:'+p.data[0]+' 情绪:'+p.data[1]}),grid:{left:50,right:20,top:20,bottom:40},xAxis:{type:'value',name:'指标数量',nameTextStyle:{color:tc.muted},axisLabel:{color:tc.muted},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},yAxis:{type:'value',name:'情绪评分',nameTextStyle:{color:tc.muted},axisLabel:{color:tc.muted},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},series:[{type:'scatter',symbolSize:p=>p[0]*3,data:scanData.map(s=>{var c=s.sentiment_score>=0?tc.up:tc.down;return{value:[s.signal_count,s.sentiment_score,s.name],itemStyle:{color:c,opacity:.8,shadowColor:hexA(c,.5),shadowBlur:7}};})}]});
  const qs=echarts.init(document.getElementById('chartQScatter'));chartInstances.qs=qs;
  const bd=scanData.filter(s=>s.bt_has_data);
  qs.setOption({backgroundColor:'transparent',tooltip:tipXL({formatter:p=>p.data[2]+'<br/>量化:'+p.data[0]+' 胜率:'+p.data[1]+'% 盈亏比:'+p.data[3]}),grid:{left:50,right:20,top:20,bottom:40},xAxis:{type:'value',name:'量化评分',nameTextStyle:{color:tc.muted},axisLabel:{color:tc.muted},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},yAxis:{type:'value',name:'回测胜率(%)',nameTextStyle:{color:tc.muted},axisLabel:{color:tc.muted},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},series:[{type:'scatter',symbolSize:p=>Math.max(p[3]*8,8),data:bd.map(s=>({value:[s.bt_quant_score,s.bt_win_rate,s.name,s.bt_pf],itemStyle:{color:tc.blue,opacity:.8,shadowColor:hexA(tc.blue,.5),shadowBlur:7}}))}]});
}

// ===== Table =====
let curSort={key:'signal_count',desc:true},curFilter='';
function renderTable(){
  let f=scanData;if(curFilter){f=scanData.filter(s=>{const pinyin=pinyinMap[s.code]||'';return fuzzyMatch(s.code,curFilter)||fuzzyMatch(s.name,curFilter)||fuzzyMatch(pinyin,curFilter)||fuzzyMatch(s.industry,curFilter);});}
  f.sort((a,b)=>{let av=a[curSort.key],bv=b[curSort.key];if(typeof av==='number'&&typeof bv==='number')return curSort.desc?bv-av:av-bv;return curSort.desc?String(bv).localeCompare(String(av)):String(av).localeCompare(String(bv));});
  document.getElementById('filterInfo').textContent='显示 '+f.length+' / '+scanData.length+' 只';
  document.getElementById('stockTableBody').innerHTML=f.map((s,i)=>{
    const pc=s.pct_change>=0?'pu':'pd',sigCls=s.trade_signal==='买入'?'buy':(s.trade_signal==='卖出'?'sell':'watch'),w=isWatched(s.code);
    return '<tr><td><button class="star-btn '+(w?'active':'')+'" onclick="toggleWatch(\''+s.code+'\')">'+(w?'★':'☆')+'</button></td><td>'+(i+1)+'</td><td>'+s.code+'</td><td style="font-weight:600;cursor:pointer;" onclick="showDetail(\''+s.code+'\')">'+s.name+'</td><td>'+s.signal_count+'</td><td style="font-size:11px;color:var(--muted);">'+s.industry+'</td><td><span class="badge '+sigCls+'">'+s.trade_signal+'</span></td><td>'+(s.bt_has_data?s.bt_win_rate.toFixed(1)+'%':'--')+'</td><td>'+(s.bt_has_data?s.bt_wr_filtered.toFixed(1)+'%':'--')+'</td><td>'+(s.bt_has_data?s.bt_quant_score.toFixed(0):'--')+'</td><td>'+(s.bt_has_data?s.bt_pf.toFixed(2):'--')+'</td><td>'+(s.position_size||'--')+(s.position_size?'%':'')+'</td><td>'+s.confidence+'</td><td>'+('★'.repeat(s.priority))+('☆'.repeat(5-s.priority))+'</td><td>'+s.risk+'</td><td>'+(s.sentiment_score>=0?'+':'')+s.sentiment_score.toFixed(2)+'</td><td style="font-weight:700;color:'+(s.signal_quality>=65?'var(--up)':(s.signal_quality>=50?'var(--gold)':'var(--muted)'))+';">'+(s.signal_quality||50).toFixed(0)+'</td><td>'+s.mkt_cap_yi.toFixed(0)+'</td><td class="'+pc+'">'+s.latest_price.toFixed(2)+'</td><td class="'+pc+'">'+(s.pct_change>=0?'+':'')+s.pct_change.toFixed(2)+'%</td></tr>';
  }).join('');
}
document.getElementById('searchInput').addEventListener('input',e=>{curFilter=e.target.value;renderTable();});
document.getElementById('sortBy').addEventListener('change',e=>{curSort.key=e.target.value;curSort.desc=true;renderTable();});
document.querySelectorAll('.sortable').forEach(th=>th.addEventListener('click',()=>{const k=th.dataset.key;if(curSort.key===k)curSort.desc=!curSort.desc;else{curSort.key=k;curSort.desc=true;}renderTable();}));

// ===== Stock Detail (Enhanced) =====
function showDetail(code){
  const s=scanData.find(x=>x.code===code);if(!s)return;
  const pc=s.pct_change>=0?'pu':'pd',ps=s.pct_change>=0?'+':'';
  const kl=klineData[code]||{};const kline=kl.kline||[];
  const w=isWatched(s.code);
  // Signal timeline
  const tl=s.signals.map(sig=>{const cls=sig.type==='看涨'?'up':(sig.type==='看跌'?'down':'gold');return '<div class="sig-tl-item"><div class="sig-tl-dot" style="background:var(--'+cls+');"></div><span style="color:var(--muted);">'+sig.date+'</span><span class="badge '+(sig.type==='看涨'?'buy':(sig.type==='看跌'?'sell':'watch'))+'">'+sig.type+'</span><span style="font-weight:600;">'+sig.indicator+'</span><span style="color:var(--muted);font-size:11px;">'+sig.section+'</span></div>';}).join('');
  // Backtest table
  let btHtml='';
  if(s.bt_has_data){
    btHtml='<div class="dt-section"><h4><span style="width:3px;height:14px;background:var(--gold);border-radius:2px;display:inline-block;"></span>回测数据</h4>';
    btHtml+='<div class="dt-grid">';
    btHtml+='<div class="dt-cell"><div class="v '+(s.bt_win_rate>=50?'up':'down')+'">'+s.bt_win_rate.toFixed(1)+'%</div><div class="l">次日胜率</div></div>';
    btHtml+='<div class="dt-cell"><div class="v '+(s.bt_wr_3d>=50?'up':'down')+'">'+s.bt_wr_3d.toFixed(1)+'%</div><div class="l">3日胜率</div></div>';
    btHtml+='<div class="dt-cell"><div class="v '+(s.bt_wr_5d>=50?'up':'down')+'">'+s.bt_wr_5d.toFixed(1)+'%</div><div class="l">5日胜率</div></div>';
    btHtml+='<div class="dt-cell"><div class="v '+(s.bt_wr_filtered>=50?'up':'down')+'">'+s.bt_wr_filtered.toFixed(1)+'%</div><div class="l">量化过滤胜率</div></div>';
    btHtml+='<div class="dt-cell"><div class="v">'+s.bt_pf.toFixed(2)+'</div><div class="l">盈亏比</div></div>';
    btHtml+='<div class="dt-cell"><div class="v">'+s.bt_exp.toFixed(2)+'%</div><div class="l">期望收益</div></div>';
    btHtml+='<div class="dt-cell"><div class="v gold">'+s.bt_quant_score.toFixed(0)+'</div><div class="l">量化评分</div></div>';
    btHtml+='<div class="dt-cell"><div class="v">'+s.bt_rsi.toFixed(1)+'</div><div class="l">RSI(14)</div></div>';
    btHtml+='<div class="dt-cell"><div class="v">'+s.bt_adx.toFixed(1)+'</div><div class="l">ADX(14)</div></div>';
    btHtml+='<div class="dt-cell"><div class="v">'+s.bt_vol_ratio.toFixed(2)+'</div><div class="l">量比</div></div>';
    btHtml+='<div class="dt-cell"><div class="v">'+(s.bt_bb_pos*100).toFixed(0)+'%</div><div class="l">布林位置</div></div>';
    btHtml+='<div class="dt-cell"><div class="v">'+s.bt_atr_pct.toFixed(2)+'%</div><div class="l">ATR波幅</div></div>';
    btHtml+='</div></div>';
  }
  // Trading signal box
  let tradeHtml='';
  if(s.entry_price||s.stop_loss||s.target_price){
    const pot=s.target_price?((s.target_price-s.entry_price)/s.entry_price*100).toFixed(1):'--';
    const risk=s.stop_loss?((s.entry_price-s.stop_loss)/s.entry_price*100).toFixed(1):'--';
    tradeHtml='<div class="dt-section" style="border-left:3px solid var(--up);"><h4><span style="width:3px;height:14px;background:var(--up);border-radius:2px;display:inline-block;"></span>交易信号</h4>';
    tradeHtml+='<div style="display:flex;gap:8px;margin-bottom:8px;"><span class="badge '+(s.trade_signal==='买入'?'buy':(s.trade_signal==='卖出'?'sell':'watch'))+'">'+s.trade_signal+'</span><span style="color:var(--muted);font-size:12px;">持仓周期: '+s.holding_period+'</span></div>';
    tradeHtml+='<div class="dt-grid">';
    if(s.entry_price)tradeHtml+='<div class="dt-cell"><div class="v pu">'+s.entry_price.toFixed(2)+'</div><div class="l">入场价</div></div>';
    if(s.stop_loss)tradeHtml+='<div class="dt-cell"><div class="v pd">'+s.stop_loss.toFixed(2)+'</div><div class="l">止损价</div></div>';
    if(s.target_price)tradeHtml+='<div class="dt-cell"><div class="v" style="color:var(--gold);">'+s.target_price.toFixed(2)+'</div><div class="l">目标价</div></div>';
    if(s.position_size)tradeHtml+='<div class="dt-cell"><div class="v">'+s.position_size+'%</div><div class="l">建议仓位</div></div>';
    if(s.entry_price&&s.target_price)tradeHtml+='<div class="dt-cell"><div class="v pu">+'+pot+'%</div><div class="l">潜在收益</div></div>';
    if(s.entry_price&&s.stop_loss)tradeHtml+='<div class="dt-cell"><div class="v pd">-'+risk+'%</div><div class="l">风险幅度</div></div>';
    tradeHtml+='</div>';
    if(s.trade_reason)tradeHtml+='<div style="margin-top:8px;padding:10px;background:var(--bg2);border-radius:8px;font-size:12px;color:var(--muted);line-height:1.6;">'+s.trade_reason+'</div>';
    tradeHtml+='</div>';
  }
  // Pattern analysis
  let patHtml='';
  if(s.pattern_descs&&s.pattern_descs.length>0){
    patHtml='<div class="dt-section"><h4><span style="width:3px;height:14px;background:var(--purple);border-radius:2px;display:inline-block;"></span>信号组合分析</h4>';
    patHtml+=s.pattern_descs.map(p=>'<div style="padding:4px 0;font-size:12px;color:var(--muted);">'+p+'</div>').join('');
    patHtml+='</div>';
  }
  // Concepts
  let conHtml='';
  if(s.concepts&&s.concepts.length>0){
    conHtml='<div class="dt-section"><h4><span style="width:3px;height:14px;background:var(--blue);border-radius:2px;display:inline-block;"></span>所属概念板块</h4><div>'+s.concepts.map(c=>'<span class="concept-tag">'+c+'</span>').join('')+'</div></div>';
  }
  // K-line chart
  let kHtml='';
  if(kline.length>0)kHtml='<div class="dt-section"><h4><span style="width:3px;height:14px;background:var(--gold);border-radius:2px;display:inline-block;"></span>K线图（MA/BOLL + 成交量/MACD/KDJ）</h4><div id="klineChart" style="width:100%;height:460px;"></div></div>';
  // Build detail HTML
  document.getElementById('detailContent').innerHTML='<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center;"><div><h2 style="display:flex;align-items:center;gap:10px;">'+s.name+' <span style="color:var(--muted);font-size:16px;font-weight:400;">'+s.code+'</span> <span class="badge '+(s.cls_key==='strong_bull'||s.cls_key==='bull'?'buy':(s.cls_key==='strong_bear'||s.cls_key==='bear'?'sell':'watch'))+'">'+s.classification+'</span></h2><div style="display:flex;gap:20px;margin-top:8px;font-size:14px;"><span>最新价: <b class="'+pc+'" style="font-size:18px;">'+s.latest_price.toFixed(2)+'</b></span><span>涨跌幅: <b class="'+pc+'">'+ps+s.pct_change.toFixed(2)+'%</b></span><span>市值: <b>'+s.mkt_cap_yi.toFixed(0)+'亿</b></span><span>行业: <b>'+s.industry+'</b></span></div></div><div style="display:flex;flex-direction:column;align-items:flex-end;gap:8px;">'+(w?'<button style="background:var(--gold);color:#000;font-weight:700;padding:8px 20px;font-size:13px;border:none;border-radius:8px;cursor:pointer;" onclick="toggleWatch(\''+s.code+'\');showDetail(\''+s.code+'\')">★ 已关注</button>':'<button style="background:var(--bg3);color:var(--ink);font-weight:600;padding:8px 20px;font-size:13px;border:1px solid var(--rule);border-radius:8px;cursor:pointer;" onclick="toggleWatch(\''+s.code+'\');showDetail(\''+s.code+'\')">☆ 添加关注</button>')+'</div></div>'+tradeHtml+execPlanHtml(s)+'<div id="t0DetailBox"></div>'+aiAnalyzeHtml(s)+btHtml+patHtml+conHtml+'<div class="dt-section"><h4><span style="width:3px;height:14px;background:var(--up);border-radius:2px;display:inline-block;"></span>信号触发时间轴（'+s.signals.length+'个信号）</h4><div class="sig-timeline">'+tl+'</div></div>'+kHtml;
  openOverlay('detailOverlay');
  if(kline.length>0)setTimeout(()=>renderKlineChart(kline),100);
  loadT0ForDetail(s.code);
}
// ===== 副图指标配置系统 =====
function loadKlineCfg(){try{const s=localStorage.getItem('klineCfg');if(s){const c=JSON.parse(s);if(c&&Array.isArray(c.subs)&&c.subs.length>0)return c;}}catch(e){}return{count:3,subs:['VOL','MACD','KDJ'],boll:true};}
function saveKlineCfg(){try{localStorage.setItem('klineCfg',JSON.stringify(window._klineCfg));}catch(e){}}
window._klineCfg=loadKlineCfg();
const KF_ORDER=['VOL','MACD','KDJ','RSI','WR','OBV'];
const KF_LABEL={VOL:'成交量',MACD:'MACD',KDJ:'KDJ',RSI:'RSI',WR:'WR',OBV:'OBV'};
const SUB_IND={
  VOL:function(kline){return{title:'成交量',yAxis:{scale:true,position:'right',axisLabel:{color:tc.muted,fontSize:9,fontFamily:"'SF Mono',monospace",formatter:v=>v>=1e8?(v/1e8).toFixed(1)+'亿':(v>=1e4?(v/1e4).toFixed(0)+'万':v)}},series:[
    {name:'成交量',type:'bar',data:kline.map(k=>({value:k.volume,itemStyle:{color:k.close>=k.open?vgrad(tc.up,.85,.15):vgrad(tc.down,.85,.15),borderRadius:[2,2,0,0]}}))},
    {name:'MAVOL5',type:'line',data:calcVolMASeries(kline.map(k=>k.volume),5),showSymbol:false,lineStyle:{width:1,color:tc.gold}},
    {name:'MAVOL10',type:'line',data:calcVolMASeries(kline.map(k=>k.volume),10),showSymbol:false,lineStyle:{width:1,color:tc.blue}}
  ]};},
  MACD:function(kline){const m=calcMACDSeries(kline.map(k=>k.close));return{title:'MACD(12,26,9)',yAxis:{scale:true,position:'right',axisLabel:{color:tc.muted,fontSize:9,formatter:v=>+v.toFixed(2)}},series:[
    {name:'MACD',type:'bar',data:m.hist.map(v=>({value:v,itemStyle:{color:v>=0?tc.up:tc.down}}))},
    {name:'DIF',type:'line',data:m.dif,showSymbol:false,lineStyle:{width:1,color:tc.gold}},
    {name:'DEA',type:'line',data:m.dea,showSymbol:false,lineStyle:{width:1,color:tc.blue}}
  ]};},
  KDJ:function(kline){const k=calcKDJSeries(kline,9);return{title:'KDJ(9,3,3)',yAxis:{scale:true,position:'right',axisLabel:{color:tc.muted,fontSize:9}},series:[
    {name:'K',type:'line',data:k.K,showSymbol:false,lineStyle:{width:1,color:tc.gold}},
    {name:'D',type:'line',data:k.D,showSymbol:false,lineStyle:{width:1,color:tc.blue}},
    {name:'J',type:'line',data:k.J,showSymbol:false,lineStyle:{width:1,color:tc.purple}}
  ]};},
  RSI:function(kline){const c=kline.map(k=>k.close);return{title:'RSI(6/12/24)',yAxis:{min:0,max:100,position:'right',axisLabel:{color:tc.muted,fontSize:9}},series:[
    {name:'RSI6',type:'line',data:calcRSISeries(c,6),showSymbol:false,lineStyle:{width:1,color:tc.gold}},
    {name:'RSI12',type:'line',data:calcRSISeries(c,12),showSymbol:false,lineStyle:{width:1,color:tc.blue}},
    {name:'RSI24',type:'line',data:calcRSISeries(c,24),showSymbol:false,lineStyle:{width:1,color:tc.purple}}
  ]};},
  WR:function(kline){return{title:'WR(14)',yAxis:{min:0,max:100,position:'right',axisLabel:{color:tc.muted,fontSize:9}},series:[
    {name:'WR14',type:'line',data:calcWRSeries(kline,14),showSymbol:false,lineStyle:{width:1,color:tc.gold},markLine:{silent:true,symbol:'none',label:{show:false},data:[{yAxis:80,lineStyle:{color:hexA(tc.down,.4),type:'dashed'}},{yAxis:20,lineStyle:{color:hexA(tc.up,.4),type:'dashed'}}]}}
  ]};},
  OBV:function(kline){return{title:'OBV',yAxis:{scale:true,position:'right',axisLabel:{color:tc.muted,fontSize:9,fontFamily:"'SF Mono',monospace",formatter:v=>v>=1e8?(v/1e8).toFixed(1)+'亿':(v>=1e4?(v/1e4).toFixed(0)+'万':v)}},series:[
    {name:'OBV',type:'line',data:calcOBVSeries(kline),showSymbol:false,lineStyle:{width:1,color:tc.gold},areaStyle:{color:hexA(tc.gold,.08)}}
  ]};}
};
function buildKlineOption(kline,cfg){
  cfg=cfg||window._klineCfg;
  const dates=kline.map(k=>k.date);
  const count=Math.max(1,Math.min(4,cfg.count||cfg.subs.length));
  const subs=(cfg.subs||['VOL','MACD','KDJ']).slice(0,count);
  while(subs.length<count)subs.push('VOL');
  const series=[
    {name:'K线',type:'candlestick',data:kline.map(k=>[k.open,k.close,k.low,k.high]),xAxisIndex:0,yAxisIndex:0,barMaxWidth:16,itemStyle:{color:tc.up,color0:tc.down,borderColor:tc.up,borderColor0:tc.down,borderWidth:1}},
    {name:'MA5',type:'line',data:kline.map(k=>k.ma5||null),xAxisIndex:0,yAxisIndex:0,smooth:true,showSymbol:false,lineStyle:{width:1.2,color:tc.gold},emphasis:{focus:'series',lineStyle:{width:2.5}}},
    {name:'MA10',type:'line',data:kline.map(k=>k.ma10||null),xAxisIndex:0,yAxisIndex:0,smooth:true,showSymbol:false,lineStyle:{width:1.2,color:tc.blue},emphasis:{focus:'series',lineStyle:{width:2.5}}},
    {name:'MA20',type:'line',data:kline.map(k=>k.ma20||null),xAxisIndex:0,yAxisIndex:0,smooth:true,showSymbol:false,lineStyle:{width:1.2,color:tc.purple},emphasis:{focus:'series',lineStyle:{width:2.5}}}
  ];
  const legendData=['K线','MA5','MA10','MA20'];
  if(cfg.boll){const b=calcBollSeries(kline.map(k=>k.close),20);legendData.push('BOLL');series.push(
    {name:'BOLL',type:'line',data:b.mid,xAxisIndex:0,yAxisIndex:0,smooth:true,showSymbol:false,lineStyle:{width:1,color:tc.blue,opacity:.5},emphasis:{focus:'series'}},
    {name:'BOLL',type:'line',data:b.up,xAxisIndex:0,yAxisIndex:0,smooth:true,showSymbol:false,lineStyle:{width:1,color:tc.muted,type:'dashed',opacity:.6},emphasis:{focus:'series'}},
    {name:'BOLL',type:'line',data:b.low,xAxisIndex:0,yAxisIndex:0,smooth:true,showSymbol:false,lineStyle:{width:1,color:tc.muted,type:'dashed',opacity:.6},emphasis:{focus:'series'}}
  );}
  const grids=[{left:'8%',right:'14%',top:'12%',height:'27%'}];
  const subStart=42,subEnd=90,gap=1.5;
  const each=(subEnd-subStart-gap*(count-1))/count;
  for(let i=0;i<count;i++)grids.push({left:'8%',right:'14%',top:(subStart+i*(each+gap)).toFixed(1)+'%',height:each.toFixed(1)+'%'});
  const xAxis=[{type:'category',data:dates,gridIndex:0,axisLabel:{show:false},axisLine:{lineStyle:{color:tc.rule}},axisTick:{show:false}}];
  const yAxis=[{scale:true,gridIndex:0,position:'left',axisLabel:{color:tc.muted,fontSize:9,fontFamily:"'SF Mono',monospace"},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}},axisLine:{show:false}}];
  const titles=[];const zoomIdx=[0];
  for(let i=0;i<count;i++){
    const gi=i+1;
    xAxis.push({type:'category',data:dates,gridIndex:gi,axisLabel:(i===count-1)?{color:tc.muted,fontSize:9}:{show:false},axisLine:{lineStyle:{color:tc.rule}},axisTick:{show:false}});
    const ind=(SUB_IND[subs[i]]||SUB_IND.VOL)(kline);
    yAxis.push(Object.assign({gridIndex:gi,axisLine:{show:false},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},ind.yAxis));
    titles.push({text:ind.title,left:'1%',top:(subStart-5+i*(each+gap)).toFixed(1)+'%',textStyle:{color:tc.muted,fontSize:10,fontWeight:600}});
    ind.series.forEach(function(s){s.xAxisIndex=gi;s.yAxisIndex=gi;series.push(s);});
    zoomIdx.push(gi);
  }
  return{
    backgroundColor:'transparent',animation:false,
    legend:{data:legendData,textStyle:{color:tc.muted,fontSize:10},top:0,left:'center',itemWidth:12,itemHeight:7,icon:'roundRect',itemGap:12},
    title:titles,
    tooltip:{trigger:'axis',axisPointer:{type:'cross',link:[{xAxisIndex:'all'}],lineStyle:{color:tc.muted,type:'dashed',width:1},crossStyle:{color:tc.muted},label:{backgroundColor:tc.bg4,color:tc.ink,fontSize:10,borderColor:tc.rule,borderWidth:1,padding:[3,6]}},backgroundColor:hexA(tc.bg2,.96),borderColor:tc.rule,borderWidth:1,padding:[10,14],textStyle:{color:tc.ink,fontSize:12},extraCssText:'border-radius:10px;box-shadow:0 10px 30px rgba(0,0,0,.35);backdrop-filter:blur(6px);line-height:1.7;'},
    grid:grids,xAxis:xAxis,yAxis:yAxis,
    dataZoom:[
      {type:'inside',xAxisIndex:zoomIdx,start:35,end:100},
      {type:'slider',xAxisIndex:zoomIdx,start:35,end:100,height:14,bottom:2,borderColor:tc.rule,backgroundColor:hexA(tc.bg3,.5),fillerColor:hexA(tc.blue,.18),handleStyle:{color:tc.bg4,borderColor:tc.muted},moveHandleStyle:{color:tc.bg4,opacity:.65},emphasis:{handleStyle:{borderColor:tc.blue},moveHandleStyle:{color:tc.blue}},dataBackground:{lineStyle:{color:tc.bg4},areaStyle:{color:hexA(tc.bg4,.3)}},selectedDataBackground:{lineStyle:{color:tc.blue},areaStyle:{color:hexA(tc.blue,.25)}},textStyle:{color:tc.muted}}
    ],
    series:series
  };
}
function renderKlineChart(kline,cid){
  const dom=document.getElementById(cid||'klineChart');if(!dom)return;
  const old=(echarts.getInstanceByDom)?echarts.getInstanceByDom(dom):null;
  const ch=old||echarts.init(dom);
  if(!window._kc)window._kc=[];window._kc.push(ch);
  dom._klineCtx={ch:ch,kline:kline};
  ch.setOption(buildKlineOption(kline,window._klineCfg),true);
  if(cid==='klineChart')renderKlineConfigBar(dom);
}
function renderKlineConfigBar(dom){
  let bar=dom._klineBar;
  if(!bar){bar=document.createElement('div');bar.style.cssText='display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:0 0 8px;font-size:12px;color:var(--muted);';dom.parentNode.insertBefore(bar,dom);dom._klineBar=bar;}
  const cfg=window._klineCfg;
  let h='<span>副图数量:</span>';
  for(let n=1;n<=4;n++){h+='<button data-kf-count="'+n+'" style="'+(cfg.count===n?'background:var(--gold);color:#000;':'background:var(--bg3);color:var(--ink);')+'border:none;border-radius:6px;padding:2px 10px;cursor:pointer;font-size:12px;font-weight:700;">'+n+'</button>';}
  h+='<span style="margin-left:6px;">副图指标:</span>';
  for(let i=0;i<cfg.count;i++){
    h+='<select data-kf-slot="'+i+'" style="background:var(--bg3);color:var(--ink);border:1px solid var(--rule);border-radius:6px;padding:2px 6px;font-size:12px;cursor:pointer;">';
    for(let j=0;j<KF_ORDER.length;j++){const key=KF_ORDER[j];h+='<option value="'+key+'"'+(cfg.subs[i]===key?' selected':'')+'>'+KF_LABEL[key]+'</option>';}
    h+='</select>';
  }
  h+='<label style="margin-left:6px;display:flex;align-items:center;gap:4px;cursor:pointer;"><input type="checkbox" data-kf-boll '+(cfg.boll?'checked':'')+' style="accent-color:var(--gold);cursor:pointer;"> BOLL叠加</label>';
  bar.innerHTML=h;
  bar.querySelectorAll('[data-kf-count]').forEach(function(b){b.onclick=function(){window._klineCfg.count=parseInt(b.getAttribute('data-kf-count'),10);while(window._klineCfg.subs.length<window._klineCfg.count)window._klineCfg.subs.push('VOL');window._klineCfg.subs=window._klineCfg.subs.slice(0,window._klineCfg.count);saveKlineCfg();rebuildKline(dom);renderKlineConfigBar(dom);};});
  bar.querySelectorAll('[data-kf-slot]').forEach(function(sel){sel.onchange=function(){window._klineCfg.subs[parseInt(sel.getAttribute('data-kf-slot'),10)]=sel.value;saveKlineCfg();rebuildKline(dom);};});
  const boll=bar.querySelector('[data-kf-boll]');if(boll)boll.onchange=function(){window._klineCfg.boll=boll.checked;saveKlineCfg();rebuildKline(dom);};
}
function rebuildKline(dom){const ctx=dom._klineCtx;if(ctx&&ctx.ch)ctx.ch.setOption(buildKlineOption(ctx.kline,window._klineCfg),true);}

// ===== Fuzzy Search =====
function fuzzyMatch(text,query){
  text=String(text).toLowerCase();query=query.toLowerCase();
  if(text.includes(query))return true;
  let ti=0;
  for(let qi=0;qi<query.length;qi++){
    ti=text.indexOf(query[qi],ti);
    if(ti===-1)return false;
    ti++;
  }
  return true;
}
function searchStocks(query){
  if(!query||query.trim().length<1)return[];
  query=query.trim();
  const results=[];
  for(const s of scanData){
    const pinyin=pinyinMap[s.code]||'';
    if(fuzzyMatch(s.code,query)||fuzzyMatch(s.name,query)||fuzzyMatch(pinyin,query)||fuzzyMatch(s.industry,query)){
      results.push(s);
    }
    if(results.length>=10)break;
  }
  return results;
}
// ===== Full Market Search via East Money API (JSONP to bypass CORS) =====
let apiSuggestTimer=null;
function jsonpRequest(url,timeout){
  return new Promise((resolve,reject)=>{
    const cbName='jsonp_cb_'+Date.now()+'_'+Math.floor(Math.random()*100000);
    const script=document.createElement('script');
    const timer=setTimeout(()=>{cleanup();reject(new Error('timeout'));},timeout||5000);
    function cleanup(){clearTimeout(timer);delete window[cbName];if(script.parentNode)script.parentNode.removeChild(script);}
    window[cbName]=function(data){cleanup();resolve(data);};
    script.onerror=function(){cleanup();reject(new Error('script error'));};
    const sep=url.indexOf('?')>=0?'&':'?';
    script.src=url+sep+'cb='+cbName;
    document.head.appendChild(script);
  });
}
async function searchAllStocks(query){
  try{
    const data=await jsonpRequest('https://searchapi.eastmoney.com/api/suggest/get?input='+encodeURIComponent(query)+'&type=14&token=D43BF722C8E33BDC906FB84D85E326E8',5000);
    if(data&&data.QuotationCodeTable&&data.QuotationCodeTable.Data){
      return data.QuotationCodeTable.Data.filter(s=>s.SecurityType=='2'||s.SecurityType=='1').map(s=>({
        code:s.Code,name:s.Name,mkt:parseInt(s.MktNum)||0,inTop50:false
      }));
    }
  }catch(e){console.log('API search skipped:',e.message);}
  return [];
}
// ===== Diagnosis =====
function diagnoseStock(){
  const input=document.getElementById('diagInput').value.trim();if(!input)return;
  const localResults=searchStocks(input);
  if(localResults.length>0){
    const s=localResults[0];
    addSearchHistory(s.code,s.name,1);
    showDetail(s.code);
    document.getElementById('diagInput').value='';
    document.getElementById('diagSuggest').style.display='none';
    return;
  }
  searchAllStocks(input).then(apiResults=>{
    if(apiResults.length>0){
      const r=apiResults[0];
      addSearchHistory(r.code,r.name,r.mkt);
      showDetailEx(r.code,r.name,r.mkt);
    }else{
      alert('未找到匹配的股票：'+input+'\\n请检查代码或名称是否正确。');
    }
  });
  document.getElementById('diagInput').value='';
  document.getElementById('diagSuggest').style.display='none';
}
// ===== Search History =====
let searchHistory=[];
try{searchHistory=JSON.parse(localStorage.getItem('stock-search-history')||'[]');}catch(e){searchHistory=[];}
function addSearchHistory(code,name,mkt){
  searchHistory=searchHistory.filter(h=>h.code!==code);
  searchHistory.unshift({code:code,name:name,mkt:mkt,time:Date.now()});
  if(searchHistory.length>10)searchHistory=searchHistory.slice(0,10);
  try{localStorage.setItem('stock-search-history',JSON.stringify(searchHistory));}catch(e){}
}
function clearSearchHistory(){
  searchHistory=[];
  try{localStorage.removeItem('stock-search-history');}catch(e){}
  var inp=document.getElementById('diagInput');
  if(inp&&!inp.value.trim())showDiagSuggestions();
}
function selectFromHistory(code,name,mkt){
  var s=scanData.find(x=>x.code===code);
  if(s){selectDiag(code);}else{selectDiagEx(code,name,mkt);}
}
function renderSearchHistory(){
  if(searchHistory.length===0)return'';
  var h='<div style="padding:6px 12px;font-size:11px;color:var(--muted);border-bottom:1px solid var(--rule);display:flex;justify-content:space-between;align-items:center;">搜索历史<span style="cursor:pointer;color:var(--muted);font-size:10px;" onclick="clearSearchHistory()">清除</span></div>';
  h+=searchHistory.map(function(item){
    return'<div style="padding:8px 12px;cursor:pointer;border-bottom:1px solid var(--rule);display:flex;justify-content:space-between;align-items:center;" onmouseenter="this.style.background=\'var(--bg3)\'" onmouseleave="this.style.background=\'\'" onclick="selectFromHistory(\''+item.code+'\',\''+item.name.replace(/'/g,'')+'\','+item.mkt+')"><div><span style="font-weight:700;">'+item.name+'</span> <span style="color:var(--muted);font-size:12px;">'+item.code+'</span></div><div style="color:var(--muted);font-size:10px;">'+(item.mkt===1?'沪':'深')+'</div></div>';
  }).join('');
  return h;
}
function showDiagSuggestions(){
  const input=document.getElementById('diagInput').value.trim();
  const box=document.getElementById('diagSuggest');
  if(input.length<1){
    var histHtml=renderSearchHistory();
    if(histHtml){box.innerHTML=histHtml;box.style.display='block';}else{box.style.display='none';}
    return;
  }
  // Show local results immediately
  const localResults=searchStocks(input);
  const localCodes=new Set(localResults.map(s=>s.code));
  let html='';
  if(localResults.length>0){
    html+=localResults.map(s=>{
      const pc=s.pct_change>=0?'pu':'pd';
      return '<div style="padding:8px 12px;cursor:pointer;border-bottom:1px solid var(--rule);display:flex;justify-content:space-between;align-items:center;" onmouseenter="this.style.background=\'var(--bg3)\'" onmouseleave="this.style.background=\'\'" onclick="selectDiag(\''+s.code+'\')"><div><span style="font-weight:700;">'+s.name+'</span> <span style="color:var(--muted);font-size:12px;">'+s.code+'</span> <span class="badge buy" style="font-size:9px;padding:1px 5px;">Top50</span></div><div><span class="'+pc+'" style="font-size:13px;font-weight:600;">'+s.latest_price.toFixed(2)+'</span> <span class="'+pc+'" style="font-size:11px;">'+(s.pct_change>=0?'+':'')+s.pct_change.toFixed(2)+'%</span></div></div>';
    }).join('');
  }
  box.innerHTML=html||'<div style="padding:10px;color:var(--muted);font-size:13px;">搜索中...</div>';
  box.style.display='block';
  // Debounced API search
  clearTimeout(apiSuggestTimer);
  apiSuggestTimer=setTimeout(async()=>{
    const apiResults=await searchAllStocks(input);
    const newResults=apiResults.filter(s=>!localCodes.has(s.code)).slice(0,7);
    if(newResults.length>0){
      const apiHtml=newResults.map(s=>{
        return '<div style="padding:8px 12px;cursor:pointer;border-bottom:1px solid var(--rule);display:flex;justify-content:space-between;align-items:center;" onmouseenter="this.style.background=\'var(--bg3)\'" onmouseleave="this.style.background=\'\'" onclick="selectDiagEx(\''+s.code+'\',\''+s.name+'\','+s.mkt+')"><div><span style="font-weight:700;">'+s.name+'</span> <span style="color:var(--muted);font-size:12px;">'+s.code+'</span> <span class="badge watch" style="font-size:9px;padding:1px 5px;">全市场</span></div><div style="color:var(--muted);font-size:11px;">点击查看详情</div></div>';
      }).join('');
      box.innerHTML=html+apiHtml;
    }else if(localResults.length===0){
      box.innerHTML='<div style="padding:10px;color:var(--muted);font-size:13px;">无匹配结果</div>';
    }
  },300);
}
function selectDiag(code){
  const s=scanData.find(x=>x.code===code);
  if(s)addSearchHistory(code,s.name,1);
  showDetail(code);
  document.getElementById('diagInput').value='';
  document.getElementById('diagSuggest').style.display='none';
}
function selectDiagEx(code,name,mkt){
  addSearchHistory(code,name,mkt);
  showDetailEx(code,name,mkt);
  document.getElementById('diagInput').value='';
  document.getElementById('diagSuggest').style.display='none';
}
// ===== Detail for stocks NOT in Top50 =====
function fetchTencentQuote(code){
  return new Promise((resolve,reject)=>{
    const pfx=code.startsWith('6')?'sh':'sz';
    const varName='v_'+pfx+code;
    const script=document.createElement('script');
    script.charset='gbk';
    script.src='https://qt.gtimg.cn/q='+pfx+code;
    const timer=setTimeout(()=>{cleanup();reject(new Error('timeout'));},5000);
    function cleanup(){clearTimeout(timer);if(script.parentNode)script.parentNode.removeChild(script);}
    script.onload=function(){cleanup();const raw=window[varName];if(raw){const p=raw.split('~');resolve({price:parseFloat(p[3])||0,prevClose:parseFloat(p[4])||0,open:parseFloat(p[5])||0,vol:parseFloat(p[6])||0,pct:parseFloat(p[32])||0,high:parseFloat(p[33])||0,low:parseFloat(p[34])||0,amount:(parseFloat(p[37])||0)*1e4,pe:parseFloat(p[39])||0,mktcap:parseFloat(p[44])||0,pb:parseFloat(p[46])||0});}else{reject(new Error('no data'));}};
    script.onerror=function(){cleanup();reject(new Error('script error'));};
    document.head.appendChild(script);
  });
}
// ===== Technical Indicator Helpers =====
function calcMA(arr,p){if(arr.length<p)return 0;let s=0;for(let i=arr.length-p;i<arr.length;i++)s+=arr[i];return s/p;}
function calcRSI(closes,p){if(closes.length<p+1)return 50;let g=0,l=0;for(let i=closes.length-p;i<closes.length;i++){const d=closes[i]-closes[i-1];if(d>0)g+=d;else l-=d;}const ag=g/p,al=l/p;return ag+al===0?50:100*ag/(ag+al);}
function calcEMA(arr,p){if(arr.length===0)return 0;let ema=arr[0];const k=2/(p+1);for(let i=1;i<arr.length;i++)ema=arr[i]*k+ema*(1-k);return ema;}
function calcMACD(closes){const ema12=calcEMA(closes.slice(-60),12);const ema26=calcEMA(closes.slice(-60),26);const dif=ema12-ema26;const dea=dif*0.8;const hist=2*(dif-dea);return{dif,dea,hist};}
function calcBoll(closes,p){if(closes.length<p)return{upper:0,mid:0,lower:0,pos:50};const ma=calcMA(closes,p);let s=0;for(let i=closes.length-p;i<closes.length;i++)s+=Math.pow(closes[i]-ma,2);const sd=Math.sqrt(s/p);const last=closes[closes.length-1];return{upper:ma+2*sd,mid:ma,lower:ma-2*sd,pos:sd>0?Math.max(0,Math.min(100,(last-ma)/(2*sd)*100+50)):50};}
function calcKDJ(kline,n){if(kline.length<n)return{k:50,d:50,j:50};let k=50,d=50;for(let i=kline.length-n;i<kline.length;i++){let hh=kline[i].high,ll=kline[i].low;for(let j=Math.max(0,i-n+1);j<=i;j++){hh=Math.max(hh,kline[j].high);ll=Math.min(ll,kline[j].low);}const rsv=hh>ll?(kline[i].close-ll)/(hh-ll)*100:50;k=2/3*k+1/3*rsv;d=2/3*d+1/3*k;}return{k,d,j:3*k-2*d};}
function calcATR(kline,p){if(kline.length<p+1)return 0;let s=0;for(let i=kline.length-p;i<kline.length;i++){const tr=Math.max(kline[i].high-kline[i].low,Math.abs(kline[i].high-kline[i-1].close),Math.abs(kline[i].low-kline[i-1].close));s+=tr;}return s/p;}
function calcVolRatio(kline){if(kline.length<6)return 1;const today=kline[kline.length-1].volume;let s=0;for(let i=kline.length-6;i<kline.length-1;i++)s+=kline[i].volume;return s>0?today/(s/5):1;}
function calcADX(kline,p){if(kline.length<p*2+1)return 25;let dmP=0,dmM=0,tr=0;for(let i=kline.length-p;i<kline.length;i++){const up=kline[i].high-kline[i-1].high;const dn=kline[i-1].low-kline[i].low;const tr1=Math.max(kline[i].high-kline[i].low,Math.abs(kline[i].high-kline[i-1].close),Math.abs(kline[i].low-kline[i-1].close));tr+=tr1;if(up>dn&&up>0)dmP+=up;if(dn>up&&dn>0)dmM+=dn;}if(tr===0)return 25;const diP=100*dmP/tr,diM=100*dmM/tr;return diP+diM>0?100*Math.abs(diP-diM)/(diP+diM):0;}
// ===== 时间序列版技术指标（用于副图绘制）=====
function calcMACDSeries(closes){const dif=[],dea=[],hist=[];let e12=closes[0],e26=closes[0],d9=0;for(let i=0;i<closes.length;i++){if(i>0){e12=e12*11/13+closes[i]*2/13;e26=e26*25/27+closes[i]*2/27;}const d=e12-e26;if(i===0){d9=d;}else{d9=d9*0.8+d*0.2;}dif.push(+d.toFixed(3));dea.push(+d9.toFixed(3));hist.push(+((d-d9)*2).toFixed(3));}return{dif,dea,hist};}
function calcKDJSeries(kline,n){n=n||9;const K=[],D=[],J=[];let k=50,d=50;for(let i=0;i<kline.length;i++){let hh=-1e18,ll=1e18;for(let j=Math.max(0,i-n+1);j<=i;j++){hh=Math.max(hh,kline[j].high);ll=Math.min(ll,kline[j].low);}const rsv=hh>ll?(kline[i].close-ll)/(hh-ll)*100:50;k=k*2/3+rsv/3;d=d*2/3+k/3;K.push(+k.toFixed(2));D.push(+d.toFixed(2));J.push(+(3*k-2*d).toFixed(2));}return{K,D,J};}
function calcRSISeries(closes,n){n=n||14;const R=[];let g=0,l=0;for(let i=0;i<closes.length;i++){if(i===0){R.push(50);continue;}const d=closes[i]-closes[i-1];if(i<=n){if(d>0)g+=d;else l-=d;if(i===n)R.push(g+l===0?50:100*g/(g+l));else R.push(50);}else{g=(g*(n-1)+(d>0?d:0))/n;l=(l*(n-1)+(d<0?-d:0))/n;R.push(+((g+l===0?50:100*g/(g+l))).toFixed(2));}}return R;}
function calcBollSeries(closes,n){n=n||20;const up=[],mid=[],low=[];for(let i=0;i<closes.length;i++){const s=Math.max(0,i-n+1);let sum=0;for(let j=s;j<=i;j++)sum+=closes[j];const ma=sum/(i-s+1);let v=0;for(let j=s;j<=i;j++)v+=Math.pow(closes[j]-ma,2);const sd=Math.sqrt(v/(i-s+1));up.push(+(ma+2*sd).toFixed(3));mid.push(+ma.toFixed(3));low.push(+(ma-2*sd).toFixed(3));}return{up,mid,low};}
function calcVolMASeries(vols,p){const r=[];for(let i=0;i<vols.length;i++){const s=Math.max(0,i-p+1);let sum=0;for(let j=s;j<=i;j++)sum+=vols[j];r.push(+((sum/(i-s+1))).toFixed(0));}return r;}
function calcWRSeries(kline,n){n=n||14;const W=[];for(let i=0;i<kline.length;i++){const s=Math.max(0,i-n+1);let hh=-1e18,ll=1e18;for(let j=s;j<=i;j++){hh=Math.max(hh,kline[j].high);ll=Math.min(ll,kline[j].low);}W.push(hh>ll?+((hh-kline[i].close)/(hh-ll)*100).toFixed(2):50);}return W;}
function calcOBVSeries(kline){const O=[];let obv=0;for(let i=0;i<kline.length;i++){if(i===0)obv=kline[i].volume;else{if(kline[i].close>kline[i-1].close)obv+=kline[i].volume;else if(kline[i].close<kline[i-1].close)obv-=kline[i].volume;}O.push(obv);}return O;}
function detectExSignals(kl,rsi,macd,boll,kdj,adx,volRatio){
  const sigs=[];if(kl.length<5)return sigs;const last=kl.length-1;const t=kl[last],p=kl[last-1];
  if(kl.length>=20&&t.close>t.open&&t.close>kl[last].ma20&&volRatio>1.5)sigs.push({indicator:'出水芙蓉',type:'看涨',date:t.date,section:'实时',desc:'放量阳线突破20日均线'});
  if(macd.hist>0&&macd.dif>macd.dea)sigs.push({indicator:'MACD多头',type:'看涨',date:t.date,section:'实时',desc:'MACD柱状图为正，DIF在DEA之上'});
  if(macd.hist<0&&macd.dif<macd.dea)sigs.push({indicator:'MACD空头',type:'看跌',date:t.date,section:'实时',desc:'MACD柱状图为负，DIF在DEA之下'});
  if(rsi>70)sigs.push({indicator:'RSI超买',type:'看跌',date:t.date,section:'实时',desc:'RSI('+rsi.toFixed(1)+')超过70，短期有回调风险'});
  else if(rsi<30)sigs.push({indicator:'RSI超卖',type:'看涨',date:t.date,section:'实时',desc:'RSI('+rsi.toFixed(1)+')低于30，有反弹可能'});
  if(boll.pos>85)sigs.push({indicator:'布林上轨',type:'看跌',date:t.date,section:'实时',desc:'股价接近布林上轨('+boll.pos.toFixed(0)+'%)，注意回调'});
  else if(boll.pos<15)sigs.push({indicator:'布林下轨',type:'看涨',date:t.date,section:'实时',desc:'股价接近布林下轨('+boll.pos.toFixed(0)+'%)，关注反弹'});
  if(kdj.j>100)sigs.push({indicator:'KDJ超买',type:'看跌',date:t.date,section:'实时',desc:'J值('+kdj.j.toFixed(1)+')超过100'});
  else if(kdj.j<0)sigs.push({indicator:'KDJ超卖',type:'看涨',date:t.date,section:'实时',desc:'J值('+kdj.j.toFixed(1)+')低于0'});
  if(volRatio>2)sigs.push({indicator:'放量',type:'看涨',date:t.date,section:'实时',desc:'量比('+volRatio.toFixed(2)+')大于2，资金活跃'});
  if(adx>25)sigs.push({indicator:'趋势增强',type:'看涨',date:t.date,section:'实时',desc:'ADX('+adx.toFixed(1)+')大于25，趋势明显'});
  if(t.close>kl[last].ma5&&kl[last].ma5>kl[last].ma10&&kl[last].ma10>kl[last].ma20)sigs.push({indicator:'均线多头排列',type:'看涨',date:t.date,section:'实时',desc:'MA5>MA10>MA20，多头排列'});
  if(t.close<kl[last].ma5&&kl[last].ma5<kl[last].ma10&&kl[last].ma10<kl[last].ma20)sigs.push({indicator:'均线空头排列',type:'看跌',date:t.date,section:'实时',desc:'MA5<MA10<MA20，空头排列'});
  if(t.close<p.close&&t.volume>p.volume&&p.close>kl[last-2].close)sigs.push({indicator:'放量下跌',type:'看跌',date:t.date,section:'实时',desc:'今日放量下跌，注意风险'});
  if(t.close>maxO(kl,5)&&t.volume>p.volume)sigs.push({indicator:'平台突破',type:'看涨',date:t.date,section:'实时',desc:'突破5日平台高点且放量'});
  return sigs;
}
function maxO(kl,n){let m=0;for(let i=kl.length-n;i<kl.length-1;i++)m=Math.max(m,kl[i].high);return m;}
async function showDetailEx(code,name,mkt){
  const prefix=mkt===1?'1':'0';
  const secid=prefix+'.'+code;
  const w=isWatched(code);
  document.getElementById('detailContent').innerHTML='<div style="text-align:center;padding:60px;color:var(--muted);"><div style="font-size:24px;margin-bottom:12px;">'+name+' '+code+'</div><div style="font-size:14px;">正在加载个股数据...</div></div>';
  openOverlay('detailOverlay');
  // Fetch real-time quote
  let price=0,pct=0,open=0,high=0,low=0,vol=0,amount=0,pe=0,pb=0,mktcap=0;
  try{
    const data=await jsonpRequest('https://push2.eastmoney.com/api/qt/stock/get?secid='+secid+'&fltt=2&fields=f43,f44,f45,f46,f47,f48,f57,f58,f84,f85,f116,f117,f162,f167,f169,f170',5000);
    if(data&&data.data){const d=data.data;price=d.f43||0;pct=d.f170||0;open=d.f46||0;high=d.f44||0;low=d.f45||0;vol=d.f47||0;amount=d.f48||0;mktcap=(d.f116||0)/1e8;pe=d.f162||0;pb=d.f167||0;}
    if(price===0)throw new Error('empty data');
  }catch(e){
    try{const q=await fetchTencentQuote(code);price=q.price;pct=q.pct;open=q.open;high=q.high;low=q.low;vol=q.vol;amount=q.amount;pe=q.pe;pb=q.pb;mktcap=q.mktcap;}catch(e2){}
  }
  // Fetch K-line data
  let kline=[];
  try{
    const klData=await jsonpRequest('https://push2his.eastmoney.com/api/qt/stock/kline/get?secid='+secid+'&fields1=f1,f2,f3,f4,f5,f6&fields2=f51,f52,f53,f54,f55,f56,f57,f58&klt=101&fqt=1&beg=0&end=20500101&lmt=120',8000);
    if(klData&&klData.data&&klData.data.klines){
      kline=klData.data.klines.map(function(line){const p=line.split(',');return{date:p[0],open:parseFloat(p[1]),close:parseFloat(p[2]),high:parseFloat(p[3]),low:parseFloat(p[4]),volume:parseFloat(p[5]),ma5:0,ma10:0,ma20:0};});
      for(let i=0;i<kline.length;i++){const s5=kline.slice(Math.max(0,i-4),i+1).map(k=>k.close);const s10=kline.slice(Math.max(0,i-9),i+1).map(k=>k.close);const s20=kline.slice(Math.max(0,i-19),i+1).map(k=>k.close);kline[i].ma5=calcMA(s5,5);kline[i].ma10=calcMA(s10,10);kline[i].ma20=calcMA(s20,20);}
    }
  }catch(e){console.log('K-line fetch failed:',e.message);}
  // Calculate indicators
  const closes=kline.map(k=>k.close);
  const rsi=calcRSI(closes,14);
  const macd=calcMACD(closes);
  const boll=calcBoll(closes,20);
  const kdj=calcKDJ(kline,9);
  const adx=calcADX(kline,14);
  const atr=calcATR(kline,14);
  const volRatio=calcVolRatio(kline);
  const atrPct=kline.length>0&&kline[kline.length-1].close>0?(atr/kline[kline.length-1].close*100):0;
  const signals=detectExSignals(kline,rsi,macd,boll,kdj,adx,volRatio);
  const bull=signals.filter(s=>s.type==='看涨').length;
  const bear=signals.filter(s=>s.type==='看跌').length;
  const sentiment=bull-bear;
  const cls=sentiment>2?'strong_bull':(sentiment>0?'bull':(sentiment<-2?'strong_bear':(sentiment<0?'bear':'neutral')));
  const clsName=sentiment>2?'强势看涨':(sentiment>0?'看涨':(sentiment<-2?'强势看跌':(sentiment<0?'看跌':'中性')));
  const clsBadge=cls==='strong_bull'||cls==='bull'?'buy':(cls==='strong_bear'||cls==='bear'?'sell':'watch');
  const pc=pct>=0?'pu':'pd';
  const ps=pct>=0?'+':'';
  const watchBtn=w?'<button style="background:var(--gold);color:#000;font-weight:700;padding:8px 20px;font-size:13px;border:none;border-radius:8px;cursor:pointer;" onclick="toggleWatch(\''+code+'\');showDetailEx(\''+code+'\',\''+name+'\','+mkt+')">★ 已关注</button>':'<button style="background:var(--bg3);color:var(--ink);font-weight:600;padding:8px 20px;font-size:13px;border:1px solid var(--rule);border-radius:8px;cursor:pointer;" onclick="toggleWatch(\''+code+'\');showDetailEx(\''+code+'\',\''+name+'\','+mkt+')">☆ 添加关注</button>';
  // Signal timeline
  const tl=signals.map(function(sig){const c=sig.type==='看涨'?'up':(sig.type==='看跌'?'down':'gold');return '<div class="sig-tl-item"><div class="sig-tl-dot" style="background:var(--'+c+');"></div><span style="color:var(--muted);">'+sig.date+'</span><span class="badge '+(sig.type==='看涨'?'buy':(sig.type==='看跌'?'sell':'watch'))+'">'+sig.type+'</span><span style="font-weight:600;">'+sig.indicator+'</span><span style="color:var(--muted);font-size:11px;">'+sig.section+'</span></div>';}).join('');
  let html='<div style="margin-bottom:16px;display:flex;justify-content:space-between;align-items:center;"><div><h2 style="display:flex;align-items:center;gap:10px;">'+name+' <span style="color:var(--muted);font-size:16px;font-weight:400;">'+code+'</span> <span class="badge '+clsBadge+'">'+clsName+'</span> <span class="badge watch">全市场</span></h2><div style="display:flex;gap:20px;margin-top:8px;font-size:14px;"><span>最新价: <b class="'+pc+'" style="font-size:18px;">'+price.toFixed(2)+'</b></span><span>涨跌幅: <b class="'+pc+'">'+ps+pct.toFixed(2)+'%</b></span><span>市值: <b>'+(mktcap>0?mktcap.toFixed(0)+'亿':'--')+'</b></span><span>PE: <b>'+pe.toFixed(1)+'</b></span><span>PB: <b>'+pb.toFixed(2)+'</b></span></div></div>'+watchBtn+'</div>';
  // Trading signal box
  const entryPrice=price;
  const stopLoss=atr>0?price-2*atr:0;
  const targetPrice=atr>0?price+3*atr:0;
  const pot=targetPrice>0?((targetPrice-entryPrice)/entryPrice*100).toFixed(1):'--';
  const risk=stopLoss>0?((entryPrice-stopLoss)/entryPrice*100).toFixed(1):'--';
  html+='<div class="dt-section" style="border-left:3px solid var(--up);"><h4><span style="width:3px;height:14px;background:var(--up);border-radius:2px;display:inline-block;"></span>交易信号</h4>';
  html+='<div style="display:flex;gap:8px;margin-bottom:8px;"><span class="badge '+(sentiment>0?'buy':(sentiment<0?'sell':'watch'))+'">'+(sentiment>0?'买入':(sentiment<0?'卖出':'观望'))+'</span><span style="color:var(--muted);font-size:12px;">信号强度: '+sentiment+' (多'+bull+'/空'+bear+')</span></div>';
  html+='<div class="dt-grid">';
  html+='<div class="dt-cell"><div class="v pu">'+entryPrice.toFixed(2)+'</div><div class="l">入场价</div></div>';
  if(stopLoss>0)html+='<div class="dt-cell"><div class="v pd">'+stopLoss.toFixed(2)+'</div><div class="l">止损价</div></div>';
  if(targetPrice>0)html+='<div class="dt-cell"><div class="v" style="color:var(--gold);">'+targetPrice.toFixed(2)+'</div><div class="l">目标价</div></div>';
  html+='<div class="dt-cell"><div class="v">50%</div><div class="l">建议仓位</div></div>';
  if(targetPrice>0)html+='<div class="dt-cell"><div class="v pu">+'+pot+'%</div><div class="l">潜在收益</div></div>';
  if(stopLoss>0)html+='<div class="dt-cell"><div class="v pd">-'+risk+'%</div><div class="l">风险幅度</div></div>';
  html+='</div>';
  html+='<div style="margin-top:8px;padding:10px;background:var(--bg2);border-radius:8px;font-size:12px;color:var(--muted);line-height:1.6;">基于ATR('+atr.toFixed(2)+')动态计算止损/目标价位。入场价取最新价，止损=2倍ATR，目标=3倍ATR，盈亏比约1.5:1。</div>';
  html+='</div>';
  html+=execPlanHtml({entry_price:entryPrice,stop_loss:stopLoss,target_price:targetPrice});
  html+='<div id="t0DetailBox"></div>';
  // Real-time quote
  html+='<div class="dt-section"><h4><span style="width:3px;height:14px;background:var(--gold);border-radius:2px;display:inline-block;"></span>实时行情</h4><div class="dt-grid">';
  html+='<div class="dt-cell"><div class="v '+pc+'">'+price.toFixed(2)+'</div><div class="l">最新价</div></div>';
  html+='<div class="dt-cell"><div class="v">'+open.toFixed(2)+'</div><div class="l">今开</div></div>';
  html+='<div class="dt-cell"><div class="v pu">'+high.toFixed(2)+'</div><div class="l">最高</div></div>';
  html+='<div class="dt-cell"><div class="v pd">'+low.toFixed(2)+'</div><div class="l">最低</div></div>';
  html+='<div class="dt-cell"><div class="v">'+(vol/10000).toFixed(0)+'万</div><div class="l">成交量(手)</div></div>';
  html+='<div class="dt-cell"><div class="v">'+(amount/1e8).toFixed(2)+'亿</div><div class="l">成交额</div></div>';
  html+='</div></div>';
  // Technical indicators
  html+='<div class="dt-section"><h4><span style="width:3px;height:14px;background:var(--gold);border-radius:2px;display:inline-block;"></span>技术指标</h4><div class="dt-grid">';
  html+='<div class="dt-cell"><div class="v '+(rsi>=70?'pd':(rsi<=30?'pu':''))+'">'+rsi.toFixed(1)+'</div><div class="l">RSI(14)</div></div>';
  html+='<div class="dt-cell"><div class="v '+(macd.hist>0?'pu':'pd')+'">'+macd.dif.toFixed(2)+'</div><div class="l">MACD-DIF</div></div>';
  html+='<div class="dt-cell"><div class="v '+(macd.hist>0?'pu':'pd')+'">'+macd.hist.toFixed(2)+'</div><div class="l">MACD柱</div></div>';
  html+='<div class="dt-cell"><div class="v '+(boll.pos>85?'pd':(boll.pos<15?'pu':''))+'">'+boll.pos.toFixed(0)+'%</div><div class="l">布林位置</div></div>';
  html+='<div class="dt-cell"><div class="v '+(kdj.j>100?'pd':(kdj.j<0?'pu':''))+'">'+kdj.j.toFixed(1)+'</div><div class="l">KDJ-J</div></div>';
  html+='<div class="dt-cell"><div class="v '+(adx>25?'pu':'')+'">'+adx.toFixed(1)+'</div><div class="l">ADX(14)</div></div>';
  html+='<div class="dt-cell"><div class="v '+(volRatio>1.5?'pu':'')+'">'+volRatio.toFixed(2)+'</div><div class="l">量比</div></div>';
  html+='<div class="dt-cell"><div class="v">'+atrPct.toFixed(2)+'%</div><div class="l">ATR波幅</div></div>';
  if(kline.length>0){const last=kline[kline.length-1];html+='<div class="dt-cell"><div class="v">'+last.ma5.toFixed(2)+'</div><div class="l">MA5</div></div>';html+='<div class="dt-cell"><div class="v">'+last.ma10.toFixed(2)+'</div><div class="l">MA10</div></div>';html+='<div class="dt-cell"><div class="v">'+last.ma20.toFixed(2)+'</div><div class="l">MA20</div></div>';}
  html+='</div></div>';
  // Valuation
  html+='<div class="dt-section"><h4><span style="width:3px;height:14px;background:var(--blue);border-radius:2px;display:inline-block;"></span>估值数据</h4><div class="dt-grid">';
  html+='<div class="dt-cell"><div class="v">'+pe.toFixed(1)+'</div><div class="l">市盈率(PE)</div></div>';
  html+='<div class="dt-cell"><div class="v">'+pb.toFixed(2)+'</div><div class="l">市净率(PB)</div></div>';
  html+='<div class="dt-cell"><div class="v gold">'+(mktcap>0?mktcap.toFixed(0)+'亿':'--')+'</div><div class="l">总市值</div></div>';
  html+='</div></div>';
  // Signal timeline
  html+='<div class="dt-section"><h4><span style="width:3px;height:14px;background:var(--up);border-radius:2px;display:inline-block;"></span>信号触发时间轴（'+signals.length+'个信号）</h4>';
  html+='<div class="sig-timeline">'+(tl||'<div style="padding:8px;color:var(--muted);font-size:12px;">暂无触发信号</div>')+'</div></div>';
  // K-line chart
  if(kline.length>0)html+='<div class="dt-section"><h4><span style="width:3px;height:14px;background:var(--gold);border-radius:2px;display:inline-block;"></span>K线图（MA/BOLL + 成交量/MACD/KDJ）</h4><div id="klineChart" style="width:100%;height:460px;"></div></div>';
  // Watch analysis
  html+='<div class="dt-section" style="border-left:3px solid var(--gold);"><h4><span style="width:3px;height:14px;background:var(--gold);border-radius:2px;display:inline-block;"></span>关注分析</h4>';
  html+='<div class="watch-advice"><strong style="color:var(--gold);">关注说明：</strong>该股票不在当前Top50指标触发列表中，数据通过实时API获取并计算。<br><strong style="color:var(--up);">操作建议：</strong>'+(w?'该股已在关注列表中，可在导航栏"我关注的"查看详细分析。':'点击上方"添加关注"按钮，将该股加入关注列表。')+'</div></div>';
  document.getElementById('detailContent').innerHTML=html;
  openOverlay('detailOverlay');
  if(kline.length>0)setTimeout(function(){renderKlineChart(kline);},100);
  loadT0ForDetail(code);
}
document.addEventListener('DOMContentLoaded',function(){
  const inp=document.getElementById('diagInput');
  if(inp){
    inp.addEventListener('input',showDiagSuggestions);
    inp.addEventListener('keydown',function(e){if(e.key==='Enter'){e.preventDefault();diagnoseStock();}});
    inp.addEventListener('blur',function(){setTimeout(function(){document.getElementById('diagSuggest').style.display='none';},200);});
    inp.addEventListener('focus',function(){if(inp.value.trim().length>=1)showDiagSuggestions();});
  }
});

// ===== Ticker =====
function renderTicker(){
  const items=IDX_SECIDS.map(secid=>{
    const code=secid.split('.')[1];const name=IDX_MAP[code];
    const rt=realtimeIndices[code];
    let price=0,pct=0;
    if(rt){price=rt.price;pct=rt.pct;}
    else if(marketTrend&&marketTrend.indices){
      const idx=marketTrend.indices.find(i=>i.name===name);
      if(idx){price=idx.index_close||0;pct=idx.index_change||0;}
    }
    const pc=pct>=0?'up':'down';
    return '<span class="ticker-item" style="cursor:pointer;" onclick="showIndexDetail(\''+code+'\')"><span style="font-weight:600;">'+name+'</span><span style="color:var(--'+pc+');">'+(price||0).toFixed(2)+'</span><span style="color:var(--'+pc+');font-size:11px;">'+(pct>=0?'+':'')+(pct||0).toFixed(2)+'%</span></span>';
  }).join('');
  document.getElementById('tickerTrack').innerHTML=items+items;
}
async function showIndexDetail(code){
  const name=IDX_MAP[code]||code;
  const secid=IDX_SECIDS.find(s=>s.endsWith('.'+code));
  if(!secid)return;
  const rt=realtimeIndices[code]||{};
  let price=rt.price||0,pct=rt.pct||0;
  let ma5=0,ma10=0,ma20=0,ma60=0,ret20d=0,trend='--';
  if(marketTrend&&marketTrend.indices){
    const idx=marketTrend.indices.find(i=>i.name===name);
    if(idx){ma5=idx.ma5||0;ma10=idx.ma10||0;ma20=idx.ma20||0;ma60=idx.ma60||0;ret20d=idx.ret_20d||0;trend=idx.trend||'--';}
  }
  document.getElementById('detailContent').innerHTML='<div style="text-align:center;padding:60px;color:var(--muted);">正在加载指数数据...</div>';
  openOverlay('detailOverlay');
  let kline=[];
  try{
    const r=await jsonpRequest('https://push2his.eastmoney.com/api/qt/stock/kline/get?secid='+secid+'&fields1=f1,f2,f3,f4,f5,f6&fields2=f51,f52,f53,f54,f55,f56,f57,f58&klt=101&fqt=1&beg=0&end=20500101&lmt=120',8000);
    if(r&&r.data&&r.data.klines){
      kline=r.data.klines.map(function(line){const p=line.split(',');return{date:p[0],open:parseFloat(p[1]),close:parseFloat(p[2]),high:parseFloat(p[3]),low:parseFloat(p[4]),volume:parseFloat(p[5]),ma5:0,ma10:0,ma20:0};});
      for(let i=0;i<kline.length;i++){kline[i].ma5=calcMA(kline.slice(Math.max(0,i-4),i+1).map(k=>k.close),5);kline[i].ma10=calcMA(kline.slice(Math.max(0,i-9),i+1).map(k=>k.close),10);kline[i].ma20=calcMA(kline.slice(Math.max(0,i-19),i+1).map(k=>k.close),20);}
    }
  }catch(e){}
  const pc=pct>=0?'pu':'pd',ps=pct>=0?'+':'';
  const trendTag={'上行':'trend-up','下行':'trend-down','震荡':'trend-flat'}[trend]||'trend-flat';
  let html='<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">';
  html+='<div><span style="font-size:24px;font-weight:800;">'+name+'</span> <span style="color:var(--muted);font-size:14px;">'+code+'</span> <span class="badge watch">指数</span></div>';
  html+='<div><span class="'+pc+'" style="font-size:28px;font-weight:700;">'+(price||0).toFixed(2)+'</span> <span class="'+pc+'" style="font-size:16px;">'+ps+(pct||0).toFixed(2)+'%</span></div>';
  html+='</div>';
  html+='<div class="dt-grid" style="margin-bottom:16px;">';
  html+='<div class="dt-cell"><div class="v">'+(ma5||0).toFixed(2)+'</div><div class="l">MA5</div></div>';
  html+='<div class="dt-cell"><div class="v">'+(ma10||0).toFixed(2)+'</div><div class="l">MA10</div></div>';
  html+='<div class="dt-cell"><div class="v">'+(ma20||0).toFixed(2)+'</div><div class="l">MA20</div></div>';
  html+='<div class="dt-cell"><div class="v">'+(ma60||0).toFixed(2)+'</div><div class="l">MA60</div></div>';
  html+='<div class="dt-cell"><div class="v '+(ret20d>=0?'up':'down')+'">'+(ret20d>=0?'+':'')+(ret20d||0).toFixed(2)+'%</div><div class="l">20日涨跌</div></div>';
  html+='<div class="dt-cell"><div class="v"><span class="trend-tag '+trendTag+'">'+trend+'</span></div><div class="l">趋势</div></div>';
  html+='</div>';
  html+='<div class="dt-section"><div class="dt-section-title">趋势分析</div>';
  if(price>ma5&&ma5>ma20)html+='<p>当前价格在MA5和MA20之上，短期趋势向上，多头排列。</p>';
  else if(price<ma5&&ma5<ma20)html+='<p>当前价格在MA5和MA20之下，短期趋势向下，空头排列。</p>';
  else html+='<p>均线交叉，趋势不明朗，处于震荡格局。</p>';
  if(ma5>ma20)html+='<p>MA5('+ma5.toFixed(2)+') > MA20('+ma20.toFixed(2)+')，短期均线在长期均线之上。</p>';
  else html+='<p>MA5('+ma5.toFixed(2)+') < MA20('+ma20.toFixed(2)+')，短期均线在长期均线之下。</p>';
  html+='</div>';
  if(kline.length>0)html+='<div class="dt-section"><div class="dt-section-title">K线走势（MA/BOLL + 成交量/MACD/KDJ）</div><div id="klineChart" style="width:100%;height:460px;"></div></div>';
  document.getElementById('detailContent').innerHTML=html;
  if(kline.length>0)setTimeout(function(){renderKlineChart(kline);},100);
}

// ===== CSV =====
function exportCSV(){
  const _h=['代码','名称','指标数§信号触发总数','看涨§看涨信号数量','看跌§看跌信号数量','情绪§多空偏向值(-1~1)','交易信号§回测+量化综合信号','入场价§建议入场价格','止损价§ATR动态止损','目标价§ATR动态目标','仓位§建议仓位%','胜率§原始回测胜率','量化胜率§过滤后胜率','盈亏比§盈亏比PF','量化评分§多因子综合评分','RSI§相对强弱(30-65优)','ADX§趋势强度(>20有趋势)','量比§量比(1.2-3优)','布林位置§布林带位置(0-1)','ATR波幅%§平均真实波幅','置信度§信号置信度','风险§风险等级','优先级§操作优先级','行业§所属行业','市值(亿)','最新价','涨跌幅%'];
  const rows=scanData.map(s=>[s.code,s.name,s.signal_count,s.bullish,s.bearish,s.sentiment_score,s.trade_signal,s.entry_price,s.stop_loss,s.target_price,s.position_size,s.bt_win_rate,s.bt_wr_filtered,s.bt_pf,s.bt_quant_score,s.bt_rsi,s.bt_adx,s.bt_vol_ratio,s.bt_bb_pos,s.bt_atr_pct,s.confidence,s.risk,s.priority,s.industry,s.mkt_cap_yi,s.latest_price,s.pct_change]);
  const csv=[h,...rows].map(r=>r.map(c=>'"'+c+'"').join(',')).join('\n');
  const blob=new Blob(['\ufeff'+csv],{type:'text/csv;charset=utf-8'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='stocks.csv';a.click();
}

// ===== Indicator Reference =====
function renderIndicatorRef(){
  const entries=Object.entries(indicatorCounts).sort((a,b)=>b[1]-a[1]);
  document.getElementById('indicatorRefGrid').innerHTML=entries.map(([name,count])=>{
    const info=indicatorInfo[name];if(!info)return'';
    const cls=info.type==='看涨'?'':(info.type==='看跌'?'bear':'neutral');
    return '<div class="ref-card '+cls+'"><div class="ref-head"><span class="ref-name">'+name+'</span><span class="ref-count">'+count+'次</span></div><div class="ref-desc">'+info.desc+'</div><div class="ref-meta"><span class="badge '+(info.type==='看涨'?'buy':(info.type==='看跌'?'sell':'watch'))+'">'+info.type+'</span><span style="font-size:11px;color:var(--muted);">'+info.section+'</span></div></div>';
  }).join('');
}

// ===== Real-time Index Data =====
let realtimeIndices={};
let indexKlineLoaded=false;
const IDX_SECIDS=['1.000001','0.399001','0.399006','1.000688','0.899050'];
const IDX_MAP={'000001':'上证指数','399001':'深证成指','399006':'创业板指','000688':'科创50','899050':'北证50'};
async function fetchRealtimeIndices(){
  try{
    const data=await jsonpRequest('https://push2.eastmoney.com/api/qt/ulist.np/get?fltt=2&fields=f2,f3,f4,f12,f14&secids='+IDX_SECIDS.join(','),8000);
    if(data&&data.data&&data.data.diff){
      data.data.diff.forEach(item=>{realtimeIndices[item.f12]={price:item.f2,pct:item.f3,name:item.f14};});
      updateIndexDisplay();
    }
  }catch(e){console.log('Index realtime fetch skipped:',e.message);}
}
async function fetchIndexKlines(){
  if(indexKlineLoaded)return;
  for(const secid of IDX_SECIDS){
    const code=secid.split('.')[1];const name=IDX_MAP[code];
    try{
      const r=await jsonpRequest('https://push2his.eastmoney.com/api/qt/stock/kline/get?secid='+secid+'&fields1=f1,f2,f3,f4,f5,f6&fields2=f51,f52,f53,f54,f55,f56,f57,f58&klt=101&fqt=1&beg=0&end=20500101&lmt=120',8000);
      if(r&&r.data&&r.data.klines){
        const closes=r.data.klines.map(function(line){return parseFloat(line.split(',')[2]);});
        if(!marketTrend)marketTrend={};
        if(!marketTrend.indices)marketTrend.indices=[];
        let idx=marketTrend.indices.find(i=>i.name===name);
        if(!idx){idx={name:name,code:secid,index_close:0,index_change:0,trend:'震荡'};marketTrend.indices.push(idx);}
        idx.ma5=closes.length>=5?closes.slice(-5).reduce((a,b)=>a+b,0)/5:0;
        idx.ma10=closes.length>=10?closes.slice(-10).reduce((a,b)=>a+b,0)/10:0;
        idx.ma20=closes.length>=20?closes.slice(-20).reduce((a,b)=>a+b,0)/20:0;
        idx.ma60=closes.length>=60?closes.slice(-60).reduce((a,b)=>a+b,0)/60:0;
        idx.ret_20d=closes.length>=21?((closes[closes.length-1]-closes[closes.length-21])/closes[closes.length-21]*100):0;
      }
    }catch(e){console.log('Index kline fetch failed for',name,e.message);}
  }
  indexKlineLoaded=true;updateIndexDisplay();
}
function updateIndexDisplay(){
  if(!marketTrend)marketTrend={indices:[]};
  if(!marketTrend.indices)marketTrend.indices=[];
  Object.keys(IDX_MAP).forEach(code=>{
    const name=IDX_MAP[code];const rt=realtimeIndices[code];
    let idx=marketTrend.indices.find(i=>i.name===name);
    if(!idx){idx={name:name,code:'',index_close:0,index_change:0,trend:'震荡',ma5:0,ma10:0,ma20:0,ma60:0,ret_20d:0};marketTrend.indices.push(idx);}
    if(rt){idx.index_close=rt.price;idx.index_change=rt.pct;
      if(idx.ma5&&idx.ma20){
        if(rt.price>idx.ma5&&idx.ma5>idx.ma20)idx.trend='上行';
        else if(rt.price<idx.ma5&&idx.ma5<idx.ma20)idx.trend='下行';
        else idx.trend='震荡';
      }
    }
  });
  const sh=marketTrend.indices.find(i=>i.name==='上证指数');
  if(sh){marketTrend.index_close=sh.index_close;marketTrend.index_change=sh.index_change;
    marketTrend.ma5=sh.ma5;marketTrend.ma20=sh.ma20;marketTrend.ma60=sh.ma60;
    marketTrend.ret_20d=sh.ret_20d;marketTrend.trend=sh.trend;}
  const active=document.querySelector('.page-section.active');
  if(active&&active.id==='layer1'){renderMarketTrend();}
  renderTicker();
  const lu=document.getElementById('lastUpdate');
  if(lu)lu.textContent=new Date().toLocaleTimeString('zh-CN');
}

// ===== Real-time Polling =====
let realtimeData={};
async function fetchRealtimeQuotes(){
  const stockCodes=scanData.map(s=>{const p=s.code.startsWith('6')?'1':'0';return p+'.'+s.code;});
  const extraCodes=watchList.filter(c=>!scanData.find(s=>s.code===c)).map(c=>{const p=c.startsWith('6')?'1':'0';return p+'.'+c;});
  const allCodes=[...stockCodes,...extraCodes].join(',');
  try{const data=await jsonpRequest('https://push2.eastmoney.com/api/qt/ulist.np/get?fltt=2&fields=f2,f3,f12,f14&secids='+allCodes,8000);if(data&&data.data&&data.data.diff){data.data.diff.forEach(item=>{realtimeData[item.f12]={price:item.f2,pct:item.f3};});updateRealtimeDisplay();}}catch(e){console.log('Realtime fetch skipped:',e.message);}
}
function updateRealtimeDisplay(){
  let u=0;
  scanData.forEach(s=>{const rt=realtimeData[s.code];if(rt){s.latest_price=rt.price;s.pct_change=rt.pct;u++;}});
  watchList.filter(c=>!scanData.find(s=>s.code===c)).forEach(c=>{if(realtimeData[c])u++;});
  if(u>0){
    renderTicker();renderTable();evaluateTracked();renderPlaybook();
    const active=document.querySelector('.page-section.active');
    if(active&&active.id==='layer-watch'){updateWatchlistPrices();refreshT0Prices();}
    if(active&&active.id==='layer4'){renderCalibration();}
    document.getElementById('lastUpdate').textContent=new Date().toLocaleTimeString('zh-CN');
  }
}
function updateWatchlistPrices(){
  watchList.forEach(code=>{
    const rt=realtimeData[code];if(!rt)return;
    const pe=document.getElementById('wlPrice_'+code);
    const pce=document.getElementById('wlPct_'+code);
    const color='var(--'+(rt.pct>=0?'up':'down')+')';
    if(pe){pe.textContent=rt.price.toFixed(2);pe.style.color=color;}
    if(pce){pce.textContent=(rt.pct>=0?'+':'')+rt.pct.toFixed(2)+'%';pce.style.color=color;}
  });
}

// ===== Sentiment Cycle Engine (KPL-style) =====
const CYCLE_STAGES={
  bing:{name:'冰点期',cls:'s-bing',color:'#4d9fff',advice:'涨停寥寥、连板断层，市场缺乏赚钱效应。<b>策略：空仓观望为主</b>，仅跟踪率先逆势放量的首板，等待转暖信号（涨停回升至40家+、出现3连板）再介入。',pos:12},
  huinuan:{name:'回暖期',cls:'s-huinuan',color:'#22c55e',advice:'涨停回升、出现空间板，资金开始试错。<b>策略：轻仓(2-3成)参与率先启动的主线龙头</b>，首板/2板低吸为主，快进快出，不见合力不重仓。',pos:32},
  fajiao:{name:'发酵期',cls:'s-fajiao',color:'#ffc83d',advice:'主线明确、梯队完整、封板质量高。<b>策略：聚焦核心主线顺势加仓(5-7成)</b>，持有龙头为主，分歧低吸为辅；3板以上不追高，等分时回踩确认。',pos:55},
  gaochao:{name:'高潮期',cls:'s-gaochao',color:'#ef4444',advice:'涨停潮、高位板密集、情绪亢奋。<b>策略：持股为主勿追板(仓位上限7成)</b>，挂好移动止盈；警惕量能极致+高位炸板=见顶前兆，备好撤退计划。',pos:78},
  fenqi:{name:'分歧期',cls:'s-fenqi',color:'#ff9500',advice:'炸板率抬升、高位板松动，多空剧烈换手。<b>策略：只做核心龙头的弱转强低吸(降半仓)</b>，非核心杂毛一律回避；断板/尾盘漏单坚决走。',pos:62},
  tuichao:{name:'退潮期',cls:'s-tuichao',color:'#16a34a',advice:'跌停增多、涨停骤减、高位股批量补跌。<b>策略：清仓防守，绝不抄底高位股</b>；等待冰点信号（连板高度≤2、跌停>涨停）出现后再谈新周期。',pos:18}
};
let emoPools={zt:[],dt:[],zb:[],ok:false};
let cycleState=null;
function getSH(){const rt=realtimeIndices['000001'];if(rt&&rt.pct!==undefined)return rt.pct;return (marketTrend&&marketTrend.index_change)||0;}
function getRegime(){const rm=riskMetrics||{};return rm.market_regime||'震荡';}
async function fetchPoolAll(kind,sort,pz,dateStr,base,ut){
  var all=[],total=0;
  var r=await jsonpRequest(base+kind+'?'+ut+'&Pageindex=0&pagesize='+pz+'&sort='+sort+'&date='+dateStr,9000).catch(function(){return null;});
  if(r&&r.data&&r.data.pool){all=r.data.pool;total=r.data.tc||all.length;}
  var pages=total>pz?Math.ceil(total/pz):1;
  if(pages>1){
    var jobs=[];
    for(var p=1;p<pages;p++)jobs.push(p);
    for(var i=0;i<jobs.length;i+=8){
      var batch=jobs.slice(i,i+8);
      var rs=await Promise.all(batch.map(function(p){return jsonpRequest(base+kind+'?'+ut+'&Pageindex='+p+'&pagesize='+pz+'&sort='+sort+'&date='+dateStr,9000).catch(function(){return null;});}));
      rs.forEach(function(rr){if(rr&&rr.data&&rr.data.pool)all=all.concat(rr.data.pool);});
    }
  }
  return all;
}
async function fetchEmotionPools(){
  const base='https://push2ex.eastmoney.com/';
  const ut='ut=7eea3edcaed734bea9cbfc24409ed989&dpt=wz.ztzt';
  var _d=new Date();
  const dateStr=_d.getFullYear()+('0'+(_d.getMonth()+1)).slice(-2)+('0'+_d.getDate()).slice(-2);
  try{
    // 全市场分页抓取：按 data.tc 总数翻页，避免 pagesize 截断（覆盖沪深京全A股）
    const pools=await Promise.all([
      fetchPoolAll('getTopicZTPool','fbt%3Aasc',500,dateStr,base,ut),
      fetchPoolAll('getTopicDTPool','fund%3Aasc',200,dateStr,base,ut),
      fetchPoolAll('getTopicZBPool','fbt%3Aasc',300,dateStr,base,ut)
    ]);
    emoPools.zt=pools[0]||[];emoPools.dt=pools[1]||[];emoPools.zb=pools[2]||[];
    emoPools.ok=emoPools.zt.length>0||emoPools.dt.length>0;
    computeEmotionCycle();
    renderEmotionCycle();renderLadder();renderPlaybook();renderSignalCenter();renderUpDownDist();
  }catch(e){console.log('Emotion pools JSONP failed, trying Python fallback:',e.message);
    // Fallback 1: use Python-injected data
    const epd=window._emoPoolsData||{};
    if(epd.ok&&epd.zt&&epd.zt.length>0){
      emoPools.zt=epd.zt;emoPools.dt=epd.dt||[];emoPools.zb=epd.zb||[];
      emoPools.ok=true;
      console.log('Using Python fallback emoPools: ZT='+emoPools.zt.length+' DT='+emoPools.dt.length+' ZB='+emoPools.zb.length);
      computeEmotionCycle();
      renderEmotionCycle();renderLadder();renderPlaybook();renderSignalCenter();renderUpDownDist();
      return;
    }
    // Fallback 2: use market trend to create basic cycleState
    const mt=marketTrend||{};const shPct=mt.index_change||0;
    let fStage='huinuan';let fScore=40;
    if(mt.trend==='上行'){fStage='fajiao';fScore=65;}
    else if(mt.trend==='下行'||mt.is_downtrend){fStage='bing';fScore=25;}
    else if(mt.is_severe_downtrend){fStage='tuichao';fScore=15;}
    if(!cycleState){cycleState={score:fScore,stage:fStage,st:CYCLE_STAGES[fStage],ztC:0,dtC:0,zbC:0,zhaBanLv:0,height:0,lbRate:0,fundTotal:0,shPct:shPct};}
    renderEmotionCycle();renderLadder();renderPlaybook();renderSignalCenter();renderUpDownDist();}
}
function computeEmotionCycle(){
  const ztC=emoPools.zt.length,dtC=emoPools.dt.length,zbC=emoPools.zb.length;
  let height=0,lbC=0,fundTotal=0;
  emoPools.zt.forEach(s=>{const lb=s.lbc||1;if(lb>height)height=lb;if(lb>=2)lbC++;fundTotal+=(s.fund||0);});
  const zhaBanLv=(ztC+zbC)>0?zbC/(ztC+zbC)*100:0;
  const lbRate=ztC>0?lbC/ztC*100:0;
  const shPct=getSH();
  let score=Math.min(ztC/100,1)*32+Math.min(height/8,1)*18+(1-Math.min(zhaBanLv/50,1))*12+Math.min(lbRate/60,1)*14+Math.max(0,Math.min((shPct+1.5)/3.5,1))*14-Math.min(dtC/20,1)*8;
  score=Math.max(0,Math.min(100,Math.round(score)));
  let stage;
  if(dtC>=15||shPct<=-1.5)stage='tuichao';
  else if(zhaBanLv>=35&&score>=45)stage='fenqi';
  else if(score>=68||height>=7)stage='gaochao';
  else if(score>=48)stage='fajiao';
  else if(score>=30)stage='huinuan';
  else stage='bing';
  cycleState={score:score,stage:stage,st:CYCLE_STAGES[stage],ztC:ztC,dtC:dtC,zbC:zbC,zhaBanLv:zhaBanLv,height:height,lbRate:lbRate,fundTotal:fundTotal/1e8,shPct:shPct};
}
function renderEmotionCycle(){
  const box=document.getElementById('cycleBox');if(!box)return;
  if(!cycleState){box.innerHTML='<div style="text-align:center;padding:40px;color:var(--muted);font-size:13px;">正在获取实时涨停数据（东财涨停池/跌停池/炸板池）...</div>';return;}
  const c=cycleState,st=c.st;
  const cu=document.getElementById('cycleUpdate');if(cu)cu.textContent='更新 '+new Date().toLocaleTimeString('zh-CN');
  let m='<div class="cycle-header"><div class="cycle-stage '+st.cls+'">'+st.name+'</div><div style="text-align:right;"><div style="font-size:28px;font-weight:800;color:'+st.color+';">'+c.score+'<span style="font-size:12px;color:var(--muted);"> /100</span></div><div style="font-size:11px;color:var(--muted);">情绪温度计</div></div></div>';
  m+='<div class="pb-bar" style="margin-top:10px;"><div style="width:'+c.score+'%;"></div></div>';
  const idx=['bing','tuichao','huinuan','fajiao','fenqi','gaochao'];
  m+='<div style="display:flex;justify-content:space-between;font-size:10px;color:var(--muted);margin-top:4px;">'+idx.map(k=>'<span style="'+(k===c.stage?'color:'+CYCLE_STAGES[k].color+';font-weight:700;':'')+'">'+CYCLE_STAGES[k].name+'</span>').join('')+'</div>';
  m+='<div class="cycle-metrics">';
  m+='<div class="cycle-metric"><div class="v" style="color:var(--up);">'+c.ztC+'</div><div class="l">涨停</div></div>';
  m+='<div class="cycle-metric"><div class="v" style="color:var(--down);">'+c.dtC+'</div><div class="l">跌停</div></div>';
  m+='<div class="cycle-metric"><div class="v" style="color:var(--orange);">'+c.zbC+'</div><div class="l">炸板</div></div>';
  m+='<div class="cycle-metric"><div class="v" style="color:'+(c.zhaBanLv<25?'var(--up)':(c.zhaBanLv<40?'var(--gold)':'var(--down)'))+';">'+c.zhaBanLv.toFixed(0)+'%</div><div class="l">炸板率</div></div>';
  m+='<div class="cycle-metric"><div class="v" style="color:var(--gold);">'+c.height+'板</div><div class="l">空间高度</div></div>';
  m+='<div class="cycle-metric"><div class="v">'+c.lbRate.toFixed(0)+'%</div><div class="l">连板率</div></div>';
  m+='<div class="cycle-metric"><div class="v" style="color:'+(c.shPct>=0?'var(--up)':'var(--down)')+';">'+(c.shPct>=0?'+':'')+c.shPct.toFixed(2)+'%</div><div class="l">上证</div></div>';
  m+='<div class="cycle-metric"><div class="v">'+c.fundTotal.toFixed(0)+'亿</div><div class="l">封单总额</div></div>';
  m+='</div>';
  m+='<div class="cycle-advice"><b style="color:'+st.color+';">【'+st.name+'·操作指引】</b>'+st.advice+'</div>';
  box.innerHTML=m;
}
function renderLadder(){
  const box=document.getElementById('ladderBox');if(!box)return;
  if(!emoPools.ok){box.innerHTML='<div style="text-align:center;padding:40px;color:var(--muted);font-size:13px;">正在获取涨停梯队数据...</div>';return;}
  const groups={};
  emoPools.zt.forEach(s=>{const lb=s.lbc||1;if(!groups[lb])groups[lb]=[];groups[lb].push(s);});
  const lbs=Object.keys(groups).map(Number).sort((a,b)=>b-a);
  if(!lbs.length){box.innerHTML='<div style="text-align:center;padding:40px;color:var(--muted);">今日暂无涨停数据</div>';return;}
  let html='';
  lbs.forEach(lb=>{
    const arr=groups[lb].sort((a,b)=>(b.fund||0)-(a.fund||0));
    const cls=lb>=5?'hi':(lb>=3?'mid':'lo');
    const stocks=arr.slice(0,10).map(s=>{
      const days=s.zttj&&s.zttj.days?s.zttj.days+'天'+(s.zttj.ct||lb)+'板':'';
      const tip=s.n+' '+s.c+' | '+(days||lb+'连板')+' | 封单'+((s.fund||0)/1e8).toFixed(1)+'亿 | '+(s.hybk||'')+(s.zbc?' | 炸板'+s.zbc+'次':'');
      return '<span class="ladder-stock" title="'+tip+'" onclick="showZtInfo(\''+s.c+'\')">'+s.n+(lb>=3?'<span class="lb">'+lb+'</span>':'')+'</span>';
    }).join('');
    html+='<div class="ladder-row"><span class="ladder-lb '+cls+'">'+lb+'板</span><span class="ladder-cnt">'+arr.length+'只</span><div class="ladder-stocks">'+stocks+(arr.length>10?'<span style="font-size:10px;color:var(--muted);align-self:center;">+'+(arr.length-10)+'</span>':'')+'</div></div>';
  });
  box.innerHTML=html;
}
function showZtInfo(code){
  const s=scanData.find(x=>x.code===code);
  if(s){showDetail(code);return;}
  const z=emoPools.zt.find(x=>x.c===code)||emoPools.zb.find(x=>x.c===code);
  if(!z)return;
  const price=(z.p||0)/1000;
  const days=z.zttj&&z.zttj.days?z.zttj.days+'天'+z.zttj.ct+'板':(z.lbc||1)+'连板';
  let html='<div style="padding:8px 4px;"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;"><div><span style="font-size:22px;font-weight:800;">'+z.n+'</span> <span style="color:var(--muted);">'+z.c+'</span></div><span class="badge buy">'+days+'</span></div>';
  html+='<div class="dt-grid">';
  html+='<div class="dt-cell"><div class="v pu">'+price.toFixed(2)+'</div><div class="l">现价</div></div>';
  html+='<div class="dt-cell"><div class="v pu">'+(z.zdp||0).toFixed(2)+'%</div><div class="l">涨跌幅</div></div>';
  html+='<div class="dt-cell"><div class="v">'+((z.fund||0)/1e8).toFixed(2)+'亿</div><div class="l">封单资金</div></div>';
  html+='<div class="dt-cell"><div class="v">'+(z.zbc||0)+'</div><div class="l">炸板次数</div></div>';
  html+='<div class="dt-cell"><div class="v gold">'+(z.hybk||'--')+'</div><div class="l">所属板块</div></div>';
  html+='<div class="dt-cell"><div class="v">'+(z.hs||0).toFixed(1)+'%</div><div class="l">换手率</div></div>';
  html+='</div>';
  if(cycleState)html+='<div class="watch-advice">当前情绪周期：<b style="color:'+cycleState.st.color+';">'+cycleState.st.name+'('+cycleState.score+'分)</b> — '+cycleState.st.advice+'</div>';
  html+='<div style="margin-top:10px;font-size:11px;color:var(--muted);">该股不在Top50扫描池中，如需完整AI推演请在搜索框添加关注后查看。</div></div>';
  document.getElementById('detailContent').innerHTML=html;openOverlay('detailOverlay');
}

// ===== Trading Playbook =====
function suggestPosition(){
  if(!cycleState)return {pos:30,txt:'情绪数据加载中，默认保守仓位'};
  const c=cycleState;let pos=Math.round(c.score*0.9);
  const reg=getRegime();
  if(reg==='空头')pos=Math.round(pos*0.4);
  else if(reg==='震荡')pos=Math.round(pos*0.75);
  pos=Math.max(0,Math.min(80,pos));
  let txt;
  if(c.stage==='gaochao')txt='情绪高潮，可持股但不再新增追高仓位';
  else if(c.stage==='fajiao')txt='顺势阶段，围绕主线龙头分批配置';
  else if(c.stage==='fenqi')txt='分歧期降杠杆，只保留核心龙头';
  else if(c.stage==='tuichao')txt='退潮期以现金为王，严控回撤';
  else if(c.stage==='huinuan')txt='回暖试错期，小仓位验证手感';
  else txt='冰点期观望为主，保留弹药等右侧';
  if(reg==='空头')txt+='；注意：大盘处于空头趋势，整体仓位需再打折扣';
  return {pos:pos,txt:txt};
}
function renderPlaybook(){
  const grid=document.getElementById('playbookGrid');if(!grid)return;
  const reg=getRegime();const c=cycleState;const sp=suggestPosition();
  const mt=marketTrend||{};
  // Card 1: market state
  let h='<div class="pb-card"><h4><span style="width:3px;height:14px;background:var(--blue);border-radius:2px;display:inline-block;"></span>市场状态</h4>';
  h+='<div class="pb-row"><span>大盘趋势</span><b style="color:'+(reg==='多头'?'var(--up)':(reg==='空头'?'var(--down)':'var(--gold)'))+';">'+reg+'</b></div>';
  h+='<div class="pb-row"><span>上证 MA5 / MA20</span><b>'+(mt.ma5?mt.ma5.toFixed(0):'--')+' / '+(mt.ma20?mt.ma20.toFixed(0):'--')+'</b></div>';
  h+='<div class="pb-row"><span>情绪周期</span><b style="color:'+(c?c.st.color:'var(--muted)')+';">'+(c?c.st.name+'('+c.score+'分)':'加载中')+'</b></div>';
  h+='<div class="pb-row"><span>涨停/跌停/炸板</span><b>'+(c?c.ztC+' / '+c.dtC+' / '+c.zbC:'--')+'</b></div>';
  h+='<div class="pb-row"><span>空间高度</span><b>'+(c?c.height+'板':'--')+'</b></div></div>';
  // Card 2: position
  h+='<div class="pb-card"><h4><span style="width:3px;height:14px;background:var(--gold);border-radius:2px;display:inline-block;"></span>仓位建议</h4>';
  h+='<div style="font-size:34px;font-weight:800;color:'+(sp.pos>=50?'var(--up)':(sp.pos>=25?'var(--gold)':'var(--down)'))+';">'+sp.pos+'<span style="font-size:13px;color:var(--muted);">%上限</span></div>';
  h+='<div class="pb-bar"><div style="width:'+sp.pos+'%;"></div></div>';
  h+='<div class="pb-disc" style="margin-top:8px;">'+sp.txt+'</div></div>';
  // Card 3: discipline
  h+='<div class="pb-card"><h4><span style="width:3px;height:14px;background:var(--down);border-radius:2px;display:inline-block;"></span>操作纪律（铁律）</h4><div class="pb-disc">';
  h+='1. <b>止损铁律</b>：破位即走，单笔亏损不超过总资金2%，绝不扛单补仓。<br>';
  h+='2. <b>仓位约束</b>：单票不超过'+(sp.pos>=50?'20':(sp.pos>=25?'15':'10'))+'%总仓位，同板块不超过2只。<br>';
  h+='3. <b>周期纪律</b>：'+(c?('<b style="color:'+c.st.color+';">'+c.st.name+'</b>，'+(c.stage==='gaochao'?'只止盈不开新仓':(c.stage==='fajiao'?'顺势持有，回踩加仓':(c.stage==='fenqi'?'弱转强才做，断板即走':(c.stage==='tuichao'?'空仓等待，不抄底':'轻仓试错，严格止损'))))):'按信号分级执行')+'。<br>';
  h+='4. <b>复盘要求</b>：每日收盘后核对信号兑现情况，学习中心自动校准胜率权重。</div></div>';
  // Card 4: today focus
  h+='<div class="pb-card"><h4><span style="width:3px;height:14px;background:var(--up);border-radius:2px;display:inline-block;"></span>今日焦点（A级共振优选）</h4><div class="pb-disc">';
  const aSigs=buySignals.map(s=>({s:s,cf:confluenceInfo(s)})).filter(x=>x.cf.grade==='A').sort((a,b)=>b.cf.score-a.cf.score).slice(0,3);
  if(aSigs.length){
    aSigs.forEach(x=>{
      const s=x.s;const cf=x.cf;
      h+='<div style="margin-bottom:6px;"><b style="color:var(--up);cursor:pointer;" onclick="showDetail(\''+s.code+'\')">'+s.name+'</b> <span class="grade-badge grade-'+cf.grade+'">共振'+cf.score+'分</span><br><span style="font-size:11px;">入场 '+s.entry_price.toFixed(2)+' · 止损 '+s.stop_loss.toFixed(2)+' · 目标 '+s.target_price.toFixed(2)+' · 建议仓 '+Math.min(s.position_size,sp.pos>=50?20:15)+'%</span></div>';
    });
  }else{
    h+='<div style="color:var(--muted);">当前无A级共振信号'+(c&&emoPools.ok?'（情绪周期'+c.st.name+'，信号需等待共振确认）':'（情绪数据加载中，稍候重试）')+'</div>';
    const bSigs=buySignals.map(s=>({s:s,cf:confluenceInfo(s)})).filter(x=>x.cf.grade==='B').sort((a,b)=>b.cf.score-a.cf.score).slice(0,2);
    bSigs.forEach(x=>{const s=x.s;h+='<div style="margin-bottom:6px;"><b style="color:var(--gold);cursor:pointer;" onclick="showDetail(\''+s.code+'\')">'+s.name+'</b> <span class="grade-badge grade-B">B级观察</span> <span style="font-size:11px;">入场 '+s.entry_price.toFixed(2)+' / 止损 '+s.stop_loss.toFixed(2)+'</span></div>';});
  }
  h+='</div></div>';
  grid.innerHTML=h;
}

// ===== Confluence Engine (win-rate booster) =====
let calibFactor=1.0;
function confluenceInfo(sig){
  const sd=scanData.find(x=>x.code===sig.code)||{};
  const sq=sd.signal_quality||50;
  const wrf=sig.wr_filtered||0;
  const pot=sig.target_price?((sig.target_price-sig.entry_price)/sig.entry_price*100):0;
  const risk=sig.stop_loss?((sig.entry_price-sig.stop_loss)/sig.entry_price*100):0;
  const rr=risk>0?pot/risk:0;
  const vol=sd.bt_vol_ratio||1;
  const adx=sd.bt_adx||0;
  const reg=getRegime();
  const stage=cycleState?cycleState.stage:null;
  const sec=sectorData.find(x=>x.name===sd.industry);
  const secSent=sec?sec.avg_sentiment:0;
  const factors=[];let score=0;
  const f=(name,on,pts)=>{factors.push({name:name,on:on,pts:pts});if(on)score+=pts;};
  f('量化胜率≥55',wrf>=55,wrf>=60?16:12);
  f('信号质量≥60',sq>=60,sq>=70?14:10);
  f('盈亏比≥1.5',rr>=1.5,rr>=2?16:12);
  f('大盘多头',reg==='多头',reg==='多头'?14:7);
  f('情绪顺周期',stage?['fajiao','gaochao','huinuan'].includes(stage):true,stage?(['fajiao','gaochao'].includes(stage)?12:(stage==='huinuan'?8:0)):5);
  f('量能确认',vol>=1.2&&vol<=3,8);
  f('趋势ADX≥20',adx>=20,8);
  f('板块共振',secSent>=0.15,10);
  score=Math.round(score*calibFactor);
  score=Math.max(0,Math.min(100,score));
  const grade=score>=75?'A':(score>=58?'B':'C');
  return {score:score,grade:grade,factors:factors,rr:rr};
}

// ===== 需求1: 涨跌分布 + 今日炒什么 + T+0机会 =====
// 前端JSONP获取全市场涨跌统计
async function fetchMarketBreadth(){
  try{
    const base='https://push2delay.eastmoney.com/api/qt/clist/get';
    const fs='m:0+t:6,m:0+t:80,m:1+t:2,m:1+t:23,m:0+t:81%2Bs:2048';
    const PZ=200;
    const fields='f3,f292';
    const first=await jsonpRequest(base+'?pn=1&pz='+PZ+'&po=1&np=1&fltt=2&invt=2&fs='+fs+'&fields='+fields,8000);
    const rawTotal=(first&&first.data&&first.data.total)||0;
    if(!rawTotal)return;
    const all=[...(first.data.diff||[])];
    const pages=Math.min(Math.ceil(rawTotal/PZ),60);
    const jobs=[];
    for(let p=2;p<=pages;p++)jobs.push(p);
    for(let i=0;i<jobs.length;i+=10){
      const batch=jobs.slice(i,i+10);
      const rs=await Promise.all(batch.map(p=>jsonpRequest(base+'?pn='+p+'&pz='+PZ+'&po=1&np=1&fltt=2&invt=2&fs='+fs+'&fields='+fields,8000).catch(()=>null)));
      rs.forEach(r=>{if(r&&r.data&&r.data.diff)all.push(...r.data.diff);});
    }
    let up=0,down=0,flat=0,sus=0,delisted=0;
    const hist=[0,0,0,0,0,0,0,0,0,0,0,0,0];
    function bk(v){if(v===0)return 6;if(v<0){if(v<=-7)return 0;if(v<=-5)return 1;if(v<=-3)return 2;if(v<=-2)return 3;if(v<=-1)return 4;return 5;}if(v>=7)return 12;if(v>=5)return 11;if(v>=3)return 10;if(v>=2)return 9;if(v>=1)return 8;return 7;}
    all.forEach(s=>{
      if(s.f292===7||s.f292==='7'){delisted++;return;}
      const v=s.f3;
      let p=null;
      if(typeof v==='number'){p=v;}
      else if(v==='-'||v==null||v===undefined){sus++;return;}
      else{const pp=parseFloat(v);if(!isNaN(pp)){p=pp;}else{sus++;return;}}
      if(p>0)up++;else if(p<0)down++;else flat++;
      hist[bk(p)]++;
    });
    const total=rawTotal-delisted;
    const lu=cycleState?cycleState.ztC:0,ld=cycleState?cycleState.dtC:0;
    const histNames=['≤-7%','-7~-5%','-5~-3%','-3~-2%','-2~-1%','-1~0%','平盘','0~1%','1~2%','2~3%','3~5%','5~7%','≥7%'];
    window._marketBreadth={total:total,up:up,down:down,flat:flat,suspended:sus,limit_up:lu,limit_down:ld,hist:histNames.map((n,i)=>({name:n,value:hist[i]}))};
    renderUpDownDist();
  }catch(e){console.log('Market breadth JSONP failed:',e.message);}
}
// 前端JSONP获取T+0 ETF
async function fetchT0Etfs(){
  try{
    const base='https://push2delay.eastmoney.com/api/qt/clist/get';
    const r=await jsonpRequest(base+'?pn=1&pz=200&po=1&np=1&fltt=2&invt=2&fs=b:MK0024,b:MK0025,b:MK0026,b:MK0027,b:MK0404,b:MK0405&fields=f12,f14,f2,f3,f6,f8',8000);
    if(r&&r.data&&r.data.diff){
      const list=[];
      r.data.diff.forEach(e=>{
        const code=e.f12||'',name=e.f14||'',amount=e.f6||0;
        const nu=name.toUpperCase();
        let type='',strategy='';
        if(name.indexOf('黄金')>=0||nu.indexOf('GOLD')>=0||code.startsWith('518')){type='黄金ETF';strategy='趋势跟踪：跟随国际金价波动，RSI<30低吸、RSI>70高抛；地缘事件驱动时可加大仓位；止损3%';}
        else if(['债','货币','短融','国开','国债','政金'].some(k=>name.indexOf(k)>=0)||code.startsWith('511')){type='债券/货币ETF';strategy='日内波段：利用债券ETF低波动特性，在盘中低点买入、高点卖出，日收益0.1-0.3%；适合大资金低风险套利';}
        else if(['原油','豆粕','有色','能源','商品','白银','化工'].some(k=>name.indexOf(k)>=0)){type='商品ETF';strategy='事件驱动：关注大宗商品价格和库存变化，突破前日高点买入、跌破前日低点止损；趋势行情可留仓过夜';}
        else if(['跨境','纳指','恒生','日经','标普','德国','法国','港股','中概','海外'].some(k=>name.indexOf(k)>=0)||nu.indexOf('QDII')>=0||code.startsWith('513')||code.startsWith('1596')){type='跨境ETF';strategy='隔日溢价套利：关注夜间海外市场走势，次日开盘顺势操作；若溢价>2%可做空溢价回落；波动大设2%止损';}
        if(type&&amount>1e7){
          list.push({code,name,price:e.f2||0,pct_change:Math.round((e.f3||0)*100)/100,
            amount:Math.round(amount/1e8*10)/10+'亿',turnover:Math.round((e.f8||0)*10)/10,type,strategy});
        }
      });
      list.sort((a,b)=>parseFloat(b.amount)-parseFloat(a.amount));
      window._t0EtfList=list.slice(0,20);
      renderT0List();
    }
  }catch(e){console.log('T+0 ETF JSONP failed:',e.message);}
}
function renderUpDownDist(){
  const el=document.getElementById('upDownDist');if(!el)return;
  el.innerHTML='';
  const mb=window._marketBreadth||{};
  if(!mb.total){el.innerHTML='<div style="color:var(--muted);text-align:center;padding:40px;">全市场涨跌数据加载中...</div>';return;}
  const ch=echarts.init(el);chartInstances.upDownDist=ch;
  const up=mb.up||0,down=mb.down||0,flat=mb.flat||0,sus=mb.suspended||0;
  // 涨停/跌停家数优先取情绪池（实时准确），仅用于统计摘要，不参与直方图分档
  let lu=mb.limit_up||0,ld=mb.limit_down||0;
  if(cycleState&&cycleState.ztC>0)lu=cycleState.ztC;
  if(cycleState&&cycleState.dtC>0)ld=cycleState.dtC;
  const total=mb.total||1;
  // 涨跌幅区间直方图（左跌→右涨，13 档）
  const defHist=[{name:'≤-7%',value:0},{name:'-7~-5%',value:0},{name:'-5~-3%',value:0},{name:'-3~-2%',value:0},{name:'-2~-1%',value:0},{name:'-1~0%',value:0},{name:'平盘',value:0},{name:'0~1%',value:0},{name:'1~2%',value:0},{name:'2~3%',value:0},{name:'3~5%',value:0},{name:'5~7%',value:0},{name:'≥7%',value:0}];
  const hist=(mb.hist&&mb.hist.length)?mb.hist:defHist;
  const catData=hist.map(x=>x.name);
  const valData=hist.map((x,i)=>{
    const c=i<6?tc.down:(i===6?tc.muted:tc.up);
    return {value:x.value,itemStyle:{color:c,borderRadius:[2,2,0,0]}};
  });
  ch.setOption({backgroundColor:'transparent',
    tooltip:tipXL({trigger:'axis',axisPointer:{type:'shadow',shadowStyle:{color:hexA(tc.bg4,.35)}},formatter:function(p){return p[0].name+': <b>'+p[0].value+'</b>只 ('+(p[0].value/total*100).toFixed(1)+'%)';}}),
    grid:{left:36,right:8,top:26,bottom:42},
    xAxis:{type:'category',data:catData,axisLabel:{color:tc.muted,fontSize:9,interval:0,rotate:38},axisLine:{lineStyle:{color:tc.rule}},axisTick:{show:false}},
    yAxis:{type:'value',axisLabel:{color:tc.muted,fontSize:9},splitLine:{lineStyle:{color:tc.bg3,type:'dashed'}}},
    series:[{type:'bar',barWidth:'58%',data:valData,
      label:{show:false},
    }],
  });
  // 统计摘要
  const summary=document.createElement('div');
  summary.style.cssText='text-align:center;font-size:11px;color:var(--muted);margin-top:6px;line-height:1.9;';
  const upPct=(up/total*100).toFixed(1),downPct=(down/total*100).toFixed(1);
  summary.innerHTML='总计 <b style="color:var(--ink);">'+total+'</b> 只 | <b style="color:var(--up);">上涨 '+up+'</b>('+upPct+'%) <b style="color:var(--down);">下跌 '+down+'</b>('+downPct+'%) | 平盘 '+flat+' | <b style="color:var(--up);">涨停 '+lu+'</b> <b style="color:var(--down);">跌停 '+ld+'</b> | 停牌 '+sus+' | '+(up>down*1.5?'<b style="color:var(--up);">多头主导</b>':(down>up*1.5?'<b style="color:var(--down);">空头主导</b>':'<b style="color:var(--gold);">多空均衡</b>'));
  el.appendChild(summary);
}
function renderTodayHot(){
  const el=document.getElementById('todayHot');if(!el)return;
  const hotMap=window._hotSectorStocks||{};
  // 扫描池 代码→胜率 索引，用于给题材机会个股补充量化胜率
  const wrMap={};
  scanData.forEach(s=>{wrMap[s.code]={win_rate:s.bt_win_rate||0,sq:s.signal_quality||50};});
  // 扫描池行业→个股映射（作为机会个股兜底）
  const indMap={};
  scanData.forEach(s=>{const ind=s.industry||'';if(!ind||ind==='未知')return;(indMap[ind]=indMap[ind]||[]).push(s);});
  function matchStocks(sectorName){
    let hits=[];
    for(const ind in indMap){
      if(ind===sectorName||ind.indexOf(sectorName)>=0||sectorName.indexOf(ind)>=0){
        hits=hits.concat(indMap[ind]);
      }
    }
    return hits;
  }
  function renderOpp(st){
    const wr=st.win_rate||0;
    const pct=parseFloat(st.pct_change)||0;
    return '<span style="display:inline-block;margin:1px 3px;padding:1px 7px;background:var(--bg3);border-radius:3px;cursor:pointer;" onclick="showDetail(\''+st.code+'\')" title="'+(wr>0?('回测胜率'+wr.toFixed(1)+'% · 信号质量'+st.sq.toFixed(0)):(pct>=0?'今日上涨':'今日下跌'))+'">'+st.name+' <b style="color:'+(pct>=0?'var(--up)':'var(--down)')+';">'+(pct>=0?'+':'')+pct.toFixed(1)+'%</b>'+(wr>0?' <b style="color:var(--gold);">'+wr.toFixed(0)+'%</b>':'')+'</span>';
  }
  let h='';
  const sectors=window._fullMarketSectors||[];
  // 1. 最强题材方向 → 机会个股（题材成分股TOP，而非全市场涨幅榜）
  const topSec=sectors.filter(s=>s.pct_change>0).sort((a,b)=>b.pct_change-a.pct_change).slice(0,6);
  if(topSec.length){
    h+='<div style="margin-bottom:4px;"><b style="color:var(--up);">🔥 最强题材方向</b> <span style="font-size:10px;color:var(--muted);">（题材 → 机会个股）</span></div>';
    topSec.forEach(s=>{
      const pct=parseFloat(s.pct_change)||0;
      h+='<div style="padding:5px 0;border-bottom:1px dashed var(--rule);">';
      h+='<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:3px;"><span style="display:inline-block;padding:2px 8px;border-radius:6px;background:var(--up-bg);color:var(--up);font-size:12px;font-weight:600;">'+s.name+'</span><span style="font-size:12px;color:var(--up);font-weight:700;">+'+pct.toFixed(2)+'%</span></div>';
      h+='<div style="font-size:11px;line-height:1.7;">';
      const hs=(hotMap[s.name]||[]).map(m=>{
        const w=wrMap[m.code]||{};
        return {code:m.code,name:m.name,pct_change:m.pct_change,win_rate:w.win_rate||0,sq:w.sq||50};
      });
      const opps=hs.length?hs:matchStocks(s.name).slice(0,5).map(m=>({code:m.code,name:m.name,pct_change:m.pct_change||0,win_rate:m.bt_win_rate||0,sq:m.signal_quality||50}));
      if(opps.length){
        h+='<span style="color:var(--muted);">机会个股：</span>';
        h+=opps.slice(0,5).map(renderOpp).join('');
      }else if(s.lead_stock&&s.lead_stock!=='--'){
        h+='<span style="color:var(--muted);">领涨：</span><span style="display:inline-block;padding:1px 7px;background:var(--bg3);border-radius:3px;">'+s.lead_stock+'</span>';
      }else{
        h+='<span style="color:var(--muted);">暂无匹配个股</span>';
      }
      h+='</div></div>';
    });
  }else{
    h+='<div style="color:var(--muted);text-align:center;padding:20px;">当前无上涨题材（或全市场板块数据加载中）</div>';
  }
  // 2. 最弱板块
  const weakSec=sectors.filter(s=>s.pct_change<0).sort((a,b)=>a.pct_change-b.pct_change).slice(0,3);
  if(weakSec.length){h+='<div style="margin-top:10px;margin-bottom:4px;"><b style="color:var(--down);">❄️ 最弱板块：</b>';weakSec.forEach(s=>{h+='<span style="display:inline-block;padding:3px 8px;margin:2px;border-radius:6px;background:var(--down-bg);color:var(--down);font-size:12px;">'+s.name+' '+(parseFloat(s.pct_change)||0).toFixed(2)+'%</span>';});h+='</div>';}
  // 3. 量化信号精选（按回测胜率，非涨幅榜）
  const buys=buySignals.slice().sort((a,b)=>b.win_rate-a.win_rate);
  if(buys.length){h+='<div style="margin-top:10px;"><b style="color:var(--blue);">🎯 量化信号精选：</b>';buys.slice(0,5).forEach(s=>{h+='<span style="display:inline-block;padding:2px 8px;margin:2px;border-radius:6px;background:var(--up-bg);color:var(--up);font-size:12px;cursor:pointer;" onclick="showDetail(\''+s.code+'\')">'+s.name+' 胜率'+s.win_rate.toFixed(0)+'%</span>';});h+='</div>';}
  el.innerHTML=h||'<div style="color:var(--muted);text-align:center;padding:20px;">暂无数据</div>';
}
function renderT0FallbackTable(etfs){
  return '<table style="width:100%;font-size:11px;"><thead><tr><th title="ETF代码">代码</th><th title="ETF名称">名称</th><th title="实时涨跌幅">涨跌</th><th title="成交额">成交</th><th title="T+0类型">类型</th></tr></thead><tbody>'+
    etfs.map(e=>{
      const cls=e.pct_change>=0?'pu':'pd';
      return '<tr><td>'+e.code+'</td><td style="font-weight:600;cursor:pointer;color:var(--blue);" onclick="showEtfDetail(\''+e.code+'\')">'+e.name+'</td><td class="'+cls+'">'+(e.pct_change>=0?'+':'')+e.pct_change.toFixed(2)+'%</td><td style="color:var(--muted);">'+e.amount+'</td><td style="font-size:10px;color:var(--gold);">'+e.type+'</td></tr>';
    }).join('')+'</tbody></table>';
}
async function renderT0List(){
  const el=document.getElementById('t0List');if(!el)return;
  const etfs=window._t0EtfList||[];
  if(!etfs.length){el.innerHTML='<div style="color:var(--muted);text-align:center;padding:30px;">暂无T+0 ETF数据<br><span style="font-size:10px;">T+0 ETF包括跨境/债券/黄金/货币/商品ETF</span></div>';return;}
  el.innerHTML='<div style="color:var(--muted);text-align:center;padding:18px;font-size:12px;">幻方多因子模型分析 T+0 交易机会中...</div>';
  const top=etfs.slice(0,12);
  const res=[];
  for(let i=0;i<top.length;i++){
    const e=top[i];
    try{
      const bars=await fetchIntradayData(e.code);
      if(bars&&bars.length>10){
        const t0=analyzeT0(e.code,bars,e.price);
        if(t0&&t0.entry&&t0.target&&t0.stop)res.push({e:e,t0:t0});
      }
    }catch(err){}
  }
  if(!res.length){
    el.innerHTML='<div style="font-size:11px;color:var(--muted);padding:6px 0;">当前无显著日内波动机会（或非交易时段），以下为可操作 T+0 ETF：</div>'+renderT0FallbackTable(etfs.slice(0,10));
    return;
  }
  res.sort((a,b)=>b.t0.winProb-a.t0.winProb);
  let h='<div style="display:flex;flex-direction:column;gap:8px;">';
  res.forEach(({e,t0})=>{
    const pc=t0.winProb>=60?'var(--up)':(t0.winProb>=52?'var(--gold)':'var(--muted)');
    const lb=t0.badge==='sell';
    const inLb=lb?'高抛价':'低吸价', outLb=lb?'低吸价':'高抛价';
    const topF=(t0.factors||[]).filter(f=>f.on).slice(0,3).map(f=>f.name).join('·');
    const reason=(t0.action||'').replace(/<[^>]+>/g,'');
    h+='<div style="background:var(--bg3);border-radius:8px;padding:8px 10px;border-left:3px solid '+pc+';cursor:pointer;" onclick="showEtfDetail(\''+e.code+'\')">';
    h+='<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:5px;"><b style="font-size:13px;">'+e.name+'</b><span style="font-size:10px;color:var(--muted);">'+e.type+'</span></div>';
    h+='<div style="display:flex;gap:6px;align-items:center;margin-bottom:6px;flex-wrap:wrap;">';
    h+='<span class="t0-badge '+t0.badge+'">'+t0.signal+'</span>';
    h+='<span style="font-size:12px;font-weight:800;color:'+pc+';">胜率 '+t0.winProb+'%</span>';
    h+='<span style="font-size:11px;color:var(--muted);">'+t0.grade+'级 · '+t0.score+'分</span></div>';
    h+='<div style="display:flex;gap:10px;font-size:11px;flex-wrap:wrap;line-height:1.6;">';
    h+='<span><b style="color:var(--blue);">'+inLb+'</b> '+t0.entry.toFixed(3)+'</span>';
    h+='<span><b style="color:var(--up);">'+outLb+'</b> '+t0.target.toFixed(3)+'</span>';
    h+='<span><b style="color:var(--down);">止损</b> '+t0.stop.toFixed(3)+'</span>';
    h+='<span><b>空间</b> '+(t0.revExp>=0?'+':'')+t0.revExp.toFixed(2)+'%</span></div>';
    h+='<div style="display:flex;gap:8px;font-size:10px;color:var(--muted);margin-top:4px;flex-wrap:wrap;">';
    h+='<span>振幅 '+t0.amplitude.toFixed(2)+'%</span><span>量比 '+t0.volRatio.toFixed(2)+'</span><span>ATR '+t0.atrPct.toFixed(1)+'%</span><span>动量 '+(t0.momentumPct>=0?'+':'')+t0.momentumPct.toFixed(2)+'%</span>'+(topF?'<span style="color:var(--gold);">'+topF+'</span>':'');
    h+='</div>';
    h+='<div style="font-size:10.5px;color:var(--muted);margin-top:6px;line-height:1.65;background:var(--bg2);border-left:2px solid var(--gold);padding:5px 8px;border-radius:4px;"><b style="color:var(--gold);">操作依据</b> '+reason+'</div></div>';
  });
  h+='</div>';
  el.innerHTML=h;
}
async function showEtfDetail(code){
  const e=(window._t0EtfList||[]).find(x=>x.code===code);
  if(!e)return;
  let html='<div style="padding:12px 4px;"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;"><div><span style="font-size:22px;font-weight:800;">'+e.name+'</span> <span style="color:var(--muted);">'+e.code+'</span></div><span class="badge '+(e.pct_change>=0?'buy':'sell')+'">'+(e.pct_change>=0?'+':'')+e.pct_change.toFixed(2)+'%</span></div>';
  html+='<div class="dt-grid">';
  html+='<div class="dt-cell"><div class="v '+(e.pct_change>=0?'pu':'pd')+'">'+(e.pct_change>=0?'+':'')+e.pct_change.toFixed(2)+'%</div><div class="l">涨跌幅</div></div>';
  html+='<div class="dt-cell"><div class="v">'+e.amount+'</div><div class="l">成交额</div></div>';
  html+='<div class="dt-cell"><div class="v gold">'+e.type+'</div><div class="l">ETF类型</div></div>';
  html+='<div class="dt-cell"><div class="v">'+(e.turnover||'--')+'%</div><div class="l">换手率</div></div>';
  html+='</div>';
  html+='<div class="watch-advice" style="margin-top:12px;"><b style="color:var(--blue);">交易策略：</b>'+e.strategy+'</div>';
  html+='<div id="etfT0Box" style="margin-top:12px;"><div style="text-align:center;color:var(--muted);font-size:12px;padding:12px;">T+0日内回转分析加载中...</div></div>';
  html+='<div style="margin-top:10px;font-size:11px;color:var(--muted);line-height:1.6;">该ETF支持当日回转交易（T+0），当日买入当日可卖出。适合日内波段操作和跨市场套利。</div></div>';
  document.getElementById('detailContent').innerHTML=html;openOverlay('detailOverlay');
  try{
    const bars=await fetchIntradayData(code);
    if(bars&&bars.length>10){
      const t0=analyzeT0(code,bars,e.price);
      if(t0){
        const box=document.getElementById('etfT0Box');
        if(box){box.innerHTML=renderT0Box(code,t0,'etfDetailChart_');renderT0Chart(code,t0,'etfDetailChart_');}
      }
    }
  }catch(err){}
}
// ===== 需求4: 全市场板块轮动 =====
function renderFullMarketRotation(){
  const el=document.getElementById('fullMarketRotation');if(!el)return;
  if(!window._fullMarketSectors){el.innerHTML='<div style="color:var(--muted);text-align:center;padding:20px;">全市场板块数据加载中...</div>';return;}
  const data=window._fullMarketSectors;
  el.innerHTML='<table style="width:100%;font-size:12px;"><thead><tr><th>板块</th><th>涨跌幅</th><th>领涨股</th><th>成交额</th><th>强度</th></tr></thead><tbody>'+
    data.map(s=>{
      const pct=s.pct_change||0;
      const cls=pct>=0?'pu':'pd';
      const str=pct>=2?'🔥强势':pct>=0?'📈偏强':pct>=-2?'📉偏弱':'❄️弱势';
      return '<tr><td style="font-weight:600;">'+s.name+'</td><td class="'+cls+'">'+(pct>=0?'+':'')+pct.toFixed(2)+'%</td><td style="font-size:11px;">'+(s.lead_stock||'--')+'</td><td>'+(s.amount||'--')+'</td><td>'+str+'</td></tr>';
    }).join('')+'</tbody></table>';
}
// ===== 需求5: 量化策略看板 =====
function renderQuantStrategy(){
  const el=document.getElementById('quantStrategyPanel');if(!el)return;
  // 基于回测数据生成策略
  const buys=buySignals.filter(s=>s.win_rate>=50&&s.profit_factor>=1.2);
  if(!buys.length){el.innerHTML='<div style="color:var(--muted);text-align:center;padding:20px;">暂无符合条件的策略信号</div>';return;}
  // 按策略类型分组
  const strategies={};
  buys.forEach(s=>{
    const sq=scanData.find(x=>x.code===s.code)||{};
    const ind=sq.industry||'未知';
    if(!strategies[ind])strategies[ind]={stocks:[],avgWr:0,avgPf:0};
    strategies[ind].stocks.push(s);
    strategies[ind].avgWr+=s.win_rate;
    strategies[ind].avgPf+=s.profit_factor;
  });
  Object.keys(strategies).forEach(k=>{strategies[k].avgWr/=strategies[k].stocks.length;strategies[k].avgPf/=strategies[k].stocks.length;});
  const sorted=Object.entries(strategies).sort((a,b)=>b[1].avgWr-a[1].avgWr);
  el.innerHTML=sorted.map(([ind,d])=>{
    const grade=d.avgWr>=60?'A':d.avgWr>=52?'B':'C';
    const gradeColor=grade==='A'?'var(--up)':grade==='B'?'var(--gold)':'var(--muted)';
    return '<div style="background:var(--bg3);border-radius:10px;padding:14px;border-left:3px solid '+gradeColor+';">'+
      '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">'+
      '<div><b style="font-size:14px;">'+ind+'板块策略</b> <span style="font-size:11px;color:'+gradeColor+';margin-left:8px;">'+grade+'级</span></div>'+
      '<div style="font-size:12px;color:var(--muted);">平均胜率 '+d.avgWr.toFixed(1)+'% | 盈亏比 '+d.avgPf.toFixed(2)+'</div></div>'+
      '<div style="display:flex;flex-wrap:wrap;gap:6px;">'+
      d.stocks.map(s=>'<span style="display:inline-block;padding:3px 8px;border-radius:6px;background:var(--up-bg);color:var(--up);font-size:11px;cursor:pointer;" onclick="showDetail(\''+s.code+'\')">'+s.name+' '+s.signal+'</span>').join('')+
      '</div><div style="margin-top:8px;font-size:11px;color:var(--muted);">策略逻辑：板块内多只个股触发看涨信号+回测胜率≥50%+盈亏比≥1.2 → 板块级共振买入</div></div>';
  }).join('');
}
// ===== AI Deep-Analysis Engine =====
function aiAnalyze(s){
  const adx=s.bt_adx||0,rsi=s.bt_rsi||50,vol=s.bt_vol_ratio||1,bb=s.bt_bb_pos||0.5,atr=s.bt_atr_pct||2;
  const sec=sectorData.find(x=>x.name===s.industry);
  const secSent=sec?sec.avg_sentiment:0;
  const stage=cycleState?cycleState.stage:null;
  const sq=s.signal_quality||50;
  const steps=[];
  const dim=(k,d,sc)=>{steps.push({k:k,d:d,score:sc});};
  // 1 Trend
  if(adx>=30)dim('趋势强度','ADX='+adx.toFixed(0)+'，趋势强劲，顺势操作胜率最高',90);
  else if(adx>=20)dim('趋势强度','ADX='+adx.toFixed(0)+'，趋势正在形成，可积极参与',70);
  else dim('趋势强度','ADX='+adx.toFixed(0)+'，盘整格局，等待方向选择',42);
  // 2 Momentum
  if(rsi>=50&&rsi<=70)dim('动量状态','RSI='+rsi.toFixed(0)+'，多头动量健康，持有为主',85);
  else if(rsi>70&&rsi<=80)dim('动量状态','RSI='+rsi.toFixed(0)+'，偏强但临近超买，注意冲高节奏',60);
  else if(rsi>80)dim('动量状态','RSI='+rsi.toFixed(0)+'，超买区，追高风险大',38);
  else if(rsi>=40)dim('动量状态','RSI='+rsi.toFixed(0)+'，动量中性，需量能配合',52);
  else dim('动量状态','RSI='+rsi.toFixed(0)+'，弱势区间，反弹以减仓思路对待',32);
  // 3 Volume
  if(vol>=1.2&&vol<=3)dim('量能确认','量比='+vol.toFixed(2)+'，温和放量，资金介入迹象明显',85);
  else if(vol>3&&vol<=5)dim('量能确认','量比='+vol.toFixed(2)+'，放量偏大，防冲高回落',62);
  else if(vol>5)dim('量能确认','量比='+vol.toFixed(2)+'，天量过热，警惕情绪透支',40);
  else if(vol>=0.8)dim('量能确认','量比='+vol.toFixed(2)+'，量能温和，突破需补量',55);
  else dim('量能确认','量比='+vol.toFixed(2)+'，缩量整理，方向未明',38);
  // 4 Bollinger
  if(bb>=0.5&&bb<=0.85)dim('价格位置','布林位置'+(bb*100).toFixed(0)+'%，处于主升通道，趋势与位置俱佳',85);
  else if(bb>0.85&&bb<=0.98)dim('价格位置','布林位置'+(bb*100).toFixed(0)+'%，接近上轨，强势但注意节奏',60);
  else if(bb>0.98)dim('价格位置','布林位置'+(bb*100).toFixed(0)+'%，上轨之外，短期过热',38);
  else if(bb>=0.3)dim('价格位置','布林位置'+(bb*100).toFixed(0)+'%，中轨附近，方向待选择',55);
  else dim('价格位置','布林位置'+(bb*100).toFixed(0)+'%，下轨低位，左侧区域需耐心',45);
  // 5 Volatility
  if(atr<3)dim('波动风险','ATR波幅'+atr.toFixed(1)+'%，波动可控，适合标准仓位',85);
  else if(atr<5)dim('波动风险','ATR波幅'+atr.toFixed(1)+'%，中等波动，仓位适当降低',65);
  else if(atr<8)dim('波动风险','ATR波幅'+atr.toFixed(1)+'%，高波动，建议减半仓位并放宽容忍',42);
  else dim('波动风险','ATR波幅'+atr.toFixed(1)+'%，极端波动，仅适合极小仓位博弈',25);
  // 6 Sector
  if(secSent>=0.15)dim('板块共振','所属板块['+s.industry+']平均情绪+'+secSent.toFixed(2)+'，板块合力向上',85);
  else if(secSent<=-0.15)dim('板块共振','所属板块['+s.industry+']平均情绪'+secSent.toFixed(2)+'，板块走弱形成拖累',30);
  else dim('板块共振','所属板块['+s.industry+']情绪中性，缺乏板块合力',55);
  // 7 Cycle
  const goodSt=['fajiao','gaochao','huinuan'];
  if(goodSt.includes(stage))dim('情绪周期','当前'+(cycleState?cycleState.st.name:'')+'，赚钱效应正循环，个股胜率天然加成',85);
  else if(stage==='fenqi')dim('情绪周期','当前分歧期，仅核心龙头有持续性，需精挑细选',58);
  else dim('情绪周期','当前'+(cycleState?cycleState.st.name:'')+'，逆周期环境，等周期转暖再介入',32);
  // 8 Signal quality
  if(sq>=65)dim('信号质量','历史回测质量分'+sq.toFixed(0)+'，指标组合可靠度高',88);
  else if(sq>=50)dim('信号质量','历史回测质量分'+sq.toFixed(0)+'，中等可靠，需共振确认',58);
  else dim('信号质量','历史回测质量分'+sq.toFixed(0)+'，历史表现不佳，谨慎对待',35);
  const weights=[.15,.12,.13,.10,.08,.12,.15,.15];
  let total=0;steps.forEach((st,i)=>{total+=st.score*weights[i];});
  total=Math.round(total);
  let verdict,cls,advice;
  const stageTxt=cycleState?cycleState.st.name:'当前周期';
  if(total>=72){verdict='强烈关注';cls='v-strong';advice='多维共振向好，若'+stageTxt+'延续，可按计划分批介入：入场参考 '+(s.entry_price?s.entry_price.toFixed(2):'--')+'，止损 '+(s.stop_loss?s.stop_loss.toFixed(2):'--')+'，目标 '+(s.target_price?s.target_price.toFixed(2):'--')+'。';}
  else if(total>=58){verdict='重点关注';cls='v-watch';advice='多数维度偏多但存在短板('+steps.filter(x=>x.score<50).map(x=>x.k).join('/')+')，建议等待量价齐升确认后再介入，或以半仓试探。';}
  else if(total>=45){verdict='观望等待';cls='v-wait';advice='多空交织，安全边际不足。放入自选观察，等待关键维度（量能/板块共振）转好。';}
  else{verdict='建议回避';cls='v-avoid';advice='多项维度转弱，尤其'+steps.slice().sort((a,b)=>a.score-b.score)[0].k+'拖累明显，不建议参与，持仓者反弹减仓。';}
  return {score:total,verdict:verdict,cls:cls,steps:steps,advice:advice};
}
function aiAnalyzeHtml(s){
  const ai=aiAnalyze(s);
  let h='<div class="ai-box"><div class="ai-head"><div><span style="font-weight:800;color:var(--purple);">AI深度推演</span> <span style="font-size:10px;color:var(--muted);">规则×量化融合引擎 · 8维推理链</span></div><span class="ai-verdict '+ai.cls+'">'+ai.verdict+'</span></div>';
  h+='<div class="ai-chain">';
  ai.steps.forEach(st=>{
    const col=st.score>=70?'var(--up)':(st.score>=50?'var(--gold)':'var(--down)');
    h+='<div class="ai-step"><span class="k" style="color:'+col+';">'+st.k+'</span><span class="d">'+st.d+'<b style="color:'+col+';float:right;">'+st.score+'</b></span></div>';
  });
  h+='</div>';
  h+='<div class="ai-score-bar"><span style="font-size:11px;color:var(--muted);">综合评分</span><span class="ai-score" style="color:'+(ai.score>=72?'var(--up)':(ai.score>=58?'var(--gold)':(ai.score>=45?'var(--muted)':'var(--down)')))+';">'+ai.score+'</span><div style="flex:1;height:8px;border-radius:4px;background:var(--bg4);overflow:hidden;"><div style="height:100%;width:'+ai.score+'%;border-radius:4px;background:linear-gradient(90deg,var(--down),var(--gold),var(--up));"></div></div></div>';
  h+='<div class="watch-advice" style="border-color:rgba(168,85,247,.3);"><b>操作结论：</b>'+ai.advice+'</div></div>';
  return h;
}

// ===== Signal Tracker & Calibration (self-learning v2) =====
let signalTracker={};
try{signalTracker=JSON.parse(localStorage.getItem('signal-tracker-v1')||'{}');}catch(e){signalTracker={};}
function saveTracker(){try{localStorage.setItem('signal-tracker-v1',JSON.stringify(signalTracker));}catch(e){}}
function trackSignals(){
  const today=new Date().toISOString().slice(0,10);
  let changed=false;
  buySignals.forEach(s=>{
    const cf=confluenceInfo(s);
    if(cf.grade==='C')return;
    const rec=signalTracker[s.code];
    if(rec&&rec.date===today){if(rec.score!==cf.score||rec.grade!==cf.grade){rec.score=cf.score;rec.grade=cf.grade;changed=true;}return;}
    if(rec&&rec.date!==today&&!rec.resolved){rec.resolved=true;rec.status='superseded';changed=true;}
    signalTracker[s.code]={code:s.code,name:s.name,date:today,entry:s.entry_price,stop:s.stop_loss,target:s.target_price,pred:s.wr_filtered||s.win_rate||50,grade:cf.grade,score:cf.score,resolved:false,status:'open'};
    changed=true;
  });
  if(changed)saveTracker();
}
function evaluateTracked(){
  const codes=Object.keys(signalTracker);let changed=false;
  codes.forEach(code=>{
    const r=signalTracker[code];if(r.resolved)return;
    const rt=realtimeData[code];const cur=rt?rt.price:null;
    if(cur!==null){
      if(cur>=r.target){r.resolved=true;r.status='win';r.exit=cur;changed=true;}
      else if(cur<=r.stop){r.resolved=true;r.status='lose';r.exit=cur;changed=true;}
    }
    const days=(Date.now()-new Date(r.date).getTime())/86400000;
    if(!r.resolved&&days>7){r.resolved=true;r.status=cur!==null?(cur>r.entry?'win':'lose'):'expired';changed=true;}
  });
  if(changed)saveTracker();
  // calibration factor
  const resolved=Object.values(signalTracker).filter(r=>r.resolved&&(r.status==='win'||r.status==='lose'));
  if(resolved.length>=5){
    const realized=resolved.filter(r=>r.status==='win').length/resolved.length*100;
    const predAvg=resolved.reduce((a,r)=>a+r.pred,0)/resolved.length;
    if(predAvg>0){
      const bias=realized/predAvg;
      calibFactor=Math.max(0.9,Math.min(1.1,bias));
    }
  }
}
function renderCalibration(){
  const grid=document.getElementById('calibGrid');if(!grid)return;
  const all=Object.values(signalTracker);
  const resolved=all.filter(r=>r.resolved&&(r.status==='win'||r.status==='lose'));
  const wins=resolved.filter(r=>r.status==='win').length;
  const realized=resolved.length?wins/resolved.length*100:0;
  const predAvg=resolved.length?resolved.reduce((a,r)=>a+r.pred,0)/resolved.length:0;
  const drift=resolved.length?realized-predAvg:0;
  let h='<div class="calib-cell"><div class="v blue">'+all.length+'</div><div class="l">累计追踪信号</div></div>';
  h+='<div class="calib-cell"><div class="v">'+resolved.length+'</div><div class="l">已了结</div></div>';
  h+='<div class="calib-cell"><div class="v '+(realized>=55?'up':(realized>=45?'':'down'))+'">'+(resolved.length?realized.toFixed(1)+'%':'--')+'</div><div class="l">实盘兑现胜率</div></div>';
  h+='<div class="calib-cell"><div class="v gold">'+(predAvg?predAvg.toFixed(1)+'%':'--')+'</div><div class="l">预测平均胜率</div></div>';
  h+='<div class="calib-cell"><div class="v '+(drift>=0?'up':'down')+'">'+(resolved.length?(drift>=0?'+':'')+drift.toFixed(1)+'pp':'--')+'</div><div class="l">预测偏差</div></div>';
  h+='<div class="calib-cell"><div class="v" style="color:var(--purple);">'+calibFactor.toFixed(2)+'x</div><div class="l">共振评分校准系数</div></div>';
  grid.innerHTML=h;
  const list=document.getElementById('trackList');
  if(!list)return;
  const sorted=all.sort((a,b)=>(a.resolved===b.resolved)?(b.date>a.date?1:-1):(a.resolved?1:-1)).slice(0,20);
  const stTag={win:'<span class="badge buy">达标</span>',lose:'<span class="badge sell">止损</span>',open:'<span class="badge watch">进行中</span>',expired:'<span class="badge watch">过期</span>',superseded:'<span class="badge watch">已更新</span>'};
  list.innerHTML=sorted.map(r=>{
    const cur=realtimeData[r.code]?realtimeData[r.code].price:null;
    const pct=cur!==null&&r.entry?((cur-r.entry)/r.entry*100):null;
    return '<div class="track-row"><span style="min-width:70px;font-weight:700;cursor:pointer;" onclick="showDetail(\''+r.code+'\')">'+r.name+'</span><span class="grade-badge grade-'+r.grade+'">'+r.grade+'级 '+r.score+'分</span><span style="color:var(--muted);min-width:76px;">'+r.date+'</span><span style="min-width:130px;color:var(--muted);">入'+r.entry.toFixed(2)+'/止'+r.stop.toFixed(2)+'/目'+r.target.toFixed(2)+'</span>'+(pct!==null?'<span style="color:'+(pct>=0?'var(--up)':'var(--down)')+';min-width:56px;">'+(pct>=0?'+':'')+pct.toFixed(1)+'%</span>':'<span style="min-width:56px;color:var(--muted);">--</span>')+stTag[r.status]+'</div>';
  }).join('')||'<div style="color:var(--muted);font-size:12px;padding:8px;">暂无追踪记录 — 首次打开后自动记录当日A/B级买入信号，并在后续访问中自动验证兑现情况。</div>';
}

// ===== Init =====
renderRegimeBadge();renderRiskDashboard();renderSignalCenter();
renderMarketTrend();renderL1Charts();renderUpDownDist();renderTodayHot();renderT0List();renderRotationInsight();renderRotationSummary();renderRotationQuadrant();renderRotationLadder();renderChainGraph();renderSectorRanking();renderHeatmap();renderL2Charts();renderFullMarketRotation();
renderRadarCharts();renderOpPanel();renderSignalMatrix();renderL3Charts();
renderQuantStrategy();renderLearningCenter();renderWatchlistPage();renderTable();renderIndicatorRef();renderTicker();
renderEmotionCycle();renderLadder();renderPlaybook();renderCalibration();
fetchRealtimeQuotes();fetchRealtimeIndices();fetchIndexKlines();fetchEmotionPools();
fetchMarketBreadth();fetchT0Etfs();
setInterval(fetchRealtimeQuotes,30000);setInterval(fetchRealtimeIndices,30000);setInterval(fetchEmotionPools,60000);
setInterval(fetchMarketBreadth,300000);setInterval(fetchT0Etfs,120000);
setInterval(function(){var a=document.querySelector('.page-section.active');if(a&&a.id==='layer-watch'&&watchList.length>0)loadT0ForWatchlist();},120000);
</script>
</body>
</html>Z__SCAN_DATE__Z__UNIQUE_INDICATORS__Z__STOCKS_JSON__Z__PINYIN_MAP_JSON__Z__INDICATOR_INFO_JSON__Z__INDICATOR_COUNTS_JSON__Z__INDUSTRY_DIST_JSON__Z__CONCEPT_DIST_JSON__Z__SECTOR_DATA_JSON__Z__ROTATION_DATA_JSON__Z__CHAIN_DATA_JSON__Z__ROTATION_INSIGHT_JSON__Z__ACTION_DIST_JSON__Z__BUY_SIGNALS_JSON__Z__SELL_SIGNALS_JSON__Z__MARKET_TREND_JSON__Z__AVG_WIN_RATE__)Z__AVG_WR_FILTERED__Z__AVG_QUANT_SCORE__Z
__AVG_PF__Z__AVG_EXP__Z__KLINE_DATA_JSON__Z__INDICATOR_PERF_JSON__Z__LEARNING_INSIGHTS_JSON__Z__RISK_METRICS_JSON__Z__FULL_MARKET_SECTORS_JSON__Z__HOT_SECTOR_STOCKS_JSON__Z__MARKET_BREADTH_JSON__Z__T0_ETF_LIST_JSON__Z__EMO_POOLS_JSON__)Úexist_okÚwzutf-8)ÚencodingzReport generated: zFile size: i   z.1fz KBzDeployed to web dir: zDeploy copy failed: (,  Ú__doc__r   Úosr
   ZdatetimeÚcollectionsr   r   Zpypinyinr   r   Z
HAS_PINYINÚImportErrorr   r   r!   r%   r(   ÚpathÚdirnameÚabspathÚ__file__ZBASE_DIRr   ZWORK_DIRÚ
expanduserZ
OUTPUT_DIRZRESULT_FILEZINDUSTRY_FILEZBACKTEST_FILEZ
KLINE_FILEZOUTPUT_HTMLÚopenÚfÚloadr)   Zindustry_dataÚexistsr  ZSECTOR_STAGEZ
SECTOR_SUBZCHAIN_EDGESZbacktest_datarF   Zbt_fullZ
bt_signalsZbt_buy_signalsZbt_sell_signalsrI   Z
kline_datarŽ   r�   r•   Zindicator_countsZsignal_datesZstocks_datarP   r‹   r’   r“   r$   rŒ   r”   rž   rŸ   r—   ZtradingZind_infoZbtZbt_sigÚappendÚsortedÚboolZindicator_infoZstockr   Zsorted_indicatorsZbullish_totalZbearish_totalZindustry_distZconcept_distÚcZsector_analysisZindZsar�   rf   ZbnZsector_rankingrÍ   Zrotation_datar›   rÀ   rÚ   rÛ   rÜ   rÝ   rÞ   r€   Úqr8  rÙ   rß   Úsortrç   rè   ré   Z
lead_namesZfollow_namesZ
fade_namesZ
top_sectorrê   Zrotation_insightrï   Zchain_nodesr*   Zchain_edgesZ
chain_dataZrotation_data_jsonZchain_data_jsonZrotation_insight_jsonZmarket_sentimentZaction_distZ
pinyin_mapZFIRST_LETTERZinitialsZfull_pinyinZpinyin_map_jsonZstocks_jsonZindicator_info_jsonr   Zindicator_counts_jsonÚmost_commonZindustry_dist_jsonZconcept_dist_jsonZsector_data_jsonZfull_market_sectorsZrequestsZ_reqZurllib3Zdisable_warningsÚ
exceptionsZInsecureRequestWarningZSessionZ_sessZverifyZheadersÚupdateZ
_api_hostsrŠ   Z_seen_bkZ_poZ_paramsZ_urlZ_respÚ_dataZ_bZ_cÚaddr#   Ú	ExceptionÚprintr�   Zhot_sector_stocksZ_hsZ_bkZ_snZ_mem_paramsZ_mrespZ_mdiffZ_mstocksÚ_mZ_eZfull_market_sectors_jsonZhot_sector_stocks_jsonZmarket_breadthZ_b_fsZ_b_hostsZ_b_allZ_b_totalZ_b_urlZ_firstZ_first_datar   Z_pageZ_pgZ_pg_diffÚextendÚintZ_up_cntZ	_down_cntZ	_flat_cntZ_suspended_cntZ_delisted_cntr  r  Z_sZ_stZ_vr	   Z_fvr&   r'   r  ÚrangeZ
_hist_jsonZ_real_totalZmarket_breadth_jsonZt0_etf_listZ	_e_paramsZ_e_dataZ_eurlZ_e_respÚ_codeÚ_namer   Z_pctZ_amountZ	_turnoverZ_priceÚupperZ_name_upZ	_etf_typeÚ
startswithZ	_strategyZt0_etf_list_jsonZemo_pools_dataZnowÚstrftimeZ_today_yyyymmddZ	_pool_cfgZ_kindZ_sortZ_psZ_keyZ_poolZ_idxZ_uZ_rZ_dZ_pZ_tcZemo_pools_jsonZaction_dist_jsonZbuy_signals_jsonZsell_signals_jsonZmarket_trend_jsonZkline_data_jsonZbt_has_data_countrÅ   Zavg_wr_filteredrÃ   Zavg_pfZavg_expZindicator_global_statsrš   Z	ind_statsZind_nameÚstZgsZindicator_performanceZwrZavg_retZavg_wr_fZcurrent_indsZscoresZpfZexpZlearning_insightsZhigh_wr_indsZlow_wr_indsZtop3Zbest_sectorsZavg_sqZ	total_buyZ
total_sellZ	total_posr3  ÚallÚvaluesZrisk_metricsZindicator_performance_jsonZlearning_insights_jsonZrisk_metrics_jsonr4  r5  r6  Zunique_indicatorsZhtmlZreplacementsr©   ÚvalÚreplaceÚmakedirsÚwriteÚgetsizeÚshutilZ	_web_htmlÚcopy2Z_dwer   r   r   r   Ú<module>   s$   ÿÿÿÿÿÿÿþþþýýýýüüüüûûûûúúùùùøøø	÷	÷	÷	÷
ö
öú

ù€	ÿüüÿÿÿþþþýýüüûúúùøø	÷	÷
ö
öõõôôóóòòññððïï
í
í €þ$8(
8þ".

ú""",üý


ÿ

:úÿ€ÿ :ý€ÿ€ý
ÿÿ"ÿÿ 
"ù"€ÿþ




ü<€þþ
ÿÿ
&VTTL
û€€þ

,
ó
€ÿB€ÿ$$$$$ L€ö$:88:<ü

6€0,4N&D$€&$ $*0ø
ù$
,                     xÿþýüûúùø	÷
öõôóòñð
ïâ ÿ($€ÿ