diff --git a/content_scripts/action.js b/content_scripts/action.js index a35cecb..e6a0db2 100644 --- a/content_scripts/action.js +++ b/content_scripts/action.js @@ -5,7 +5,8 @@ const action = { action.saveParameters = async () => { const strategyData = await tv.getStrategy(null, true) if(!strategyData || !strategyData.hasOwnProperty('name') || !strategyData.hasOwnProperty('properties') || !strategyData.properties) { - await ui.showWarningPopup('Please open the indicator (strategy) parameters window before saving them to a file.') + await ui.showErrorPopup('The current indicator/strategy do not contain inputs that can be saved.') + // await ui.showWarningPopup('Please open the indicator (strategy) parameters window before saving them to a file.') return } let strategyParamsCSV = `Name,Value\n"__indicatorName",${JSON.stringify(strategyData.name)}\n` @@ -30,7 +31,7 @@ action.uploadStrategyTestParameters = async () => { action.getStrategyTemplate = async () => { const strategyData = await tv.getStrategy() if(!strategyData || !strategyData.hasOwnProperty('name') || !strategyData.hasOwnProperty('properties') || !strategyData.properties) { - await ui.showErrorPopup('It was not possible to find a strategy with parameters among the indicators. Add it to the chart and try again.') + await ui.showErrorPopup('The current strategy do not contain inputs, than can be saved') } else { const paramRange = model.getStrategyRange(strategyData) console.log(paramRange) @@ -69,7 +70,6 @@ action.downloadStrategyTestResults = async () => { action.testStrategy = async (request, isDeepTest = false) => { - console.log('request', request) try { const strategyData = await action._getStrategyData() const [allRangeParams, paramRange, cycles] = await action._getRangeParams(strategyData) @@ -133,9 +133,9 @@ action.testStrategy = async (request, isDeepTest = false) => { action._getRangeParams = async (strategyData) => { let paramRange = await model.getStrategyParameters(strategyData) console.log('paramRange', paramRange) - if(!paramRange) - throw new Error('Error get changed strategy parameters') - // return + if(paramRange === null) + // throw new Error('Error get changed strategy parameters') + return [null, null, null] const initParams = {} initParams.paramRange = paramRange @@ -168,7 +168,7 @@ action._getStrategyData = async () => { ui.statusMessage('Get the initial parameters.') const strategyData = await tv.getStrategy() if(!strategyData || !strategyData.hasOwnProperty('name') || !strategyData.hasOwnProperty('properties') || !strategyData.properties) { - throw new Error('Could not find any strategy with parameters among the indicators. Add it to the chart and try again.') + throw new Error('The current strategy do not contain inputs, than can be optimized. You can choose another strategy to optimize.') } return strategyData } diff --git a/content_scripts/model.js b/content_scripts/model.js index 56104b3..c17b269 100644 --- a/content_scripts/model.js +++ b/content_scripts/model.js @@ -6,7 +6,11 @@ model.getStrategyParameters = async (strategyData) => { if(paramRange) { const mismatched = Object.keys(paramRange).filter(key => !Object.keys(strategyData.properties).includes(key)) if(mismatched && mismatched.length) { - const isDef = confirm(`The data loaded from the storage has parameters that are not present in the current strategy: ${mismatched.join(',')}.\n\nYou need to load the correct strategy in the Tradingview chart or load new parameters for the current one. \nAlternatively, you can use the default strategy optimization parameters.\n\nShould it use the default settings?`) + // const isDef = confirm(`The data loaded from the storage has parameters that are not present in the current strategy: ${mismatched.join(',')}.\n\nYou need to load the correct strategy in the Tradingview chart or load new parameters for the current one. \nAlternatively, you can use the default strategy optimization parameters.\n\nShould it use the default settings?`) + const isDef = await ui.alertPopup (`The data loaded from the storage has parameters that are not present in the + current strategy: ${mismatched.join(',')}.\n\nYou need to load the correct strategy in the Tradingview chart or + load new parameters for the current one. \nAlternatively, you can use the default strategy optimization parameters. + \n\nShould it use the default settings?`, false, true) if (!isDef) return null paramRange = model.getStrategyRange(strategyData) diff --git a/content_scripts/page.js b/content_scripts/page.js index 82acfe8..150dce6 100644 --- a/content_scripts/page.js +++ b/content_scripts/page.js @@ -2,7 +2,7 @@ const page = {} page.waitForTimeout = async (timeout = 2500) => new Promise(resolve => setTimeout(resolve, timeout)) -page.waitForSelector = async function (selector, timeout = 5000, isHide = false, parentEl) { +page.waitForSelectorOld2del = async function (selector, timeout = 5000, isHide = false, parentEl) { //2023-04-18 parentEl = parentEl ? parentEl : document return new Promise(async (resolve) => { let iter = 0 @@ -17,6 +17,25 @@ page.waitForSelector = async function (selector, timeout = 5000, isHide = false, }); } + +page.waitForSelector = async (selector, timeout = 5000, isHide = false, parentEl = null) => { + return new Promise(async (resolve) => { + parentEl = parentEl ? parentEl : document + let iter = 0 + let elem = parentEl.querySelector(selector) + const tikTime = timeout === 0 ? 1000 : 50 + while (timeout === 0 || (!isHide && !elem) || (isHide && !!elem)) { + await page.waitForTimeout(tikTime) + elem = parentEl.querySelector(selector) + iter += 1 + if(timeout !== 0 && tikTime * iter >= timeout) + break + // throw new Error(`Timeout ${timeout} waiting for ${isHide ? 'hide ' : '' } ${selector}`) // break + } + return resolve(elem ? elem : null) + }) +} + const reactValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; page._inputEvent = new Event('input', { bubbles: true}); page._changeEvent = new Event('change', { bubbles: true}); diff --git a/content_scripts/selector.js b/content_scripts/selector.js index 7ee9acd..98f3570 100644 --- a/content_scripts/selector.js +++ b/content_scripts/selector.js @@ -3,10 +3,10 @@ const SEL = { tvLegendIndicatorItem: 'div[data-name="legend"] div[class^="sourcesWrapper"] div[class^="sources"] div[data-name="legend-source-item"]', tvLegendIndicatorItemTitle: 'div[data-name="legend-source-title"]', tvDialogRoot: '#overlap-manager-root', - indicatorTitle: '#overlap-manager-root div[data-name="indicator-properties-dialog"] div[class^="title"]', - tabInput: 'div[data-name="indicator-properties-dialog"] div[data-value="inputs"]', - tabInputActive: 'div[data-name="indicator-properties-dialog"] div[class*="active"][data-value="inputs"]', - tabProperties: 'div[data-name="indicator-properties-dialog"] div[data-value="properties"]', + indicatorTitle: '#overlap-manager-root div[data-name="indicator-properties-dialog"] [class^="container"] div[class^="title"]', + tabInput: '#overlap-manager-root div[data-name="indicator-properties-dialog"] [class^="tab"] button#inputs', + tabInputActive: '#overlap-manager-root div[data-name="indicator-properties-dialog"] [class^="tab"] button#inputs[class*="selected"]', + tabProperties: '#overlap-manager-root div[data-name="indicator-properties-dialog"] [class^="tab"] button#properties', ticker: '#header-toolbar-symbol-search > div[class*="text-"]', timeFrame: '#header-toolbar-intervals div[data-role^="button"]', timeFrameActive: '#header-toolbar-intervals div[data-role^="button"][class*="isActive"]', @@ -14,8 +14,8 @@ const SEL = { indicatorProperty: 'div[data-name="indicator-properties-dialog"] div[class^="content-"] div[class^="cell-"]', okBtn: 'div[data-name="indicator-properties-dialog"] div[class^="footer-"] button[name="submit"]', cancelBtn: 'div[data-name="indicator-properties-dialog"] span[data-name="close"][data-role="button"]', - strategyTesterTab: '#footer-chart-panel div[data-name="backtesting"]', - strategyTesterTabActive: '#footer-chart-panel div[data-name="backtesting"][data-active="true"]', + strategyTesterTab: '[data-name="backtesting"]', // 2023-10-19 #footer-chart-panel or #bottom-area + strategyTesterTabActive: '[data-name="backtesting"][data-active="true"]', // 2023-10-19 #footer-chart-panel or #bottom-area strategyCaption: '#bottom-area div[class^="backtesting"] [class^="strategyGroup"] [data-strategy-title]', strategyDialogParam: '#bottom-area div[class^="backtesting"] [class^="strategyGroup"] > div:nth-child(2) > button:nth-child(1)', strategySummary: '#bottom-area div[class^="backtesting"] div[class^="tabSwitcher"] > button:nth-child(2)', diff --git a/content_scripts/tv.js b/content_scripts/tv.js index 457ffbe..bccb330 100644 --- a/content_scripts/tv.js +++ b/content_scripts/tv.js @@ -36,50 +36,37 @@ async function messageHandler(event) { } -tv.getStrategy = async (strategyName = null, isIndicatorSave = false) => { - let strategyData = {} +tv.getStrategy = async (strategyName = '', isIndicatorSave = false) => { let indicatorName = null if(strategyName !== null) { if (!strategyName) { - let isStrategyActiveEl = document.querySelector(SEL.strategyTesterTabActive) - if(!isStrategyActiveEl) { - const strategyTabEl = document.querySelector(SEL.strategyTesterTab) - if(strategyTabEl) { - strategyTabEl.click() - } else { - await ui.showErrorPopup('There is not strategy tester tab on the page. Open correct page please') - return null - } - } + await tv.openStrategyTab() let strategyCaptionEl = document.querySelector(SEL.strategyCaption) - // strategyCaptionEl = !strategyCaptionEl ? document.querySelector(SEL.strategyCaptionNew) : strategyCaptionEl // TODO 2del 22.05.31 if(!strategyCaptionEl || !strategyCaptionEl.innerText) { - await ui.showErrorPopup('There is not strategy name element on page. Open correct page please') - return null + throw new Error('There is not strategy name element on "Strategy tester" tab. Please retry. If the problem reproduced then it is possible taht TV UI changed. Check ' + + 'issues') } indicatorName = strategyCaptionEl.innerText let stratParamEl = document.querySelector(SEL.strategyDialogParam) if(!stratParamEl) { - await ui.showErrorPopup('There is not strategy param button on the strategy tab. Test stopped. Open correct page please') - return null + throw new Error('There is not strategy param button on the "Strategy tester" tab. Please retry. If the problem reproduced then it is possible taht TV UI changed. Check ' + + 'issues') } stratParamEl.click() const dialogTitle = await page.waitForSelector(SEL.indicatorTitle, 2500) if (!dialogTitle || !dialogTitle.innerText) { - await ui.showErrorPopup('There is open strategy properties. Open correct page please') if (document.querySelector(SEL.cancelBtn)) document.querySelector(SEL.cancelBtn).click() - return null + throw new Error('The strategy parameter windows is not opened. Please retry. If the problem reproduced then it is possible taht TV UI changed. Check ' + + 'issues') } let isStrategyPropertiesTab = document.querySelector(SEL.tabProperties) // For strategy only if (isIndicatorSave || isStrategyPropertiesTab) { indicatorName = dialogTitle.innerText - } - } - else { + } else { const indicatorLegendsEl = document.querySelectorAll(SEL.tvLegendIndicatorItem) if(!indicatorLegendsEl) return null @@ -105,7 +92,6 @@ tv.getStrategy = async (strategyName = null, isIndicatorSave = false) => { } } } - } else { let dialogTitleEl = await page.waitForSelector(SEL.indicatorTitle, 2500) if (!dialogTitleEl || !dialogTitleEl.innerText) { @@ -120,9 +106,29 @@ tv.getStrategy = async (strategyName = null, isIndicatorSave = false) => { } } if(indicatorName === null) - return strategyData - strategyData = {name: indicatorName, properties: {}} - if(await tv.changeDialogTabToInput()) { + throw new Error('It was not possible to find a strategy with parameters among the indicators. Add it to the chart and try again.') + // return strategyData + + if(!await tv.changeDialogTabToInput()) + throw new Error(`Can\'t activate input tab in strategy parameters`) + // if(await tv.changeDialogTabToInput()) { + + // } else { + // console.error(`Can't set parameters tab to input`) + // } + const strategyInputs = await tv.getStrategyParams(isIndicatorSave) + const strategyData = {name: indicatorName, properties: strategyInputs} + + if (document.querySelector(SEL.cancelBtn)) { + document.querySelector(SEL.cancelBtn).click() + await page.waitForSelector(SEL.cancelBtn, 1000, true) + } + + return strategyData +} + +tv.getStrategyParams = async (isIndicatorSave=false) => { + const strategyInputs = {} // TODO to list of values and set them in the same order const indicProperties = document.querySelectorAll(SEL.indicatorProperty) for (let i = 0; i < indicProperties.length; i++) { const propClassName = indicProperties[i].getAttribute('class') @@ -138,13 +144,13 @@ tv.getStrategy = async (strategyName = null, isIndicatorSave = false) => { let propValue = indicProperties[i].querySelector('input').value if(indicProperties[i].querySelector('input').getAttribute('inputmode') === 'numeric' || (parseFloat(propValue) == propValue || parseInt(propValue) == propValue)) { // not only inputmode==numbers input have digits - const digPropValue = parseFloat(propValue) == parseInt(propValue) ? parseInt(propValue) : parseFloat(propValue) // TODO how to get float from param or just search point in string + const digPropValue = parseFloat(propValue) == parseInt(propValue) ? parseInt(propValue) : parseFloat(propValue) // Detection if float or int in the string if(!isNaN(propValue)) - strategyData.properties[propText] = digPropValue + strategyInputs[propText] = digPropValue else - strategyData.properties[propText] = propValue + strategyInputs[propText] = propValue } else { - strategyData.properties[propText] = propValue + strategyInputs[propText] = propValue } } else if(indicProperties[i].querySelector('span[role="button"]')) { // List const buttonEl = indicProperties[i].querySelector('span[role="button"]') @@ -153,7 +159,7 @@ tv.getStrategy = async (strategyName = null, isIndicatorSave = false) => { const propValue = buttonEl.innerText if(propValue) { if(isIndicatorSave) { - strategyData.properties[propText] = propValue + strategyInputs[propText] = propValue continue } buttonEl.scrollIntoView() @@ -169,10 +175,10 @@ tv.getStrategy = async (strategyName = null, isIndicatorSave = false) => { } } if(allOptionsList) - strategyData.properties[propText] = allOptionsList + strategyInputs[propText] = allOptionsList page.mouseClick(buttonEl) } else { - strategyData.properties[propText] = propValue + strategyInputs[propText] = propValue } } } else { // Undefined @@ -181,7 +187,7 @@ tv.getStrategy = async (strategyName = null, isIndicatorSave = false) => { } else if (propClassName.includes('fill-')) { const element = indicProperties[i].querySelector('input[type="checkbox"]') if(element) - strategyData.properties[propText] = element.getAttribute('checked') !== null ? element.checked : false + strategyInputs[propText] = element.getAttribute('checked') !== null ? element.checked : false else { // Undefined type of element continue } @@ -191,15 +197,7 @@ tv.getStrategy = async (strategyName = null, isIndicatorSave = false) => { continue } } - } else { - console.error(`Can't set parameters tab to input`) - } - if (document.querySelector(SEL.cancelBtn)) { - document.querySelector(SEL.cancelBtn).click() - await page.waitForSelector(SEL.cancelBtn, 1000, true) - } - - return strategyData + return strategyInputs } tv.setStrategyParams = async (name, propVal, isCheckOpenedWindow = false) => { @@ -263,7 +261,7 @@ tv.changeDialogTabToInput = async () => { if(isInputTabActive) return true const inputTabEl = document.querySelector(SEL.tabInput) if (!inputTabEl) { - throw new Error('There are no parameters in this strategy that can be optimized (There is no "Inputs" tab with parameters)') + throw new Error('There are no parameters in this strategy that can be optimized (There is no "Inputs" tab with input values)') } inputTabEl.click() isInputTabActive = await page.waitForSelector(SEL.tabInputActive, 2000) @@ -342,7 +340,9 @@ tv.openStrategyTab = async () => { if(strategyTabEl) { strategyTabEl.click() } else { - throw new Error('There is not strategy tester tab on the page. Open correct page please') + throw new Error('There is not "Strategy Tester" tab on the page. Open correct page please or if it is ' + + 'correct, then it is possible that TV UI changed. Create ' + + 'issue') } } return true diff --git a/content_scripts/ui.js b/content_scripts/ui.js index 8b56c38..26f93d5 100644 --- a/content_scripts/ui.js +++ b/content_scripts/ui.js @@ -134,7 +134,7 @@ top:0px; left:0px; z-index:10000;` -async function alertPopup (msgText, isError = null, isConfirm = false) { +ui.alertPopup = async (msgText, isError = null, isConfirm = false) => { return new Promise(resolve => { function removeAlertPopup () { const iondvAlertPopupEl = document.getElementById('iondvAlertPopup') @@ -191,15 +191,15 @@ async function alertPopup (msgText, isError = null, isConfirm = false) { } ui.showPopup = async (msgText) => { - return await alertPopup(msgText, null) + return await ui.alertPopup(msgText, null) } ui.showErrorPopup = async (msgText) => { - return await alertPopup(msgText, true) + return await ui.alertPopup(msgText, true) } ui.showWarningPopup = async (msgText) => { - return await alertPopup(msgText, false) + return await ui.alertPopup(msgText, false) } ui.statusMessageRemove = () => { diff --git a/manifest.json b/manifest.json index 5789765..3259d43 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "description": "An assistant for backtesting trading strategies and showing external signals in Tradingview", "name": "Tradingview assistant", - "version": "2.3", + "version": "2.6", "icons": { "16": "images/tv_assist_16.png", "32": "images/tv_assist_32.png",