@@ -946,7 +946,7 @@ <h4 class="panel-title">
946946 < label class ="form-label ">
947947 < i class ="fas fa-coins "> </ i > Asset
948948 </ label >
949- < div class ="asset-button-grid ">
949+ < div class ="asset-button-grid " id =" assetButtonGrid " >
950950 < button type ="button " class ="asset-btn " data-asset ="BTC " onclick ="selectAsset(this, 'BTC') ">
951951 < i class ="fas fa-bitcoin "> </ i > BTC
952952 </ button >
@@ -965,7 +965,7 @@ <h4 class="panel-title">
965965 < button type ="button " class ="asset-btn " data-asset ="TSLA " onclick ="selectAsset(this, 'TSLA') ">
966966 < i class ="fas fa-car "> </ i > TSLA
967967 </ button >
968- < button type ="button " class ="asset-btn custom-asset-btn " onclick ="showCustomAssetInput() ">
968+ < button type ="button " class ="asset-btn custom-asset-btn " id =" customAssetBtn " onclick ="showCustomAssetInput() ">
969969 < i class ="fas fa-plus "> </ i > Custom
970970 </ button >
971971 </ div >
@@ -1163,7 +1163,186 @@ <h4 class="panel-title">
11631163 let currentAsset = '' ;
11641164 let currentTimeframe = '' ;
11651165
1166- // Initialize page
1166+ // Persisted state keys
1167+ const STORAGE_KEYS = {
1168+ CUSTOM_ASSETS : 'quantagent_custom_assets' ,
1169+ SELECTED_ASSET : 'quantagent_selected_asset' ,
1170+ SELECTED_TIMEFRAME : 'quantagent_selected_timeframe'
1171+ } ;
1172+
1173+ // Add or render a custom asset button into the grid
1174+ function addCustomAssetButton ( symbol , isServerLoaded = false ) {
1175+ const grid = document . getElementById ( 'assetButtonGrid' ) ;
1176+ if ( ! grid ) return ;
1177+
1178+ // Prevent duplicates (check existing data-asset values)
1179+ const existing = Array . from ( grid . querySelectorAll ( '.asset-btn' ) ) . some ( btn => btn . dataset . asset === symbol ) ;
1180+ if ( existing ) return ;
1181+
1182+ const btn = document . createElement ( 'button' ) ;
1183+ btn . type = 'button' ;
1184+ btn . className = 'asset-btn' ;
1185+ btn . dataset . asset = symbol ;
1186+ btn . innerHTML = `<i class="fas fa-tag"></i> ${ symbol } ` ;
1187+ btn . addEventListener ( 'click' , ( ) => selectAsset ( btn , symbol ) ) ;
1188+
1189+ // Insert before the "Custom" button if present
1190+ const customBtn = document . getElementById ( 'customAssetBtn' ) ;
1191+ if ( customBtn && customBtn . parentNode === grid ) {
1192+ grid . insertBefore ( btn , customBtn ) ;
1193+ } else {
1194+ grid . appendChild ( btn ) ;
1195+ }
1196+
1197+ // If loaded from server or localStorage, do not auto-select unless it matches stored selection
1198+ const storedSelected = localStorage . getItem ( STORAGE_KEYS . SELECTED_ASSET ) ;
1199+ if ( storedSelected === symbol ) {
1200+ // mimic click to set active and store
1201+ btn . click ( ) ;
1202+ }
1203+ }
1204+
1205+ // Load custom assets from localStorage and server, render them
1206+ function loadCustomAssets ( ) {
1207+ // Load from localStorage first (fast)
1208+ try {
1209+ const local = localStorage . getItem ( STORAGE_KEYS . CUSTOM_ASSETS ) ;
1210+ if ( local ) {
1211+ const list = JSON . parse ( local ) ;
1212+ list . forEach ( sym => addCustomAssetButton ( sym , false ) ) ;
1213+ }
1214+ } catch ( e ) {
1215+ console . warn ( 'Failed to parse local custom assets' , e ) ;
1216+ }
1217+
1218+ // Then attempt to load server-persisted custom assets and merge
1219+ fetch ( '/api/custom-assets' )
1220+ . then ( r => r . json ( ) )
1221+ . then ( data => {
1222+ if ( Array . isArray ( data . custom_assets ) ) {
1223+ data . custom_assets . forEach ( sym => addCustomAssetButton ( sym , true ) ) ;
1224+ // Sync server list into localStorage (merge, dedupe)
1225+ try {
1226+ const local = JSON . parse ( localStorage . getItem ( STORAGE_KEYS . CUSTOM_ASSETS ) || '[]' ) ;
1227+ const merged = Array . from ( new Set ( [ ...( local || [ ] ) , ...data . custom_assets ] ) ) ;
1228+ localStorage . setItem ( STORAGE_KEYS . CUSTOM_ASSETS , JSON . stringify ( merged ) ) ;
1229+ } catch ( e ) {
1230+ console . warn ( 'Failed to sync custom assets to localStorage' , e ) ;
1231+ }
1232+ }
1233+ } )
1234+ . catch ( err => {
1235+ // Not fatal - server might not be reachable
1236+ console . warn ( 'Could not fetch server custom assets' , err ) ;
1237+ } ) ;
1238+ }
1239+
1240+ // Override confirmCustomAsset to create a persistent custom asset and persist to server/localStorage
1241+ function confirmCustomAsset ( ) {
1242+ const customAssetEl = document . getElementById ( 'customAssetInput' ) ;
1243+ const customAsset = customAssetEl . value . trim ( ) ;
1244+
1245+ if ( ! customAsset ) {
1246+ alert ( 'Please enter a custom asset symbol.' ) ;
1247+ return ;
1248+ }
1249+
1250+ // Normalize (trim)
1251+ const symbol = customAsset ;
1252+
1253+ // Save to localStorage list
1254+ try {
1255+ const listRaw = localStorage . getItem ( STORAGE_KEYS . CUSTOM_ASSETS ) ;
1256+ const list = listRaw ? JSON . parse ( listRaw ) : [ ] ;
1257+ if ( ! list . includes ( symbol ) ) {
1258+ list . push ( symbol ) ;
1259+ localStorage . setItem ( STORAGE_KEYS . CUSTOM_ASSETS , JSON . stringify ( list ) ) ;
1260+ }
1261+ } catch ( e ) {
1262+ console . warn ( 'Could not persist custom asset to localStorage' , e ) ;
1263+ }
1264+
1265+ // Persist to server (best-effort)
1266+ fetch ( '/api/save-custom-asset' , {
1267+ method : 'POST' ,
1268+ headers : { 'Content-Type' : 'application/json' } ,
1269+ body : JSON . stringify ( { symbol } )
1270+ } ) . then ( r => r . json ( ) )
1271+ . then ( resp => {
1272+ if ( ! resp . success ) {
1273+ console . warn ( 'Server did not save custom asset' , resp ) ;
1274+ }
1275+ } ) . catch ( err => {
1276+ console . warn ( 'Error saving custom asset to server' , err ) ;
1277+ } ) ;
1278+
1279+ // Add button and select it
1280+ addCustomAssetButton ( symbol ) ;
1281+ // Select the new asset: find button and click
1282+ const grid = document . getElementById ( 'assetButtonGrid' ) ;
1283+ const newBtn = Array . from ( grid . querySelectorAll ( '.asset-btn' ) ) . find ( b => b . dataset . asset === symbol ) ;
1284+ if ( newBtn ) {
1285+ newBtn . click ( ) ;
1286+ }
1287+
1288+ // Hide custom asset input
1289+ document . getElementById ( 'customAssetDiv' ) . style . display = 'none' ;
1290+
1291+ // Clear input
1292+ customAssetEl . value = '' ;
1293+
1294+ // Notify user
1295+ // Use a non-blocking notification (replace alert with console + subtle DOM message if desired)
1296+ console . log ( `Custom asset "${ symbol } " selected and persisted` ) ;
1297+ }
1298+
1299+ // Updated selectAsset to persist selection to localStorage
1300+ function selectAsset ( button , asset ) {
1301+ // Remove active class from all asset buttons
1302+ document . querySelectorAll ( '.asset-btn' ) . forEach ( btn => {
1303+ btn . classList . remove ( 'active' ) ;
1304+ } ) ;
1305+
1306+ // Add active class to clicked button
1307+ button . classList . add ( 'active' ) ;
1308+
1309+ // Store selected asset
1310+ selectedAsset = asset ;
1311+ try {
1312+ localStorage . setItem ( STORAGE_KEYS . SELECTED_ASSET , asset ) ;
1313+ } catch ( e ) {
1314+ console . warn ( 'Could not persist selected asset' , e ) ;
1315+ }
1316+
1317+ // Hide custom asset input if it was showing
1318+ const cav = document . getElementById ( 'customAssetDiv' ) ;
1319+ if ( cav ) cav . style . display = 'none' ;
1320+
1321+ console . log ( 'Selected asset:' , asset ) ;
1322+ }
1323+
1324+ // Persist timeframe selection as well
1325+ function selectTimeframe ( button , timeframe ) {
1326+ // Remove active class from all timeframe buttons
1327+ document . querySelectorAll ( '.timeframe-btn' ) . forEach ( btn => {
1328+ btn . classList . remove ( 'active' ) ;
1329+ } ) ;
1330+
1331+ // Add active class to clicked button
1332+ button . classList . add ( 'active' ) ;
1333+
1334+ // Store selected timeframe
1335+ selectedTimeframe = timeframe ;
1336+ try {
1337+ localStorage . setItem ( STORAGE_KEYS . SELECTED_TIMEFRAME , timeframe ) ;
1338+ } catch ( e ) {
1339+ console . warn ( 'Could not persist selected timeframe' , e ) ;
1340+ }
1341+
1342+ console . log ( 'Selected timeframe:' , timeframe ) ;
1343+ }
1344+
1345+ // On page load, restore selected asset/timeframe and custom assets
11671346 document . addEventListener ( 'DOMContentLoaded' , function ( ) {
11681347 // Set default dates
11691348 const now = new Date ( ) ;
@@ -1180,20 +1359,60 @@ <h4 class="panel-title">
11801359 // Set default end date (today)
11811360 document . getElementById ( 'endDate' ) . value = formatDate ( now ) ;
11821361
1183- // Set default active timeframe
1184- const defaultTimeframeBtn = document . querySelector ( '[data-timeframe="1h"]' ) ;
1185- if ( defaultTimeframeBtn ) {
1186- defaultTimeframeBtn . classList . add ( 'active' ) ;
1187- selectedTimeframe = '1h' ;
1188- }
1362+ // Apply the current-time checkbox behavior immediately on load
1363+ // (this will disable & populate end date/time if the checkbox is checked by default)
1364+ try { handleUseCurrentTimeChange ( ) ; } catch ( e ) { console . warn ( 'handleUseCurrentTimeChange not available yet' , e ) ; }
11891365
1190- // Set default asset
1191- const defaultAssetBtn = document . querySelector ( '[data-asset="BTC"]' ) ;
1192- if ( defaultAssetBtn ) {
1193- defaultAssetBtn . classList . add ( 'active' ) ;
1194- selectedAsset = 'BTC' ;
1366+ // Load custom assets and server-synced assets
1367+ loadCustomAssets ( ) ;
1368+
1369+ // Restore selected timeframe (if saved)
1370+ try {
1371+ const savedTF = localStorage . getItem ( STORAGE_KEYS . SELECTED_TIMEFRAME ) ;
1372+ if ( savedTF ) {
1373+ const tfBtn = Array . from ( document . querySelectorAll ( '.timeframe-btn' ) ) . find ( b => b . dataset . timeframe === savedTF ) ;
1374+ if ( tfBtn ) {
1375+ tfBtn . classList . add ( 'active' ) ;
1376+ selectedTimeframe = savedTF ;
1377+ }
1378+ }
1379+ } catch ( e ) {
1380+ console . warn ( 'Could not restore timeframe from localStorage' , e ) ;
11951381 }
1196-
1382+
1383+ // Restore selected asset (if saved). If it is a custom asset that hasn't been added yet,
1384+ // add it and then select it (addCustomAssetButton handles checking duplicates).
1385+ try {
1386+ const savedAsset = localStorage . getItem ( STORAGE_KEYS . SELECTED_ASSET ) ;
1387+ if ( savedAsset ) {
1388+ // Try to find an existing button
1389+ let btn = Array . from ( document . querySelectorAll ( '.asset-btn' ) ) . find ( b => b . dataset . asset === savedAsset ) ;
1390+ if ( ! btn ) {
1391+ // Add it as custom and then select
1392+ addCustomAssetButton ( savedAsset , false ) ;
1393+ btn = Array . from ( document . querySelectorAll ( '.asset-btn' ) ) . find ( b => b . dataset . asset === savedAsset ) ;
1394+ }
1395+ if ( btn ) {
1396+ btn . classList . add ( 'active' ) ;
1397+ selectedAsset = savedAsset ;
1398+ }
1399+ } else {
1400+ // fallback defaults from original code
1401+ const defaultTimeframeBtn = document . querySelector ( '[data-timeframe="1h"]' ) ;
1402+ if ( defaultTimeframeBtn && ! selectedTimeframe ) {
1403+ defaultTimeframeBtn . classList . add ( 'active' ) ;
1404+ selectedTimeframe = '1h' ;
1405+ }
1406+ const defaultAssetBtn = document . querySelector ( '[data-asset="BTC"]' ) ;
1407+ if ( defaultAssetBtn && ! selectedAsset ) {
1408+ defaultAssetBtn . classList . add ( 'active' ) ;
1409+ selectedAsset = 'BTC' ;
1410+ }
1411+ }
1412+ } catch ( e ) {
1413+ console . warn ( 'Could not restore selected asset from localStorage' , e ) ;
1414+ }
1415+
11971416 document . getElementById ( 'useCurrentTime' ) . addEventListener ( 'change' , handleUseCurrentTimeChange ) ;
11981417
11991418 // Set up date/time validation
@@ -1255,10 +1474,16 @@ <h4 class="panel-title">
12551474
12561475 // Store selected asset
12571476 selectedAsset = asset ;
1258-
1477+ try {
1478+ localStorage . setItem ( STORAGE_KEYS . SELECTED_ASSET , asset ) ;
1479+ } catch ( e ) {
1480+ console . warn ( 'Could not persist selected asset' , e ) ;
1481+ }
1482+
12591483 // Hide custom asset input if it was showing
1260- document . getElementById ( 'customAssetDiv' ) . style . display = 'none' ;
1261-
1484+ const cav = document . getElementById ( 'customAssetDiv' ) ;
1485+ if ( cav ) cav . style . display = 'none' ;
1486+
12621487 console . log ( 'Selected asset:' , asset ) ;
12631488 }
12641489
@@ -1277,38 +1502,61 @@ <h4 class="panel-title">
12771502 }
12781503
12791504 function confirmCustomAsset ( ) {
1280- const customAsset = document . getElementById ( 'customAssetInput' ) . value . trim ( ) ;
1281-
1505+ const customAssetEl = document . getElementById ( 'customAssetInput' ) ;
1506+ const customAsset = customAssetEl . value . trim ( ) ;
1507+
12821508 if ( ! customAsset ) {
12831509 alert ( 'Please enter a custom asset symbol.' ) ;
12841510 return ;
12851511 }
1286-
1287- // Store selected asset
1288- selectedAsset = customAsset ;
1289-
1290- // Hide custom asset input
1291- document . getElementById ( 'customAssetDiv' ) . style . display = 'none' ;
1292-
1293- // Show success message
1294- alert ( `Custom asset "${ customAsset } " selected successfully!` ) ;
1295-
1296- console . log ( 'Selected custom asset:' , customAsset ) ;
1297- }
1298-
1299- function selectTimeframe ( button , timeframe ) {
1300- // Remove active class from all timeframe buttons
1301- document . querySelectorAll ( '.timeframe-btn' ) . forEach ( btn => {
1302- btn . classList . remove ( 'active' ) ;
1303- } ) ;
1304-
1305- // Add active class to clicked button
1306- button . classList . add ( 'active' ) ;
1307-
1308- // Store selected timeframe
1309- selectedTimeframe = timeframe ;
1310-
1311- console . log ( 'Selected timeframe:' , timeframe ) ;
1512+
1513+ // Normalize (trim)
1514+ const symbol = customAsset ;
1515+
1516+ // Save to localStorage list
1517+ try {
1518+ const listRaw = localStorage . getItem ( STORAGE_KEYS . CUSTOM_ASSETS ) ;
1519+ const list = listRaw ? JSON . parse ( listRaw ) : [ ] ;
1520+ if ( ! list . includes ( symbol ) ) {
1521+ list . push ( symbol ) ;
1522+ localStorage . setItem ( STORAGE_KEYS . CUSTOM_ASSETS , JSON . stringify ( list ) ) ;
1523+ }
1524+ } catch ( e ) {
1525+ console . warn ( 'Could not persist custom asset to localStorage' , e ) ;
1526+ }
1527+
1528+ // Persist to server (best-effort)
1529+ fetch ( '/api/save-custom-asset' , {
1530+ method : 'POST' ,
1531+ headers : { 'Content-Type' : 'application/json' } ,
1532+ body : JSON . stringify ( { symbol } )
1533+ } ) . then ( r => r . json ( ) )
1534+ . then ( resp => {
1535+ if ( ! resp . success ) {
1536+ console . warn ( 'Server did not save custom asset' , resp ) ;
1537+ }
1538+ } ) . catch ( err => {
1539+ console . warn ( 'Error saving custom asset to server' , err ) ;
1540+ } ) ;
1541+
1542+ // Add button and select it
1543+ addCustomAssetButton ( symbol ) ;
1544+ // Select the new asset: find button and click
1545+ const grid = document . getElementById ( 'assetButtonGrid' ) ;
1546+ const newBtn = Array . from ( grid . querySelectorAll ( '.asset-btn' ) ) . find ( b => b . dataset . asset === symbol ) ;
1547+ if ( newBtn ) {
1548+ newBtn . click ( ) ;
1549+ }
1550+
1551+ // Hide custom asset input
1552+ document . getElementById ( 'customAssetDiv' ) . style . display = 'none' ;
1553+
1554+ // Clear input
1555+ customAssetEl . value = '' ;
1556+
1557+ // Notify user
1558+ // Use a non-blocking notification (replace alert with console + subtle DOM message if desired)
1559+ console . log ( `Custom asset "${ symbol } " selected and persisted` ) ;
13121560 }
13131561
13141562 function runAnalysis ( ) {
0 commit comments