@@ -612,6 +612,7 @@ <h2 data-i18n="summary">切换摘要</h2>
612612 "tqqq_growth_income" ,
613613 "soxl_soxx_trend_income" ,
614614 "nasdaq_sp500_smart_dca" ,
615+ "hk_low_vol_dividend_quality_snapshot" ,
615616 "global_etf_rotation" ,
616617 "russell_1000_multi_factor_defensive" ,
617618 "mega_cap_leader_rotation_top50_balanced" ,
@@ -621,16 +622,17 @@ <h2 data-i18n="summary">切换摘要</h2>
621622 tqqq_growth_income : "TQQQ Growth Income" ,
622623 soxl_soxx_trend_income : "SOXL/SOXX Trend Income" ,
623624 nasdaq_sp500_smart_dca : "Nasdaq/S&P 500 Smart DCA" ,
625+ hk_low_vol_dividend_quality_snapshot : "HK Low Vol Dividend Quality" ,
624626 global_etf_rotation : "Global ETF Rotation" ,
625627 russell_1000_multi_factor_defensive : "Russell 1000 Defensive" ,
626628 mega_cap_leader_rotation_top50_balanced : "Mega Cap Top 50" ,
627629 } ;
628630
629631 const defaultAccountOptions = {
630632 longbridge : [
631- { key : "hk" , label : "hk" , target_name : "hk" , account_selector : "HK" , default_strategy_profile : "tqqq_growth_income " } ,
633+ { key : "hk" , label : "hk" , target_name : "hk" , account_selector : "HK" , default_strategy_profile : "hk_low_vol_dividend_quality_snapshot " } ,
632634 { key : "sg" , label : "sg" , target_name : "sg" , account_selector : "SG" , default_strategy_profile : "tqqq_growth_income" } ,
633- { key : "paper" , label : "paper" , target_name : "paper" , account_selector : "PAPER" , default_strategy_profile : "tqqq_growth_income " } ,
635+ { key : "paper" , label : "paper" , target_name : "paper" , account_selector : "PAPER" , default_strategy_profile : "mega_cap_leader_rotation_top50_balanced " } ,
634636 ] ,
635637 ibkr : [
636638 {
@@ -665,10 +667,10 @@ <h2 data-i18n="summary">切换摘要</h2>
665667 } ,
666668 ] ,
667669 schwab : [
668- { key : "default" , label : "default" , target_name : "default" , default_strategy_profile : "tqqq_growth_income " } ,
670+ { key : "default" , label : "default" , target_name : "default" , default_strategy_profile : "soxl_soxx_trend_income " } ,
669671 ] ,
670672 firstrade : [
671- { key : "default" , label : "default" , target_name : "default" , default_strategy_profile : "tqqq_growth_income " } ,
673+ { key : "default" , label : "default" , target_name : "default" , default_strategy_profile : "mega_cap_leader_rotation_top50_balanced " } ,
672674 ] ,
673675 } ;
674676
@@ -760,6 +762,7 @@ <h2 data-i18n="summary">切换摘要</h2>
760762 lang : initialLang ,
761763 auth : { available : false , allowed : false , admin : false , login : null } ,
762764 accountOptions : clone ( defaultAccountOptions ) ,
765+ currentStrategies : { } ,
763766 configSource : "default" ,
764767 forms : {
765768 longbridge : { accountKey : "hk" , strategy : "tqqq_growth_income" , executionMode : "live" } ,
@@ -784,9 +787,50 @@ <h2 data-i18n="summary">切换摘要</h2>
784787 return options . find ( ( option ) => option . key === form . accountKey ) || options [ 0 ] ;
785788 }
786789
787- function defaultStrategyForAccount ( account , fallback = "tqqq_growth_income" ) {
788- const profile = String ( account ?. default_strategy_profile || account ?. strategy_profile || "" ) . trim ( ) ;
789- if ( strategyOptions . includes ( profile ) ) return profile ;
790+ function cleanStrategyProfile ( value ) {
791+ const profile = String ( value || "" ) . trim ( ) ;
792+ return / ^ [ a - z 0 - 9 . _ = - ] { 1 , 120 } $ / . test ( profile ) ? profile : "" ;
793+ }
794+
795+ function strategyChoices ( ) {
796+ const choices = [ ...strategyOptions ] ;
797+ const addChoice = ( value ) => {
798+ const profile = cleanStrategyProfile ( value ) ;
799+ if ( profile && ! choices . includes ( profile ) ) choices . push ( profile ) ;
800+ } ;
801+ for ( const platform of Object . keys ( platformMeta ) ) {
802+ for ( const account of optionsFor ( platform ) ) {
803+ addChoice ( account . default_strategy_profile || account . strategy_profile ) ;
804+ }
805+ for ( const entry of Object . values ( state . currentStrategies [ platform ] || { } ) ) {
806+ addChoice ( entry . strategy_profile ) ;
807+ }
808+ addChoice ( state . forms [ platform ] . strategy ) ;
809+ }
810+ return choices ;
811+ }
812+
813+ function strategyLabel ( profile ) {
814+ return strategyLabels [ profile ] || profile ;
815+ }
816+
817+ function currentStrategyForAccount ( platform , account ) {
818+ const byPlatform = state . currentStrategies [ platform ] || { } ;
819+ const keys = [ account ?. key , account ?. target_name , account ?. label ]
820+ . filter ( Boolean )
821+ . map ( ( value ) => String ( value ) ) ;
822+ for ( const key of keys ) {
823+ const profile = cleanStrategyProfile ( byPlatform [ key ] ?. strategy_profile ) ;
824+ if ( profile ) return profile ;
825+ }
826+ return "" ;
827+ }
828+
829+ function defaultStrategyForAccount ( platform , account , fallback = "tqqq_growth_income" ) {
830+ const currentProfile = currentStrategyForAccount ( platform , account ) ;
831+ if ( currentProfile ) return currentProfile ;
832+ const profile = cleanStrategyProfile ( account ?. default_strategy_profile || account ?. strategy_profile ) ;
833+ if ( profile ) return profile ;
790834 const hint = [
791835 account ?. key ,
792836 account ?. label ,
@@ -801,12 +845,18 @@ <h2 data-i18n="summary">切换摘要</h2>
801845 return fallback ;
802846 }
803847
848+ function syncStrategyForAccount ( platform ) {
849+ const account = selectedAccount ( platform ) ;
850+ if ( ! account ) return ;
851+ state . forms [ platform ] . strategy = defaultStrategyForAccount ( platform , account , state . forms [ platform ] . strategy ) ;
852+ }
853+
804854 function ensureAccountSelection ( platform ) {
805855 const options = optionsFor ( platform ) ;
806856 if ( ! options . length ) return ;
807857 if ( ! options . some ( ( option ) => option . key === state . forms [ platform ] . accountKey ) ) {
808858 state . forms [ platform ] . accountKey = options [ 0 ] . key ;
809- state . forms [ platform ] . strategy = defaultStrategyForAccount ( options [ 0 ] , state . forms [ platform ] . strategy ) ;
859+ state . forms [ platform ] . strategy = defaultStrategyForAccount ( platform , options [ 0 ] , state . forms [ platform ] . strategy ) ;
810860 }
811861 }
812862
@@ -859,7 +909,7 @@ <h2 data-i18n="summary">切换摘要</h2>
859909 return [
860910 [ t ( "repository" ) , repositories [ state . selected ] ] ,
861911 [ t ( "selectedAccount" ) , account . label ] ,
862- [ t ( "selectedStrategy" ) , strategyLabels [ inputs . strategy_profile ] || inputs . strategy_profile ] ,
912+ [ t ( "selectedStrategy" ) , strategyLabel ( inputs . strategy_profile ) ] ,
863913 [ t ( "selectedMode" ) , inputs . execution_mode ] ,
864914 [ t ( "target" ) , inputs . target_name ] ,
865915 [ t ( "accountSelector" ) , inputs . account_selector || "auto" ] ,
@@ -894,7 +944,7 @@ <h2 data-i18n="summary">切换摘要</h2>
894944 <span class="platform-copy">
895945 <strong>${ meta . label } </strong>
896946 <span>${ escapeHtml ( account . label ) } </span>
897- <small>${ escapeHtml ( strategyLabels [ form . strategy ] || form . strategy ) } </small>
947+ <small>${ escapeHtml ( strategyLabel ( form . strategy ) ) } </small>
898948 </span>
899949 ` ;
900950 strip . appendChild ( button ) ;
@@ -919,8 +969,8 @@ <h2 data-i18n="summary">切换摘要</h2>
919969 : `<option value="">${ t ( "noAccount" ) } </option>` ;
920970 el ( "account-meta" ) . textContent = accounts . length ? accountMetaText ( platform ) : "" ;
921971
922- strategySelect . innerHTML = strategyOptions . map ( ( strategy ) => (
923- `<option value="${ strategy } " ${ strategy === form . strategy ? "selected" : "" } >${ escapeHtml ( strategyLabels [ strategy ] || strategy ) } </option>`
972+ strategySelect . innerHTML = strategyChoices ( ) . map ( ( strategy ) => (
973+ `<option value="${ strategy } " ${ strategy === form . strategy ? "selected" : "" } >${ escapeHtml ( strategyLabel ( strategy ) ) } </option>`
924974 ) ) . join ( "" ) ;
925975
926976 document . querySelectorAll ( "[data-mode]" ) . forEach ( ( button ) => {
@@ -1002,12 +1052,17 @@ <h2 data-i18n="summary">切换摘要</h2>
10021052 const payload = await response . json ( ) ;
10031053 if ( payload . accountOptions ) {
10041054 state . accountOptions = normalizeAccountOptions ( payload . accountOptions ) ;
1055+ state . currentStrategies = normalizeCurrentStrategies ( payload . currentStrategies || { } ) ;
10051056 state . configSource = "private" ;
1006- for ( const platform of Object . keys ( platformMeta ) ) ensureAccountSelection ( platform ) ;
1057+ for ( const platform of Object . keys ( platformMeta ) ) {
1058+ ensureAccountSelection ( platform ) ;
1059+ syncStrategyForAccount ( platform ) ;
1060+ }
10071061 render ( ) ;
10081062 }
10091063 } catch {
10101064 state . configSource = "default" ;
1065+ state . currentStrategies = { } ;
10111066 }
10121067 }
10131068
@@ -1034,6 +1089,24 @@ <h2 data-i18n="summary">切换摘要</h2>
10341089 return normalized ;
10351090 }
10361091
1092+ function normalizeCurrentStrategies ( raw ) {
1093+ const normalized = { } ;
1094+ for ( const platform of Object . keys ( platformMeta ) ) {
1095+ if ( ! raw [ platform ] || typeof raw [ platform ] !== "object" || Array . isArray ( raw [ platform ] ) ) continue ;
1096+ normalized [ platform ] = { } ;
1097+ for ( const [ key , entry ] of Object . entries ( raw [ platform ] ) ) {
1098+ const profile = cleanStrategyProfile ( entry ?. strategy_profile ) ;
1099+ if ( ! profile ) continue ;
1100+ normalized [ platform ] [ String ( key ) ] = {
1101+ strategy_profile : profile ,
1102+ source : entry ?. source ? String ( entry . source ) : "" ,
1103+ } ;
1104+ }
1105+ if ( ! Object . keys ( normalized [ platform ] ) . length ) delete normalized [ platform ] ;
1106+ }
1107+ return normalized ;
1108+ }
1109+
10371110 async function dispatchSwitch ( ) {
10381111 if ( ! state . auth . allowed ) return ;
10391112 el ( "toast" ) . textContent = t ( "dispatching" ) ;
@@ -1083,10 +1156,7 @@ <h2 data-i18n="summary">切换摘要</h2>
10831156
10841157 el ( "account-select" ) . addEventListener ( "change" , ( ) => {
10851158 state . forms [ state . selected ] . accountKey = el ( "account-select" ) . value ;
1086- state . forms [ state . selected ] . strategy = defaultStrategyForAccount (
1087- selectedAccount ( ) ,
1088- state . forms [ state . selected ] . strategy ,
1089- ) ;
1159+ syncStrategyForAccount ( state . selected ) ;
10901160 render ( ) ;
10911161 } ) ;
10921162
0 commit comments