@@ -158,12 +158,40 @@ function makeCurrentStrategies(platform, overrides = {}) {
158158
159159function currentEntryForAccount ( state , platform , account ) {
160160 const byPlatform = state . currentStrategies [ platform ] || { } ;
161+ const normalizeAccountLookupKey = ( value ) => String ( value || "" ) . trim ( ) . toLowerCase ( ) ;
161162 const keys = [ account ?. key , account ?. target_name , account ?. label ] . filter ( Boolean ) . map ( String ) ;
163+
164+ const candidates = [ ...new Set ( keys . map ( normalizeAccountLookupKey ) . filter ( Boolean ) ) ] ;
165+
162166 for ( const key of keys ) {
163167 const entry = byPlatform [ key ] ;
164- if ( entry && typeof entry === "object" ) return entry ;
168+ if ( entry && typeof entry === "object" ) {
169+ if ( ! entry . strategy_profile ) {
170+ return { ...entry , strategy_profile : account ?. default_strategy_profile || "" , source : "worker+account_defaults" } ;
171+ }
172+ return entry ;
173+ }
165174 }
166- return null ;
175+
176+ for ( const [ rawKey , entry ] of Object . entries ( byPlatform ) ) {
177+ if ( ! entry || typeof entry !== "object" ) continue ;
178+ if ( candidates . includes ( normalizeAccountLookupKey ( rawKey ) ) ) {
179+ if ( ! entry . strategy_profile ) {
180+ return {
181+ ...entry ,
182+ strategy_profile : account ?. default_strategy_profile || "" ,
183+ source : "worker+account_defaults" ,
184+ } ;
185+ }
186+ return entry ;
187+ }
188+ }
189+
190+ return {
191+ runtime_target_enabled : true ,
192+ strategy_profile : account ?. default_strategy_profile || account ?. strategy_profile || "" ,
193+ source : "account_defaults" ,
194+ } ;
167195}
168196
169197function currentReservePolicyForAccount ( state , platform , account ) {
@@ -813,12 +841,12 @@ console.log("\n=== 9. syncRuntimeTargetForAccount (解析为具体值) ===\n");
813841 assert ( form . runtimeTargetMode === "disabled" , "9d: runtime_target='FALSE' → 'disabled'" ) ;
814842}
815843
816- // 9e: no entry → stays "current"
844+ // 9e: no entry → use default enabled状态
817845{
818846 const state = { currentStrategies : { } } ;
819847 const form = { runtimeTargetMode : "current" , runtimeTargetTouched : false } ;
820848 syncRuntimeTargetForAccount ( state , form , "ibkr" , makeAccount ( "preview" ) ) ;
821- assert ( form . runtimeTargetMode === "current " , "9c : no entry → 'current' " ) ;
849+ assert ( form . runtimeTargetMode === "enabled " , "9e : no entry → default enabled " ) ;
822850}
823851
824852// 9f: touched → skip
@@ -937,7 +965,99 @@ console.log("\n=== 10. syncIncomeLayerForAccount (解析为具体值) ===\n");
937965}
938966
939967// ============================================================
940- // 11. syncOptionOverlayForAccount (解析为具体值)
968+ // 11. currentEntryForAccount 映射健壮性 ===
969+ // ============================================================
970+
971+ console . log ( "\n=== 11. currentEntryForAccount 映射健壮性 ===\n" ) ;
972+
973+ // 11a: 按原 key 命中
974+ {
975+ const state = {
976+ currentStrategies : {
977+ ibkr : {
978+ preview : { runtime_target_enabled : true } ,
979+ } ,
980+ } ,
981+ } ;
982+ const account = makeAccount ( "preview" ) ;
983+ const entry = currentEntryForAccount ( state , "ibkr" , account ) ;
984+ assert ( entry && entry . runtime_target_enabled === true , "11a: exact key should hit current entry" ) ;
985+ }
986+
987+ // 11b: key 大小写不一致也能命中
988+ {
989+ const state = {
990+ currentStrategies : {
991+ longbridge : {
992+ sg : { runtime_target_enabled : false } ,
993+ } ,
994+ } ,
995+ } ;
996+ const account = {
997+ key : "SG" ,
998+ target_name : "sg" ,
999+ label : "SG" ,
1000+ } ;
1001+ const entry = currentEntryForAccount ( state , "longbridge" , account ) ;
1002+ assert ( entry && entry . runtime_target_enabled === false , "11b: case-insensitive key should match normalized" ) ;
1003+ }
1004+
1005+ // 11c: key 前后空白也能命中
1006+ {
1007+ const state = {
1008+ currentStrategies : {
1009+ longbridge : {
1010+ sg : { runtime_target_enabled : false } ,
1011+ } ,
1012+ } ,
1013+ } ;
1014+ const account = {
1015+ key : " sg " ,
1016+ target_name : "SG" ,
1017+ label : "longbridge sg" ,
1018+ } ;
1019+ const entry = currentEntryForAccount ( state , "longbridge" , account ) ;
1020+ assert ( entry && entry . runtime_target_enabled === false , "11c: whitespace-trimmed key should match" ) ;
1021+ }
1022+
1023+ // 11d: 无 key 命中时回退到默认账号配置
1024+ {
1025+ const state = {
1026+ currentStrategies : {
1027+ longbridge : { } ,
1028+ } ,
1029+ } ;
1030+ const account = {
1031+ key : "sg" ,
1032+ target_name : "sg" ,
1033+ label : "sg" ,
1034+ default_strategy_profile : "tqqq_growth_income" ,
1035+ } ;
1036+ const entry = currentEntryForAccount ( state , "longbridge" , account ) ;
1037+ assert ( Boolean ( entry ) , "11d: missing entry should return synthesized defaults" ) ;
1038+ assert ( entry . runtime_target_enabled === true , "11d: missing entry should default runtime_target_enabled=enabled" ) ;
1039+ }
1040+
1041+ // 11e: 当前条目存在 strategy_profile 时不改写策略
1042+ {
1043+ const state = {
1044+ currentStrategies : {
1045+ longbridge : {
1046+ sg : { strategy_profile : "soxl_soxx_trend_income" , runtime_target_enabled : false } ,
1047+ } ,
1048+ } ,
1049+ } ;
1050+ const account = {
1051+ key : "SG" ,
1052+ target_name : "SG" ,
1053+ label : "sg" ,
1054+ } ;
1055+ const entry = currentEntryForAccount ( state , "longbridge" , account ) ;
1056+ assert ( entry ?. strategy_profile === "soxl_soxx_trend_income" , "11e: existing strategy_profile should be preserved" ) ;
1057+ }
1058+
1059+ // ============================================================
1060+ // 12. syncOptionOverlayForAccount (解析为具体值)
9411061// ============================================================
9421062
9431063function optionOverlaySupported ( profile ) { return profile !== "no_overlay" ; }
@@ -973,54 +1093,54 @@ function syncOptionOverlayForAccount(state, form, platform, account) {
9731093 }
9741094}
9751095
976- console . log ( "\n=== 11 . syncOptionOverlayForAccount (解析为具体值) ===\n" ) ;
1096+ console . log ( "\n=== 12 . syncOptionOverlayForAccount (解析为具体值) ===\n" ) ;
9771097
978- // 11a : option_overlay_enabled: true → enabled
1098+ // 12a : option_overlay_enabled: true → enabled
9791099{
9801100 const state = { currentStrategies : makeCurrentStrategies ( "ibkr" , { extra : { option_overlay_enabled : true } } ) } ;
9811101 const form = { optionOverlayMode : "current" , optionOverlayTouched : false , strategy : "some_profile" } ;
9821102 syncOptionOverlayForAccount ( state , form , "ibkr" , makeAccount ( "preview" ) ) ;
983- assert ( form . optionOverlayMode === "enabled" , "11a : option overlay enabled → 'enabled'" ) ;
1103+ assert ( form . optionOverlayMode === "enabled" , "12a : option overlay enabled → 'enabled'" ) ;
9841104}
9851105
986- // 11b : option_overlay_enabled: false → disabled
1106+ // 12b : option_overlay_enabled: false → disabled
9871107{
9881108 const state = { currentStrategies : makeCurrentStrategies ( "ibkr" , { extra : { option_overlay_enabled : false } } ) } ;
9891109 const form = { optionOverlayMode : "current" , optionOverlayTouched : false , strategy : "some_profile" } ;
9901110 syncOptionOverlayForAccount ( state , form , "ibkr" , makeAccount ( "preview" ) ) ;
991- assert ( form . optionOverlayMode === "disabled" , "11b : option overlay disabled → 'disabled'" ) ;
1111+ assert ( form . optionOverlayMode === "disabled" , "12b : option overlay disabled → 'disabled'" ) ;
9921112}
9931113
994- // 11c : no entry + supported → enabled (default)
1114+ // 12c : no entry + supported → enabled (default)
9951115{
9961116 const state = { currentStrategies : { } } ;
9971117 const form = { optionOverlayMode : "current" , optionOverlayTouched : false , strategy : "some_profile" } ;
9981118 syncOptionOverlayForAccount ( state , form , "ibkr" , makeAccount ( "preview" ) ) ;
999- assert ( form . optionOverlayMode === "enabled" , "11c : no entry + supported → 'enabled'" ) ;
1119+ assert ( form . optionOverlayMode === "enabled" , "12c : no entry + supported → 'enabled'" ) ;
10001120}
10011121
1002- // 11d : not supported → disabled
1122+ // 12d : not supported → disabled
10031123{
10041124 const state = { currentStrategies : makeCurrentStrategies ( "ibkr" , { extra : { option_overlay_enabled : true } } ) } ;
10051125 const form = { optionOverlayMode : "current" , optionOverlayTouched : false , strategy : "no_overlay" } ;
10061126 syncOptionOverlayForAccount ( state , form , "ibkr" , makeAccount ( "preview" ) ) ;
1007- assert ( form . optionOverlayMode === "disabled" , "11d : not supported → 'disabled'" ) ;
1127+ assert ( form . optionOverlayMode === "disabled" , "12d : not supported → 'disabled'" ) ;
10081128}
10091129
1010- // 11e : account has explicit mode → use it
1130+ // 12e : account has explicit mode → use it
10111131{
10121132 const form = { optionOverlayMode : "current" , optionOverlayTouched : false , strategy : "some_profile" } ;
10131133 syncOptionOverlayForAccount ( { } , form , "ibkr" , { key : "preview" , option_overlay_mode : "disabled" } ) ;
1014- assert ( form . optionOverlayMode === "disabled" , "11e : account explicit 'disabled' → 'disabled'" ) ;
1134+ assert ( form . optionOverlayMode === "disabled" , "12e : account explicit 'disabled' → 'disabled'" ) ;
10151135}
10161136
10171137// ============================================================
1018- // 12 . 互斥 UI 不再禁用选项 (回归测试)
1138+ // 13 . 互斥 UI 不再禁用选项 (回归测试)
10191139// ============================================================
10201140
1021- console . log ( "\n=== 12 . 互斥 UI 不再禁用选项 ===\n" ) ;
1141+ console . log ( "\n=== 13 . 互斥 UI 不再禁用选项 ===\n" ) ;
10221142
1023- // 12a : 预留现金覆盖活跃时,select cash-only=disabled 不应因 option.disabled 被阻挡
1143+ // 13a : 预留现金覆盖活跃时,select cash-only=disabled 不应因 option.disabled 被阻挡
10241144// (此测试验证 reconcileExecutionCashPolicy 可处理用户选择,无需前置禁用)
10251145{
10261146 const form = {
@@ -1036,13 +1156,13 @@ console.log("\n=== 12. 互斥 UI 不再禁用选项 ===\n");
10361156 form . cashOnlyExecutionTouched = true ;
10371157 reconcileExecutionCashPolicy ( form , "margin" ) ;
10381158 // 预留现金应被清除
1039- assert ( form . reservePolicyMode === "none" , "12a : margin=yes → reserve cleared" ) ;
1040- assert ( form . cashOnlyExecutionMode === "disabled" , "12a : margin stays 'disabled'" ) ;
1159+ assert ( form . reservePolicyMode === "none" , "13a : margin=yes → reserve cleared" ) ;
1160+ assert ( form . cashOnlyExecutionMode === "disabled" , "13a : margin stays 'disabled'" ) ;
10411161 // 不再冲突
1042- assert ( executionCashPolicyConflict ( form ) === false , "12a : no conflict after reconciliation" ) ;
1162+ assert ( executionCashPolicyConflict ( form ) === false , "13a : no conflict after reconciliation" ) ;
10431163}
10441164
1045- // 12b : margin 启用时,reserve 下拉框不应 disabled,用户可选 ratio
1165+ // 13b : margin 启用时,reserve 下拉框不应 disabled,用户可选 ratio
10461166{
10471167 const form = {
10481168 cashOnlyExecutionMode : "disabled" , // margin enabled
@@ -1058,18 +1178,18 @@ console.log("\n=== 12. 互斥 UI 不再禁用选项 ===\n");
10581178 form . reservedCashTouched = true ;
10591179 reconcileExecutionCashPolicy ( form , "reserve" ) ;
10601180 // 融资应被禁用
1061- assert ( form . cashOnlyExecutionMode === "enabled" , "12b : reserve=ratio → margin disabled" ) ;
1062- assert ( form . reservePolicyMode === "ratio" , "12b : reserve stays 'ratio'" ) ;
1063- assert ( executionCashPolicyConflict ( form ) === false , "12b : no conflict after reconciliation" ) ;
1181+ assert ( form . cashOnlyExecutionMode === "enabled" , "13b : reserve=ratio → margin disabled" ) ;
1182+ assert ( form . reservePolicyMode === "ratio" , "13b : reserve stays 'ratio'" ) ;
1183+ assert ( executionCashPolicyConflict ( form ) === false , "13b : no conflict after reconciliation" ) ;
10641184}
10651185
10661186// ============================================================
1067- // 13 . 融资切换 save/restore 预留现金配置
1187+ // 14 . 融资切换 save/restore 预留现金配置
10681188// ============================================================
10691189
1070- console . log ( "\n=== 13 . Save/Restore 预留现金配置 ===\n" ) ;
1190+ console . log ( "\n=== 14 . Save/Restore 预留现金配置 ===\n" ) ;
10711191
1072- // 13a : 用户开融资 → 值保留 → 关融资 → 值恢复
1192+ // 14a : 用户开融资 → 值保留 → 关融资 → 值恢复
10731193{
10741194 const form = {
10751195 cashOnlyExecutionMode : "enabled" , // 当前:不允许融资
@@ -1083,20 +1203,20 @@ console.log("\n=== 13. Save/Restore 预留现金配置 ===\n");
10831203 form . cashOnlyExecutionMode = "disabled" ;
10841204 form . cashOnlyExecutionTouched = true ;
10851205 reconcileExecutionCashPolicy ( form , "margin" ) ;
1086- assert ( form . reservePolicyMode === "none" , "13a -step1: reserve cleared to 'none'" ) ;
1087- assert ( form . reservedCashRatio === "0.05" , "13a -step1: ratio value preserved" ) ;
1088- assert ( form . _prevReserve . mode === "ratio" , "13a -step1: prev mode saved" ) ;
1089- assert ( form . _prevReserve . ratio === "0.05" , "13a -step1: prev ratio saved" ) ;
1206+ assert ( form . reservePolicyMode === "none" , "14a -step1: reserve cleared to 'none'" ) ;
1207+ assert ( form . reservedCashRatio === "0.05" , "14a -step1: ratio value preserved" ) ;
1208+ assert ( form . _prevReserve . mode === "ratio" , "14a -step1: prev mode saved" ) ;
1209+ assert ( form . _prevReserve . ratio === "0.05" , "14a -step1: prev ratio saved" ) ;
10901210
10911211 // 用户选"允许融资: 否" → 恢复
10921212 form . cashOnlyExecutionMode = "enabled" ;
10931213 restoreReserveAfterMarginDisabled ( form ) ;
1094- assert ( form . reservePolicyMode === "ratio" , "13a -step2: reserve restored to 'ratio'" ) ;
1095- assert ( form . reservedCashRatio === "0.05" , "13a -step2: ratio restored" ) ;
1096- assert ( form . _prevReserve === undefined , "13a -step2: _prevReserve cleaned up" ) ;
1214+ assert ( form . reservePolicyMode === "ratio" , "14a -step2: reserve restored to 'ratio'" ) ;
1215+ assert ( form . reservedCashRatio === "0.05" , "14a -step2: ratio restored" ) ;
1216+ assert ( form . _prevReserve === undefined , "14a -step2: _prevReserve cleaned up" ) ;
10971217}
10981218
1099- // 13b : 用户开融资 → 关融资 → 手动改 reserve → 再开再关不复原旧值
1219+ // 14b : 用户开融资 → 关融资 → 手动改 reserve → 再开再关不复原旧值
11001220{
11011221 const form = {
11021222 cashOnlyExecutionMode : "enabled" ,
@@ -1109,13 +1229,13 @@ console.log("\n=== 13. Save/Restore 预留现金配置 ===\n");
11091229 // 开融资
11101230 form . cashOnlyExecutionMode = "disabled" ;
11111231 reconcileExecutionCashPolicy ( form , "margin" ) ;
1112- assert ( form . reservePolicyMode === "none" , "13b -step1: cleared" ) ;
1113- assert ( form . _prevReserve . mode === "max" , "13b -step1: prev 'max' saved" ) ;
1232+ assert ( form . reservePolicyMode === "none" , "14b -step1: cleared" ) ;
1233+ assert ( form . _prevReserve . mode === "max" , "14b -step1: prev 'max' saved" ) ;
11141234
11151235 // 关融资 → 恢复
11161236 form . cashOnlyExecutionMode = "enabled" ;
11171237 restoreReserveAfterMarginDisabled ( form ) ;
1118- assert ( form . reservePolicyMode === "max" , "13b -step2: restored to 'max'" ) ;
1238+ assert ( form . reservePolicyMode === "max" , "14b -step2: restored to 'max'" ) ;
11191239
11201240 // 用户手动改为 floor
11211241 form . reservePolicyMode = "floor" ;
@@ -1126,17 +1246,17 @@ console.log("\n=== 13. Save/Restore 预留现金配置 ===\n");
11261246 // 再开融资
11271247 form . cashOnlyExecutionMode = "disabled" ;
11281248 reconcileExecutionCashPolicy ( form , "margin" ) ;
1129- assert ( form . _prevReserve . mode === "floor" , "13b -step3: prev 'floor' saved" ) ;
1130- assert ( form . _prevReserve . floor === "5000" , "13b -step3: prev floor saved" ) ;
1249+ assert ( form . _prevReserve . mode === "floor" , "14b -step3: prev 'floor' saved" ) ;
1250+ assert ( form . _prevReserve . floor === "5000" , "14b -step3: prev floor saved" ) ;
11311251
11321252 // 再关融资 → 恢复 floor
11331253 form . cashOnlyExecutionMode = "enabled" ;
11341254 restoreReserveAfterMarginDisabled ( form ) ;
1135- assert ( form . reservePolicyMode === "floor" , "13b -step4: restored to 'floor'" ) ;
1136- assert ( form . minReservedCashUsd === "5000" , "13b -step4: floor restored" ) ;
1255+ assert ( form . reservePolicyMode === "floor" , "14b -step4: restored to 'floor'" ) ;
1256+ assert ( form . minReservedCashUsd === "5000" , "14b -step4: floor restored" ) ;
11371257}
11381258
1139- // 13c : reserve=none 时开融资不应保存 _prevReserve
1259+ // 14c : reserve=none 时开融资不应保存 _prevReserve
11401260{
11411261 const form = {
11421262 cashOnlyExecutionMode : "enabled" ,
@@ -1147,10 +1267,10 @@ console.log("\n=== 13. Save/Restore 预留现金配置 ===\n");
11471267 } ;
11481268 form . cashOnlyExecutionMode = "disabled" ;
11491269 reconcileExecutionCashPolicy ( form , "margin" ) ;
1150- assert ( form . _prevReserve === undefined , "13c : reserve=none → no _prevReserve saved" ) ;
1270+ assert ( form . _prevReserve === undefined , "14c : reserve=none → no _prevReserve saved" ) ;
11511271}
11521272
1153- // 13d : margin 切到 "enabled" 但无 _prevReserve → 不动
1273+ // 14d : margin 切到 "enabled" 但无 _prevReserve → 不动
11541274{
11551275 const form = {
11561276 cashOnlyExecutionMode : "disabled" ,
@@ -1161,10 +1281,10 @@ console.log("\n=== 13. Save/Restore 预留现金配置 ===\n");
11611281 } ;
11621282 form . cashOnlyExecutionMode = "enabled" ;
11631283 restoreReserveAfterMarginDisabled ( form ) ;
1164- assert ( form . reservePolicyMode === "none" , "13d : no _prevReserve → stays 'none'" ) ;
1284+ assert ( form . reservePolicyMode === "none" , "14d : no _prevReserve → stays 'none'" ) ;
11651285}
11661286
1167- // 13e : account switch 清除 _prevReserve
1287+ // 14e : account switch 清除 _prevReserve
11681288{
11691289 const form = {
11701290 cashOnlyExecutionMode : "disabled" ,
@@ -1174,7 +1294,7 @@ console.log("\n=== 13. Save/Restore 预留现金配置 ===\n");
11741294 _prevReserve : { mode : "max" , floor : "10000" , ratio : "0.05" } ,
11751295 } ;
11761296 delete form . _prevReserve ; // simulate account switch
1177- assert ( form . _prevReserve === undefined , "13e : account switch clears _prevReserve" ) ;
1297+ assert ( form . _prevReserve === undefined , "14e : account switch clears _prevReserve" ) ;
11781298}
11791299
11801300// ============================================================
0 commit comments