From 89b0c5a5e1cb8149ebd847b7f455e07321013ac9 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 12:25:04 +0800 Subject: [PATCH 01/60] Clean up unused constants --- src/botPage/common/const.js | 41 ++----------------------------------- 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/src/botPage/common/const.js b/src/botPage/common/const.js index d188ef7999..44b6871014 100644 --- a/src/botPage/common/const.js +++ b/src/botPage/common/const.js @@ -165,45 +165,8 @@ const config = { [translate('8 hours'), '28800'], [translate('1 day'), '86400'], ], - mainBlocks : ['trade', 'before_purchase', 'after_purchase', 'during_purchase'], - durationTypes: { - RISEFALL: [ - [translate('Ticks'), 't'], - [translate('Seconds'), 's'], - [translate('Minutes'), 'm'], - [translate('Hours'), 'h'], - ], - RISEFALLEQUALS: [ - [translate('Ticks'), 't'], - [translate('Seconds'), 's'], - [translate('Minutes'), 'm'], - [translate('Hours'), 'h'], - ], - HIGHERLOWER: [ - [translate('Ticks'), 't'], - [translate('Seconds'), 's'], - [translate('Minutes'), 'm'], - [translate('Hours'), 'h'], - ], - RESET: [ - [translate('Ticks'), 't'], - [translate('Seconds'), 's'], - [translate('Minutes'), 'm'], - [translate('Hours'), 'h'], - ], - TOUCHNOTOUCH : [[translate('Ticks'), 't'], [translate('Minutes'), 'm'], [translate('Hours'), 'h']], - ENDSINOUT : [[translate('Minutes'), 'm'], [translate('Hours'), 'h']], - STAYSINOUT : [[translate('Minutes'), 'm'], [translate('Hours'), 'h']], - ASIANS : [[translate('Ticks'), 't']], - MATCHESDIFFERS: [[translate('Ticks'), 't']], - EVENODD : [[translate('Ticks'), 't']], - OVERUNDER : [[translate('Ticks'), 't']], - HIGHLOWTICKS : [[translate('Ticks'), 't']], - }, - hasPrediction : ['MATCHESDIFFERS', 'OVERUNDER', 'HIGHLOWTICKS'], - hasBarrierOffset : ['HIGHERLOWER', 'TOUCHNOTOUCH', 'ENDSINOUT', 'STAYSINOUT'], - hasSecondBarrierOffset: ['ENDSINOUT', 'STAYSINOUT'], - conditionsCategory : { + mainBlocks : ['trade', 'before_purchase', 'after_purchase', 'during_purchase'], + conditionsCategory: { callput : ['risefall', 'higherlower'], callputequal: ['risefallequals'], touchnotouch: ['touchnotouch'], From 0bcbe2a07b49163760ff0f2da221597a6a84a902 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 12:26:20 +0800 Subject: [PATCH 02/60] Filter n/a (sub)markets & symbols, implement logic for getting barriers + prediction from API, refactoring --- src/botPage/view/blockly/blocks/shared.js | 302 +++++++++++++++------- 1 file changed, 213 insertions(+), 89 deletions(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index 15c2e9ab92..abb397447b 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -9,6 +9,7 @@ import { getTokenList, removeAllTokens, } from '../../../../common/utils/storageManager'; +import { observer as globalObserver } from '../../../../common/utils/observer'; let purchaseChoices = [[translate('Click to select'), '']]; @@ -95,7 +96,12 @@ fieldGeneratorMapping.SUBMARKET_LIST = block => () => { return [['', 'Invalid']]; } const submarkets = getActiveSubMarket(markets[marketName].submarkets); - return Object.keys(submarkets).map(e => [submarkets[e].name, e]); + return ( + Object.keys(submarkets) + .map(e => [submarkets[e].name, e]) + // Filter out markets we don't have contracts for + .filter(submarket => !['energy'].includes(submarket[1])) + ); }; fieldGeneratorMapping.SYMBOL_LIST = block => () => { @@ -107,7 +113,12 @@ fieldGeneratorMapping.SYMBOL_LIST = block => () => { const marketName = block.getFieldValue('MARKET_LIST'); const submarkets = getActiveSubMarket(markets[marketName].submarkets); const symbols = getActiveSymbols(submarkets[submarketName].symbols); - return Object.keys(symbols).map(e => [symbols[e].display, symbols[e].symbol]); + return ( + Object.keys(symbols) + .map(e => [symbols[e].display, symbols[e].symbol]) + // Filter out symbols we don't have contracts for (these symbols have only forward-starting) + .filter(symbol => !['frxGBPNOK', 'frxUSDNOK', 'frxUSDNEK'].includes(symbol[1])) + ); }; fieldGeneratorMapping.TRADETYPECAT_LIST = block => () => { @@ -126,10 +137,14 @@ fieldGeneratorMapping.TRADETYPE_LIST = block => () => { if (!tradeTypeCat) { return [['', '']]; } - return config.conditionsCategory[tradeTypeCat].map(e => [ - config.opposites[e.toUpperCase()].map(c => c[Object.keys(c)[0]]).join('/'), - e, - ]); + return ( + config.conditionsCategory[tradeTypeCat] + .map(e => [config.opposites[e.toUpperCase()].map(c => c[Object.keys(c)[0]]).join('/'), e]) + // Filter out trade types we don't offer + .filter( + tradeType => !(block.getFieldValue('SUBMARKET_LIST') === 'smart_fx' && tradeType[1] === 'higherlower') + ) + ); }; export const dependentFieldMapping = { @@ -139,9 +154,35 @@ export const dependentFieldMapping = { TRADETYPECAT_LIST: 'TRADETYPE_LIST', }; -export const getAvailableDurations = (symbol, selectedContractType) => { - const contractsForStore = JSON.parse(getStorage('contractsForStore') || '[]'); - let tokenList = getTokenList(); +const contractsForStore = JSON.parse(getStorage('contractsForStore') || '[]'); + +const getContractCategory = input => + Object.keys(config.conditionsCategory).find(c => c === input || config.conditionsCategory[c].includes(input)); + +const matchesBarrierCategory = (contract, contractCategory) => { + const conditions = []; + Object.keys(config.barrierCategories).some(barrierCategory => { + if (config.barrierCategories[barrierCategory].includes(contractCategory)) { + conditions.push(contract.barrier_category === barrierCategory); + } + return conditions.length; + }); + // If `barrierCategory` for `contractCategory` not found fallback to all contracts + return !conditions.includes(false); +}; + +const filterContractsByCategory = (contracts, contractCategory, contractType) => { + if (!contracts) return []; + return contracts.filter(contract => { + // We don't offer forward-starting contracts in Binary Bot, remove these + if (contract.start_type === 'forward') { + return false; + } + return contract.contract_category === contractCategory && matchesBarrierCategory(contract, contractType); + }); +}; + +export const getDurationsForContracts = (contractsAvailable, selectedContractType) => { const defaultDurations = [ [translate('Ticks'), 't'], [translate('Seconds'), 's'], @@ -149,23 +190,90 @@ export const getAvailableDurations = (symbol, selectedContractType) => { [translate('Hours'), 'h'], [translate('Days'), 'd'], ]; + const noDurationsAvailable = [{ label: translate('Not available'), unit: 'na', minimum: 0 }]; + if (!contractsAvailable) return noDurationsAvailable; + + const getMinimumAmount = input => input.replace(/\D/g, ''); + const getDurationIndex = input => defaultDurations.findIndex(d => d[1] === input.replace(/\d+/g, '')); - const getContractsForSymbolFromApi = async underlyingSymbol => { - // Refactor this when reducing WS connections - const api = generateLiveApiInstance(); - - // Try to authorize for accurate contracts response - if (tokenList.length) { - try { - await api.authorize(tokenList[0].token); - } catch (e) { - removeAllTokens(); - tokenList = []; + const offeredDurations = []; + + const contractsForContractCategory = filterContractsByCategory( + contractsAvailable, + getContractCategory(selectedContractType), + selectedContractType + ); + contractsForContractCategory.forEach(c => { + if (!c.min_contract_duration || !c.max_contract_duration) return; + + const startIndex = getDurationIndex(c.min_contract_duration); + const endIndex = getDurationIndex(c.max_contract_duration === '1d' ? '24h' : c.max_contract_duration); + + defaultDurations.slice(startIndex, endIndex + 1).forEach((duration, index) => { + if (!offeredDurations.find(offeredDuration => offeredDuration.unit === duration[1])) { + offeredDurations.push({ + label : duration[0], + unit : duration[1], + minimum: index === 0 ? getMinimumAmount(c.min_contract_duration) : 1, + }); } + }); + }); + // If only intraday contracts available remove day-durations + if (contractsForContractCategory.every(c => c.expiry_type === 'intraday')) { + const dayDurationIndex = offeredDurations.findIndex(d => d[1] === 'd'); + if (dayDurationIndex !== -1) { + offeredDurations.splice(dayDurationIndex, 1); } + } + if (!offeredDurations.length) { + return noDurationsAvailable; + } + return ( + offeredDurations + // Maintain order based on duration unit + .sort((a, b) => getDurationIndex(a.unit) - getDurationIndex(b.unit)) + ); +}; + +export const haveContractsForSymbol = underlyingSymbol => contractsForStore.find(c => c.symbol === underlyingSymbol); +export const getContractsAvailableForSymbol = async underlyingSymbol => { + const contractsForSymbol = haveContractsForSymbol(underlyingSymbol); + if (!contractsForSymbol) { + const contractsAvailableForSymbol = await getContractsAvailableForSymbolFromApi(underlyingSymbol); + return Promise.resolve(contractsAvailableForSymbol.available); + } + // Different accounts have access to different markets, request and serve new data + const tokenList = getTokenList(); + const isDifferentAccount = () => tokenList.length && contractsForSymbol.accountName !== tokenList[0].accountName; + if (isDifferentAccount()) { + const contractsAvailableForSymbol = await getContractsAvailableForSymbolFromApi(underlyingSymbol); + return Promise.resolve(contractsAvailableForSymbol.available); + } + // Data expired, serve cached data, but retrieve updated data async + const isExpiredData = () => Math.floor((Date.now() - contractsForSymbol.timestamp) / 1000) > 600; + if (isExpiredData()) { + getContractsAvailableForSymbolFromApi(underlyingSymbol); + } + // Not a different account or expired, return what we have. + return Promise.resolve(contractsForSymbol.available); +}; + +export const getContractsAvailableForSymbolFromApi = async underlyingSymbol => { + const api = generateLiveApiInstance(); + let tokenList = getTokenList(); + if (tokenList.length) { + try { + await api.authorize(tokenList[0].token); + } catch (e) { + removeAllTokens(); + tokenList = []; + } + } + const contractsForSymbol = {}; + try { const response = await api.getContractsForSymbol(underlyingSymbol); - const contractsForSymbol = {}; if (response.contracts_for) { Object.assign(contractsForSymbol, { symbol : underlyingSymbol, @@ -186,81 +294,97 @@ export const getAvailableDurations = (symbol, selectedContractType) => { contractsForStore.push(contractsForSymbol); setStorage('contractsForStore', JSON.stringify(contractsForStore)); } + } catch (e) { + if (window.trackJs) { + trackJs.addMetadata('getContractsAvailableForSymbolFromApi Error', e.message); + } + globalObserver.emit('ui.log.error', translate('Could not retrieve contracts')); + } + if (typeof api.disconnect === 'function') { api.disconnect(); - return contractsForSymbol; - }; - const getDurationsForContract = contractsForSymbol => { - if (!contractsForSymbol) return defaultDurations; - - // Resolve contract_category (e.g. risefall = callput) - const contractCategory = Object.keys(config.conditionsCategory).find( - c => c === selectedContractType || config.conditionsCategory[c].includes(selectedContractType) - ); - - // Get contracts based on `contract_category` and `barrier_category` - const contractsForContractCategory = contractsForSymbol.filter(c => { - const meetsBarrierConditions = () => { - const conditions = []; - Object.keys(config.barrierCategories).some(barrierCategory => { - if (config.barrierCategories[barrierCategory].includes(selectedContractType)) { - conditions.push(c.barrier_category === barrierCategory); - } - return conditions.length; - }); - // If `barrierCategory` for `selectedContractType` not found fallback to all contracts for durations - return !conditions.includes(false); - }; - // We don't offer forward-starting contracts in Binary Bot, remove these - if (c.start_type === 'forward') { - return false; - } - return c.contract_category === contractCategory && meetsBarrierConditions(); - }); + } + return contractsForSymbol; +}; - const getDurationIndex = input => defaultDurations.findIndex(d => d[1] === input.replace(/\d+/g, '')); +export const getBarriersForContracts = (contracts, selectedContractType, selectedDuration) => { + const category = getContractCategory(selectedContractType); + const contractsForContractCategory = filterContractsByCategory(contracts, category, selectedContractType); + const barriers = {}; + if (contractsForContractCategory) { + let contract; + if (selectedDuration) { + // Find barriers based on selected duration (e.g. Hours & days can have different barrier values) + contract = contractsForContractCategory.find(c => { + const durations = getDurationsForContracts([c], selectedContractType); + return durations.map(duration => duration.unit).includes(selectedDuration); + }); + } else { + // Default to smallest barriers available + contract = contractsForContractCategory + .sort((a, b) => { + const barrierValueA = a.barrier || a.high_barrier; + const barrierValueB = b.barrier || b.high_barrier; + return parseFloat(barrierValueA) - parseFloat(barrierValueB); + }) + .shift(); + } + if (contract) { + const offsetRegex = new RegExp('^[-|+]([0-9]+.[0-9]+)$'); + if (contract.barriers === 2 && contract.high_barrier && contract.low_barrier) { + const highBarrierOffsetMatch = contract.high_barrier.toString().match(offsetRegex); + const lowBarrierOffsetMatch = contract.low_barrier.toString().match(offsetRegex); - // Generate list of available durations from filtered contracts - const offeredDurations = []; - contractsForContractCategory.forEach(c => { - const startIndex = getDurationIndex(c.min_contract_duration); - const endIndex = getDurationIndex(c.max_contract_duration === '1d' ? '24h' : c.max_contract_duration); - defaultDurations.slice(startIndex, endIndex + 1).forEach(duration => { - if (!offeredDurations.includes(duration)) { - offeredDurations.push(duration); + if (highBarrierOffsetMatch && lowBarrierOffsetMatch) { + barriers.high_barrier = highBarrierOffsetMatch[1]; // eslint-disable-line prefer-destructuring + barriers.low_barrier = lowBarrierOffsetMatch[1]; // eslint-disable-line prefer-destructuring + } else { + const countDecimals = number => { + if (Math.floor(number) === number) return 0; + return number.toString().split('.')[1].length || 0; + }; + const highBarrier = parseFloat(contract.high_barrier); + const lowBarrier = parseFloat(contract.low_barrier); + const barrier = ((highBarrier - lowBarrier) / 2).toFixed(countDecimals(contract.high_barrier)); + barriers.high_barrier = barrier; + barriers.low_barrier = barrier; + } + } else if (contract.barriers === 1 && contract.barrier) { + const barrierOffsetMatch = contract.barrier.toString().match(offsetRegex); + if (barrierOffsetMatch) { + barriers.barrier = barrierOffsetMatch[1]; // eslint-disable-line prefer-destructuring + } else { + // Return default value if we only get a barrier based on price (e.g. 234.1231) + // as we can't calculate the offset since we don't stream price like SmartTrader + barriers.barrier = 0.1; } - }); - }); - // If only intraday contracts are available, remove day-durations - if (contractsForContractCategory.every(c => c.expiry_type === 'intraday')) { - const dayDurationIndex = offeredDurations.findIndex(d => d[1] === 'd'); - if (dayDurationIndex !== -1) { - offeredDurations.splice(dayDurationIndex, 1); } } - offeredDurations.sort((a, b) => getDurationIndex(a[1]) - getDurationIndex(b[1])); - return offeredDurations; - }; - - const getFreshContractsFor = () => - new Promise(resolve => { - getContractsForSymbolFromApi(symbol).then(contractsForSymbolFromApi => { - resolve(getDurationsForContract(contractsForSymbolFromApi.available)); - }); - }); + } + return barriers; +}; - // Check if we have local data to get durations from - const contractsForSymbol = contractsForStore.find(c => c.symbol === symbol); - if (contractsForSymbol) { - const isDifferentAccount = () => - tokenList.length && contractsForSymbol.accountName !== tokenList[0].accountName; - const isExpiredData = () => Math.floor((Date.now() - contractsForSymbol.timestamp) / 1000) > 600; - if (isDifferentAccount()) { - return getFreshContractsFor(); - } else if (isExpiredData()) { - // Return cached data, update cached data in background - getContractsForSymbolFromApi(symbol); +export const getPredictionForContracts = (contracts, selectedContractType) => { + const category = getContractCategory(selectedContractType); + const contractsForContractCategory = filterContractsByCategory(contracts, category, selectedContractType); + + const contractMapping = {}; + if (category === 'digits') { + contractMapping.matchesdiffers = ['DIGITMATCH', 'DIGITDIFF']; + contractMapping.overunder = ['DIGITOVER', 'DIGITUNDER']; + } else if (category === 'highlowticks') { + contractMapping.highlowticks = ['TICKHIGH', 'TICKLOW']; + } + + const predictionRange = []; + if (contractMapping[selectedContractType]) { + const contract = contractsForContractCategory.find(c => + contractMapping[selectedContractType].includes(c.contract_type) + ); + if (contract.last_digit_range) { + predictionRange.push(...contract.last_digit_range); + } else { + predictionRange.push(0); } - return Promise.resolve(getDurationsForContract(contractsForSymbol.available)); } - return getFreshContractsFor(); + return predictionRange; }; From 5c0fc9424c9d25f2c7e4078de7af346316ce5e08 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 12:26:49 +0800 Subject: [PATCH 03/60] Always initialise barrier offset + prediction fields --- .../blocks/trade/backwardCompatibility.js | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js b/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js index 89df0052de..db7dd350ab 100644 --- a/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js +++ b/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js @@ -63,22 +63,9 @@ export default () => { }); duration(this); payout(this); - if (config.hasPrediction.indexOf(oppositesName) > -1) { - prediction(this); - } else { - this.removeInput('PREDICTION'); - } - if (config.hasBarrierOffset.indexOf(oppositesName) > -1) { - barrierOffset(this); - } else { - this.removeInput('BARRIEROFFSET'); - } - if (config.hasSecondBarrierOffset.indexOf(oppositesName) > -1) { - barrierOffset(this); - secondBarrierOffset(this); - } else { - this.removeInput('SECONDBARRIEROFFSET'); - } + prediction(this); + barrierOffset(this); + secondBarrierOffset(this); this.setInputsInline(false); this.setPreviousStatement(true, 'Condition'); }, From 9eb50e121b45e4af49ed5be3b03d1f91a2cabc7d Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 12:28:04 +0800 Subject: [PATCH 04/60] Create common function for generating barriers, hide fields by default --- .../view/blockly/blocks/trade/components.js | 71 +++++++------------ 1 file changed, 27 insertions(+), 44 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/components.js b/src/botPage/view/blockly/blocks/trade/components.js index ad5a139c21..a843b2a317 100644 --- a/src/botPage/view/blockly/blocks/trade/components.js +++ b/src/botPage/view/blockly/blocks/trade/components.js @@ -69,62 +69,45 @@ export const payout = block => { } }; -export const barrierOffset = block => { - if (!block.getInput('BARRIEROFFSET')) { +const barrierOffsetGenerator = (inputName, block) => { + if (!block.getInput(inputName)) { + // Determine amount of barrier-blocks on workspace + const labelPrefix = translate('Barrier Offset'); + const barrierNumber = block.inputList.filter(input => /BARRIEROFFSET$/.test(input.name)).length; + + // Set barrier options according to barrierNumber (i.e. + and -) + const barrierOffsetList = new Blockly.FieldDropdown(config.barrierTypes); + barrierOffsetList.setValue(config.barrierTypes[barrierNumber % config.barrierTypes.length][1]); + block - .appendValueInput('BARRIEROFFSET') + .appendValueInput(inputName) .setCheck('Number') - .appendField(`${translate('Barrier Offset')} 1:`) - .appendField(new Blockly.FieldDropdown(config.barrierTypes), 'BARRIEROFFSETTYPE_LIST'); - } else { - const barrierOffsetList = block.getField('BARRIEROFFSETTYPE_LIST'); + .appendField(`${labelPrefix} ${barrierNumber + 1}:`) + .appendField(barrierOffsetList, `${inputName}TYPE_LIST`); - if ( - !block.workspace.getBlockById('BARRIERVALUE') && - !block.getInput('BARRIEROFFSET').connection.isConnected() - ) { - const barrierValue = block.workspace.newBlock('math_number', 'BARRIERVALUE'); - barrierOffsetList.setValue('+'); - barrierValue.setFieldValue('0.27', 'NUM'); - barrierValue.setShadow(true); - barrierValue.outputConnection.connect(block.getInput('BARRIEROFFSET').connection); - barrierValue.initSvg(); - barrierValue.render(); - } + const input = block.getInput(inputName); + input.setVisible(false); } }; -export const secondBarrierOffset = block => { - if (!block.getInput('SECONDBARRIEROFFSET')) { - block - .appendValueInput('SECONDBARRIEROFFSET') - .setCheck('Number') - .appendField(`${translate('Barrier Offset')} 2:`) - .appendField(new Blockly.FieldDropdown(config.barrierTypes), 'SECONDBARRIEROFFSETTYPE_LIST'); - } else { - const barrierOffsetList = block.getField('SECONDBARRIEROFFSETTYPE_LIST'); +export const barrierOffset = block => { + barrierOffsetGenerator('BARRIEROFFSET', block); +}; - if ( - !block.workspace.getBlockById('SECONDBARRIERVALUE') && - !block.getInput('SECONDBARRIEROFFSET').connection.isConnected() - ) { - const secondBarrierValue = block.workspace.newBlock('math_number', 'SECONDBARRIERVALUE'); - barrierOffsetList.setValue('-'); - secondBarrierValue.setFieldValue('0.27', 'NUM'); - secondBarrierValue.setShadow(true); - secondBarrierValue.outputConnection.connect(block.getInput('SECONDBARRIEROFFSET').connection); - secondBarrierValue.initSvg(); - secondBarrierValue.render(); - } - } +export const secondBarrierOffset = block => { + barrierOffsetGenerator('SECONDBARRIEROFFSET', block); }; export const prediction = block => { - if (!block.getInput('PREDICTION')) { + const inputName = 'PREDICTION'; + if (!block.getInput(inputName)) { block - .appendValueInput('PREDICTION') + .appendValueInput(inputName) .setCheck('Number') - .appendField(translate('Prediction:')); + .appendField(`${translate('Prediction')}:`); + + const input = block.getInput(inputName); + input.setVisible(false); } }; From 885b17171030183ff5d2524109b615bd73e2e333 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 12:28:39 +0800 Subject: [PATCH 05/60] Combine getTradeType & getSelectedSymbol into common function --- src/botPage/view/blockly/blocks/trade/tools.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tools.js b/src/botPage/view/blockly/blocks/trade/tools.js index 16b20e6258..ad62dbdf0b 100644 --- a/src/botPage/view/blockly/blocks/trade/tools.js +++ b/src/botPage/view/blockly/blocks/trade/tools.js @@ -1,20 +1,14 @@ import { marketDropdown, tradeTypeDropdown, candleInterval, contractTypes, restart } from './components'; import { findTopParentBlock } from '../../utils'; - import { observer as globalObserver } from '../../../../../common/utils/observer'; -export const getTradeType = block => { - const tradeDefBlock = findTopParentBlock(block); - return tradeDefBlock && tradeDefBlock.getFieldValue('TRADETYPE_LIST'); -}; - -export const getSelectedSymbol = block => { - const tradeDefBlock = findTopParentBlock(block); - return tradeDefBlock && tradeDefBlock.getFieldValue('SYMBOL_LIST'); +export const getParentValue = (block, fieldName) => { + const parentBlock = findTopParentBlock(block); + return parentBlock && parentBlock.getFieldValue(fieldName); }; export const updateInputList = block => { - const tradeType = getTradeType(block); + const tradeType = getParentValue(block, 'TRADETYPE_LIST'); if (tradeType) { Blockly.Blocks[tradeType].init.call(block); } From b8c38893fd2f12b3b6cf61b06273d7c5d15b6930 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 12:29:42 +0800 Subject: [PATCH 06/60] Move custom functions to customBlockly, add attachShadowBlock function --- src/botPage/view/blockly/customBlockly.js | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/botPage/view/blockly/customBlockly.js b/src/botPage/view/blockly/customBlockly.js index 11712f5ea2..4e9319a389 100644 --- a/src/botPage/view/blockly/customBlockly.js +++ b/src/botPage/view/blockly/customBlockly.js @@ -1,3 +1,5 @@ +import { translate } from '../../../common/utils/tools'; + /* eslint-disable */ Blockly.WorkspaceAudio.prototype.preload = function() {}; Blockly.FieldDropdown.prototype.render_ = function() { @@ -288,3 +290,47 @@ Blockly.FieldTextInput.prototype.showInlineEditor_ = function(quietInput) { this.bindEvents_(htmlInput); }; +const originalContextMenuFn = Blockly.ContextMenu.show; +Blockly.ContextMenu.show = (e, menuOptions, rtl) => { + // Rename 'Clean up blocks' + menuOptions.some(option => { + if (option.text === Blockly.Msg.CLEAN_UP) { + option.text = translate('Rearrange vertically'); // eslint-disable-line no-param-reassign + return true; + } + return false; + }) && + /* Remove delete all blocks, but only when 'Clean up blocks' is available (i.e. workspace) + * This allows users to still delete root blocks containing blocks + */ + menuOptions.some((option, i) => { + if ( + option.text === Blockly.Msg.DELETE_BLOCK || + option.text.replace(/[0-9]+/, '%1') === Blockly.Msg.DELETE_X_BLOCKS + ) { + menuOptions.splice(i, 1); + return true; + } + return false; + }); + // Open the Elev.io widget when clicking 'Help' + // eslint-disable-next-line no-underscore-dangle + if (window._elev) { + menuOptions.some(option => { + if (option.text === Blockly.Msg.HELP) { + option.callback = () => window._elev.open(); // eslint-disable-line no-param-reassign, no-underscore-dangle + return true; + } + return false; + }); + } + originalContextMenuFn(e, menuOptions, rtl); +}; +Blockly.Input.prototype.attachShadowBlock = function(value, name, shadowBlockType) { + const shadowBlock = this.sourceBlock_.workspace.newBlock(shadowBlockType); + shadowBlock.setShadow(true); + shadowBlock.setFieldValue(value, name); // Refactor when using shadow block for strings in future + shadowBlock.outputConnection.connect(this.connection); + shadowBlock.initSvg(); + shadowBlock.render(); +}; From 1dbe490b5018853cdcdd69512a11b64584b379cf Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 12:30:33 +0800 Subject: [PATCH 07/60] Remove custom blockly hooks, move them to customBlockly --- src/botPage/view/blockly/index.js | 40 ------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/botPage/view/blockly/index.js b/src/botPage/view/blockly/index.js index 1ea3de507a..cd350b91b8 100644 --- a/src/botPage/view/blockly/index.js +++ b/src/botPage/view/blockly/index.js @@ -420,43 +420,3 @@ while(true) { } /* eslint-enable */ } - -// Hooks to override default Blockly behaviour -/* eslint-disable no-unused-expressions */ -const originalContextMenuFn = Blockly.ContextMenu.show; -Blockly.ContextMenu.show = (e, menuOptions, rtl) => { - // Rename 'Clean up blocks' - menuOptions.some(option => { - if (option.text === Blockly.Msg.CLEAN_UP) { - option.text = translate('Rearrange vertically'); // eslint-disable-line no-param-reassign - return true; - } - return false; - }) && - /* Remove delete all blocks, but only when 'Clean up blocks' is available (i.e. workspace) - * This allows users to still delete root blocks containing blocks - */ - menuOptions.some((option, i) => { - if ( - option.text === Blockly.Msg.DELETE_BLOCK || - option.text.replace(/[0-9]+/, '%1') === Blockly.Msg.DELETE_X_BLOCKS - ) { - menuOptions.splice(i, 1); - return true; - } - return false; - }); - // Open the Elev.io widget when clicking 'Help' - // eslint-disable-next-line no-underscore-dangle - if (window._elev) { - menuOptions.some(option => { - if (option.text === Blockly.Msg.HELP) { - option.callback = () => window._elev.openHome(); // eslint-disable-line no-param-reassign, no-underscore-dangle - return true; - } - return false; - }); - } - originalContextMenuFn(e, menuOptions, rtl); -}; -/* eslint-enable */ From eb875ad8dcf3f740b19c59e6282427eec0ab8b81 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 12:31:00 +0800 Subject: [PATCH 08/60] Create hideInteractionsFromBlockly function --- src/botPage/view/blockly/utils.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/botPage/view/blockly/utils.js b/src/botPage/view/blockly/utils.js index ece0b55396..237b7af0c9 100644 --- a/src/botPage/view/blockly/utils.js +++ b/src/botPage/view/blockly/utils.js @@ -489,3 +489,9 @@ export const loadRemote = blockObj => } } }); + +export const hideInteractionsFromBlockly = callback => { + Blockly.Events.recordUndo = false; + callback(); + Blockly.Events.recordUndo = true; +}; From 4285efb66a58666504f546c4591ee2665f50390a Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 12:31:48 +0800 Subject: [PATCH 09/60] Refactor tradeOptions, show barrier offset, prediction, durations based on contracts_for response --- .../view/blockly/blocks/trade/tradeOptions.js | 277 +++++++++++++----- 1 file changed, 203 insertions(+), 74 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index 965d5d8fa4..eaac50f40a 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -1,9 +1,16 @@ -import { setInputList, updateInputList, getTradeType, getSelectedSymbol } from './tools'; -import { expectValue, getAvailableDurations } from '../shared'; +import { setInputList, updateInputList, getParentValue } from './tools'; +import { + expectValue, + haveContractsForSymbol, + getContractsAvailableForSymbol, + getDurationsForContracts, + getBarriersForContracts, + getPredictionForContracts, +} from '../shared'; import { insideTrade } from '../../relationChecker'; -import { findTopParentBlock } from '../../utils'; -import config from '../../../../common/const'; +import { findTopParentBlock, hideInteractionsFromBlockly, getBlocksByType } from '../../utils'; import { translate } from '../../../../../common/i18n'; +import { observer as globalObserver } from '../../../../../common/utils/observer'; export default () => { Blockly.Blocks.tradeOptions = { @@ -17,94 +24,216 @@ export default () => { if (ev.group === 'BackwardCompatibility') { return; } - if (ev.type === Blockly.Events.CREATE || ev.type === Blockly.Events.MOVE) { - Blockly.Events.fire( - new Blockly.Events.Change(this, 'field', 'SYMBOL_LIST', '', this.getFieldValue('SYMBOL_LIST')) - ); - } - if ([Blockly.Events.CREATE, Blockly.Events.CHANGE].includes(ev.type)) { - updateInputList(this); - } - if (ev.name === 'TRADETYPE_LIST') { + if (ev.type === Blockly.Events.CREATE) { + // After creation emit change event so API-dependent fields become populated updateInputList(this); - } - if (ev.name === 'SYMBOL_LIST' || ev.name === 'TRADETYPE_LIST') { - // eslint-disable-next-line no-underscore-dangle - if (ev.oldValue !== ev.newValue && this.parentBlock_ !== null) { - const durationTypeList = this.getField('DURATIONTYPE_LIST'); - if (durationTypeList === null) return; - - const symbol = getSelectedSymbol(this); - const tradeType = getTradeType(this); + const block = Blockly.mainWorkspace.getBlockById(ev.blockId); + if (ev.workspaceId === Blockly.mainWorkspace.id && block.type === 'trade') { + ['SYMBOL_LIST', 'TRADETYPE_LIST', 'DURATIONTYPE_LIST'].forEach(field => { + const changeEvent = new Blockly.Events.Change( + this, + 'field', + field, + '', + this.getFieldValue(field) + ); + Blockly.Events.fire(changeEvent); + }); + } + } else if (ev.type === Blockly.Events.MOVE) { + // Make sure tradeOptions-blocks are consistent with symbol, tradeType when re-added to root-block + if (!ev.oldParentId && ev.newParentId) { + const movedBlock = Blockly.mainWorkspace.getBlockById(ev.blockId); + const topParentBlock = findTopParentBlock(movedBlock); - const prevSelectedDuration = durationTypeList.getValue(); + if (topParentBlock && topParentBlock.type === 'trade') { + const symbol = topParentBlock.getFieldValue('SYMBOL_LIST'); + if (!symbol) return; - Blockly.Events.recordUndo = false; - this.setFieldValue(translate('Loading...'), 'DURATIONTYPE_LIST'); - Blockly.Events.recordUndo = true; + const getNestedTradeOptions = block => { + if (block.type === 'tradeOptions') { + this.pollForContracts(symbol).then(contracts => { + this.updateBarrierOffsetBlocks(contracts, [block.id]); + this.updatePredictionBlocks(contracts, [block.id]); + this.updateDurationLists(contracts, [block.id]); + }); + } else { + block.getChildren().forEach(childBlock => { + getNestedTradeOptions(childBlock); + }); + } + }; + getNestedTradeOptions(movedBlock); + } + } + } else if (ev.type === Blockly.Events.CHANGE) { + // eslint-disable-next-line no-underscore-dangle + if (this.parentBlock_ !== null) { + const symbol = getParentValue(this, 'SYMBOL_LIST'); + if (!symbol) return; - getAvailableDurations(symbol, tradeType).then(durations => { - Blockly.Events.recordUndo = false; - // Prevent UI flickering by only updating field if options have changed - // eslint-disable-next-line no-underscore-dangle - if (JSON.stringify(durationTypeList.menuGenerator_) !== JSON.stringify(durations)) { - durationTypeList.menuGenerator_ = durations; // eslint-disable-line no-underscore-dangle + this.pollForContracts(symbol).then(contracts => { + if (ev.name === 'SYMBOL_LIST') { + // Called to update duration options and min durations for symbol + this.updateDurationLists(contracts); + } else if (ev.name === 'TRADETYPE_LIST') { + // Both are called to check if these blocks are required + this.updatePredictionBlocks(contracts); + this.updateBarrierOffsetBlocks(contracts); } - // Maintain previously selected duration if possible (req for imported strategies) - const selectedValue = durationTypeList.menuGenerator_.find(d => d[1] === prevSelectedDuration); // eslint-disable-line no-underscore-dangle - if (selectedValue) { - this.setFieldValue(selectedValue[1], 'DURATIONTYPE_LIST'); - // eslint-disable-next-line no-underscore-dangle - } else if (durationTypeList.menuGenerator_.length) { - this.setFieldValue(durationTypeList.menuGenerator_[0][1], 'DURATIONTYPE_LIST'); // eslint-disable-line no-underscore-dangle - } else { - this.setFieldValue(translate('Not available'), 'DURATIONTYPE_LIST'); - } - Blockly.Events.recordUndo = true; + updateInputList(this); }); } } }, + pollForContracts(symbol) { + return new Promise(resolve => { + const contractsForSymbol = haveContractsForSymbol(symbol); + if (!haveContractsForSymbol(symbol)) { + // Register an event and use as a lock to avoid spamming API + const event = `contractsLoaded.${symbol}`; + if (!globalObserver.isRegistered(event)) { + globalObserver.register(event, () => {}); + getContractsAvailableForSymbol(symbol).then(contracts => { + globalObserver.unregisterAll(event); // Release the lock + resolve(contracts); + }); + } else { + // Request in progress, start polling localStorage until contracts are available. + const pollingFn = setInterval(() => { + const contracts = haveContractsForSymbol(symbol); + if (contracts) { + clearInterval(pollingFn); + resolve(contracts.available); + } + }, 100); + setTimeout(() => clearInterval(pollingFn), 10000); + } + } else { + resolve(contractsForSymbol.available); + } + }); + }, + updatePredictionBlocks(contracts, updateOnly = []) { + getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => { + if (tradeOptionsBlock.disabled) return; + if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return; + + const predictionInput = tradeOptionsBlock.getInput('PREDICTION'); + if (!predictionInput) return; + + const tradeType = getParentValue(tradeOptionsBlock, 'TRADETYPE_LIST'); + const predictionRange = getPredictionForContracts(contracts, tradeType); + + hideInteractionsFromBlockly(() => { + if (!predictionRange.length) { + tradeOptionsBlock.removeInput('PREDICTION'); + return; + } + predictionInput.setVisible(true); + + // Attach shadow block with API-returned prediction-value (only if user hasn't defined a value) + if (!predictionInput.connection.isConnected()) { + predictionInput.attachShadowBlock(predictionRange[0], 'NUM', 'math_number'); + } + }); + }); + }, + updateBarrierOffsetBlocks(contracts, updateOnly = []) { + getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => { + if (tradeOptionsBlock.disabled) return; + if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return; + + const tradeType = getParentValue(tradeOptionsBlock, 'TRADETYPE_LIST'); + const selectedDuration = tradeOptionsBlock.getFieldValue('DURATIONTYPE_LIST'); + const barriers = getBarriersForContracts(contracts, tradeType, selectedDuration); + + hideInteractionsFromBlockly(() => { + const barrierBlockNames = ['BARRIEROFFSET', 'SECONDBARRIEROFFSET']; + if (!Object.keys(barriers).length) { + barrierBlockNames.forEach(barrierInputName => tradeOptionsBlock.removeInput(barrierInputName)); + return; + } + const barrierKeys = Object.keys(barriers); + barrierKeys.forEach((barrier, index) => { + const barrierBlock = tradeOptionsBlock.getInput(barrierBlockNames[index]); + if (barrierBlock) { + barrierBlock.setVisible(true); + + // Attach shadow block with API-returned barrier-value (only if user hasn't defined a value) + if (!barrierBlock.connection.isConnected()) { + barrierBlock.attachShadowBlock(barriers[barrier], 'NUM', 'math_number'); + } + } + }); + // Check if number of barriers returned by API is less than barrier inputs on our workspace + // Remove leftover barrierBlockNames from the workspace + if (barrierKeys.length < barrierBlockNames.length) { + barrierBlockNames + .slice(barrierKeys.length) + .forEach(barrierName => tradeOptionsBlock.removeInput(barrierName)); + } + }); + }); + }, + updateDurationLists(contracts, updateOnly = []) { + getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => { + if (tradeOptionsBlock.disabled) return; + if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return; + + const tradeType = getParentValue(tradeOptionsBlock, 'TRADETYPE_LIST'); + const durationTypeList = tradeOptionsBlock.getField('DURATIONTYPE_LIST'); + + const durations = getDurationsForContracts(contracts, tradeType); + const durationOptions = durations.map(duration => [duration.label, duration.unit]); + + hideInteractionsFromBlockly(() => { + // Prevent UI flickering by only updating field only if options have changed + // eslint-disable-next-line no-underscore-dangle + if (JSON.stringify(durationTypeList.menuGenerator_) !== JSON.stringify(durationOptions)) { + durationTypeList.menuGenerator_ = durationOptions; // eslint-disable-line no-underscore-dangle + } + // Set duration to previous selected duration (required for imported strategies) + // eslint-disable-next-line no-underscore-dangle + const prevSelectedDuration = durationTypeList.menuGenerator_.find( + duration => duration[1] === durationTypeList.getValue() + ); + if (prevSelectedDuration) { + durationTypeList.setValue(''); + durationTypeList.setValue(prevSelectedDuration[1]); + // eslint-disable-next-line no-underscore-dangle + } else if (durationTypeList.menuGenerator_.length) { + durationTypeList.setValue(''); + durationTypeList.setValue(durationTypeList.menuGenerator_[0][1]); // eslint-disable-line no-underscore-dangle + } + // Attach shadow block with min value (only when user hasn't already attached another output block) + if (durations.length) { + const durationInput = tradeOptionsBlock.getInput('DURATION'); + if (!durationInput.connection.isConnected()) { + durationInput.attachShadowBlock(durations[0].minimum, 'NUM', 'math_number'); + } + } + }); + }); + }, }; Blockly.JavaScript.tradeOptions = block => { - const durationValue = expectValue(block, 'DURATION'); - const durationType = block.getFieldValue('DURATIONTYPE_LIST'); - const currency = block.getFieldValue('CURRENCY_LIST'); - const amount = expectValue(block, 'AMOUNT'); const tradeDefBlock = findTopParentBlock(block); if (!tradeDefBlock) { return ''; } - const oppositesName = tradeDefBlock.getFieldValue('TRADETYPE_LIST').toUpperCase(); - let predictionValue = 'undefined'; - let barrierOffsetValue = 'undefined'; - let secondBarrierOffsetValue = 'undefined'; - if (config.hasPrediction.indexOf(oppositesName) > -1) { - predictionValue = expectValue(block, 'PREDICTION'); - } - if ( - config.hasBarrierOffset.indexOf(oppositesName) > -1 || - config.hasSecondBarrierOffset.indexOf(oppositesName) > -1 - ) { - const barrierOffsetType = block.getFieldValue('BARRIEROFFSETTYPE_LIST'); - const value = expectValue(block, 'BARRIEROFFSET'); - barrierOffsetValue = `${barrierOffsetType}${value}`; - } - if (config.hasSecondBarrierOffset.indexOf(oppositesName) > -1) { - const barrierOffsetType = block.getFieldValue('SECONDBARRIEROFFSETTYPE_LIST'); - const value = expectValue(block, 'SECONDBARRIEROFFSET'); - secondBarrierOffsetValue = `${barrierOffsetType}${value}`; - } + const getInputValue = fieldName => + Blockly.JavaScript.valueToCode(block, fieldName, Blockly.JavaScript.ORDER_ATOMIC) || 'undefined'; const code = ` Bot.start({ limitations: BinaryBotPrivateLimitations, - duration: ${durationValue}, - duration_unit: '${durationType}', - currency: '${currency}', - amount: ${amount}, - prediction: ${predictionValue}, - barrierOffset: ${barrierOffsetValue}, - secondBarrierOffset: ${secondBarrierOffsetValue}, + duration: ${expectValue(block, 'DURATION')}, + duration_unit: '${block.getFieldValue('DURATIONTYPE_LIST')}', + currency: '${block.getFieldValue('CURRENCY_LIST')}', + amount: ${expectValue(block, 'AMOUNT')}, + prediction: ${getInputValue('PREDICTION')}, + barrierOffset: ${getInputValue('BARRIEROFFSET')}, + secondBarrierOffset: ${getInputValue('SECONDBARRIEROFFSET')}, }); `; return code; From 078c3aa1a7bc7156a17ee2174ee094ce9891862a Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 12:32:29 +0800 Subject: [PATCH 10/60] package-lock.json --- package-lock.json | 43 ++++++++++++------------------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0c4927a503..159c491185 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5268,8 +5268,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -5293,15 +5292,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5318,22 +5315,19 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -5464,8 +5458,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -5479,7 +5472,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5496,7 +5488,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5505,15 +5496,13 @@ "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5534,7 +5523,6 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5623,8 +5611,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -5638,7 +5625,6 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5734,8 +5720,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -5777,7 +5762,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5799,7 +5783,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5848,15 +5831,13 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true, - "optional": true + "dev": true } } }, @@ -8624,7 +8605,7 @@ "dev": true, "requires": { "acorn": "^4.0.11", - "clone": "github:aminmarashi/clone#d97b4f" + "clone": "github:aminmarashi/clone#d97b4f0ff3d3afebcaaf4a2ecc9c50fbce914900" }, "dependencies": { "acorn": { From 9d5039b6b23f5a6d6f6e0339b65fc343e526cd4b Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 12:44:54 +0800 Subject: [PATCH 11/60] Default to smallest barriers if barriers based on duration could not be found --- src/botPage/view/blockly/blocks/shared.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index abb397447b..5d749ae3a5 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -318,7 +318,8 @@ export const getBarriersForContracts = (contracts, selectedContractType, selecte const durations = getDurationsForContracts([c], selectedContractType); return durations.map(duration => duration.unit).includes(selectedDuration); }); - } else { + } + if (!contract) { // Default to smallest barriers available contract = contractsForContractCategory .sort((a, b) => { From 9e860b6793c6b4b8073750ef1e5be6255e6985fe Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 14:27:52 +0800 Subject: [PATCH 12/60] Maintain user-imported duration, but default to smallest duration on symbol-change --- .../view/blockly/blocks/trade/tradeOptions.js | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index eaac50f40a..570a5cbb03 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -29,19 +29,17 @@ export default () => { updateInputList(this); const block = Blockly.mainWorkspace.getBlockById(ev.blockId); if (ev.workspaceId === Blockly.mainWorkspace.id && block.type === 'trade') { - ['SYMBOL_LIST', 'TRADETYPE_LIST', 'DURATIONTYPE_LIST'].forEach(field => { - const changeEvent = new Blockly.Events.Change( - this, - 'field', - field, - '', - this.getFieldValue(field) - ); - Blockly.Events.fire(changeEvent); + const symbol = block.getFieldValue('SYMBOL_LIST'); + if (!symbol) return; + + this.pollForContracts(symbol).then(contracts => { + this.updateBarrierOffsetBlocks(contracts); + this.updatePredictionBlocks(contracts); + this.updateDurationLists(contracts, false, false); // false because we want to maintain user's values on import }); } } else if (ev.type === Blockly.Events.MOVE) { - // Make sure tradeOptions-blocks are consistent with symbol, tradeType when re-added to root-block + // Make sure tradeOptions-blocks are consistent with symbol/tradeType when re-added to root-block if (!ev.oldParentId && ev.newParentId) { const movedBlock = Blockly.mainWorkspace.getBlockById(ev.blockId); const topParentBlock = findTopParentBlock(movedBlock); @@ -55,7 +53,7 @@ export default () => { this.pollForContracts(symbol).then(contracts => { this.updateBarrierOffsetBlocks(contracts, [block.id]); this.updatePredictionBlocks(contracts, [block.id]); - this.updateDurationLists(contracts, [block.id]); + this.updateDurationLists(contracts, true, [block.id]); }); } else { block.getChildren().forEach(childBlock => { @@ -75,11 +73,17 @@ export default () => { this.pollForContracts(symbol).then(contracts => { if (ev.name === 'SYMBOL_LIST') { // Called to update duration options and min durations for symbol - this.updateDurationLists(contracts); - } else if (ev.name === 'TRADETYPE_LIST') { + this.updateDurationLists(contracts, true); + } else if (['TRADETYPECAT_LIST', 'TRADETYPE_LIST'].includes(ev.name)) { // Both are called to check if these blocks are required this.updatePredictionBlocks(contracts); this.updateBarrierOffsetBlocks(contracts); + + // Called to default to smallest durations for symbol + this.updateDurationLists(contracts, true); + } else if (ev.name === 'DURATIONTYPE_LIST') { + // Called to set min durations for selected unit + this.updateDurationLists(contracts, true, [this.id]); } updateInputList(this); }); @@ -107,7 +111,10 @@ export default () => { resolve(contracts.available); } }, 100); - setTimeout(() => clearInterval(pollingFn), 10000); + setTimeout(() => { + clearInterval(pollingFn), 10000; + resolve([]); + }); } } else { resolve(contractsForSymbol.available); @@ -176,7 +183,7 @@ export default () => { }); }); }, - updateDurationLists(contracts, updateOnly = []) { + updateDurationLists(contracts, defaultToMin = false, updateOnly = []) { getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => { if (tradeOptionsBlock.disabled) return; if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return; @@ -193,10 +200,11 @@ export default () => { if (JSON.stringify(durationTypeList.menuGenerator_) !== JSON.stringify(durationOptions)) { durationTypeList.menuGenerator_ = durationOptions; // eslint-disable-line no-underscore-dangle } + // Set duration to previous selected duration (required for imported strategies) // eslint-disable-next-line no-underscore-dangle const prevSelectedDuration = durationTypeList.menuGenerator_.find( - duration => duration[1] === durationTypeList.getValue() + d => d[1] === durationTypeList.getValue() ); if (prevSelectedDuration) { durationTypeList.setValue(''); @@ -206,11 +214,18 @@ export default () => { durationTypeList.setValue(''); durationTypeList.setValue(durationTypeList.menuGenerator_[0][1]); // eslint-disable-line no-underscore-dangle } + // Attach shadow block with min value (only when user hasn't already attached another output block) if (durations.length) { const durationInput = tradeOptionsBlock.getInput('DURATION'); if (!durationInput.connection.isConnected()) { durationInput.attachShadowBlock(durations[0].minimum, 'NUM', 'math_number'); + } else if (defaultToMin) { + const connectedBlock = durationInput.connection.targetBlock(); + const minDuration = durations.find(d => d.unit === durationTypeList.getValue()); + if (connectedBlock.isShadow() && minDuration) { + connectedBlock.setFieldValue(minDuration.minimum, 'NUM'); + } } } }); From 22605325e2e41773e703ac73f50396226843bd5d Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 16:21:39 +0800 Subject: [PATCH 13/60] Add args to only change min duration in updateDurationLists args --- .../view/blockly/blocks/trade/tradeOptions.js | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index 570a5cbb03..2b82c88729 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -51,15 +51,14 @@ export default () => { const getNestedTradeOptions = block => { if (block.type === 'tradeOptions') { this.pollForContracts(symbol).then(contracts => { - this.updateBarrierOffsetBlocks(contracts, [block.id]); + this.updateBarrierOffsetBlocks(contracts, true, [block.id]); this.updatePredictionBlocks(contracts, [block.id]); - this.updateDurationLists(contracts, true, [block.id]); - }); - } else { - block.getChildren().forEach(childBlock => { - getNestedTradeOptions(childBlock); + this.updateDurationLists(contracts, true, true, [block.id]); }); } + block.getChildren().forEach(childBlock => { + getNestedTradeOptions(childBlock); + }); }; getNestedTradeOptions(movedBlock); } @@ -71,19 +70,20 @@ export default () => { if (!symbol) return; this.pollForContracts(symbol).then(contracts => { - if (ev.name === 'SYMBOL_LIST') { - // Called to update duration options and min durations for symbol - this.updateDurationLists(contracts, true); - } else if (['TRADETYPECAT_LIST', 'TRADETYPE_LIST'].includes(ev.name)) { + if (ev.name === 'SYMBOL_LIST' && ev.oldValue !== ev.newValue) { + // Called to update duration options and set min durations + this.updateDurationLists(contracts, true, true); + } else if (['TRADETYPE_LIST'].includes(ev.name) && ev.oldValue !== ev.newValue) { // Both are called to check if these blocks are required this.updatePredictionBlocks(contracts); - this.updateBarrierOffsetBlocks(contracts); - + this.updateBarrierOffsetBlocks(contracts, true); // Called to default to smallest durations for symbol this.updateDurationLists(contracts, true); - } else if (ev.name === 'DURATIONTYPE_LIST') { + } else if (ev.name === 'DURATIONTYPE_LIST' && ev.oldValue !== ev.newValue) { + // Called to set barriers based on duration + this.updateBarrierOffsetBlocks(contracts, true, [ev.blockId]); // Called to set min durations for selected unit - this.updateDurationLists(contracts, true, [this.id]); + this.updateDurationLists(contracts, false, true, [ev.blockId]); } updateInputList(this); }); @@ -93,7 +93,7 @@ export default () => { pollForContracts(symbol) { return new Promise(resolve => { const contractsForSymbol = haveContractsForSymbol(symbol); - if (!haveContractsForSymbol(symbol)) { + if (!contractsForSymbol) { // Register an event and use as a lock to avoid spamming API const event = `contractsLoaded.${symbol}`; if (!globalObserver.isRegistered(event)) { @@ -112,9 +112,9 @@ export default () => { } }, 100); setTimeout(() => { - clearInterval(pollingFn), 10000; + clearInterval(pollingFn); resolve([]); - }); + }, 10000); } } else { resolve(contractsForSymbol.available); @@ -146,7 +146,7 @@ export default () => { }); }); }, - updateBarrierOffsetBlocks(contracts, updateOnly = []) { + updateBarrierOffsetBlocks(contracts, useDefault = false, updateOnly = []) { getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => { if (tradeOptionsBlock.disabled) return; if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return; @@ -163,18 +163,23 @@ export default () => { } const barrierKeys = Object.keys(barriers); barrierKeys.forEach((barrier, index) => { - const barrierBlock = tradeOptionsBlock.getInput(barrierBlockNames[index]); - if (barrierBlock) { - barrierBlock.setVisible(true); + const barrierInput = tradeOptionsBlock.getInput(barrierBlockNames[index]); + if (barrierInput) { + barrierInput.setVisible(true); // Attach shadow block with API-returned barrier-value (only if user hasn't defined a value) - if (!barrierBlock.connection.isConnected()) { - barrierBlock.attachShadowBlock(barriers[barrier], 'NUM', 'math_number'); + if (!barrierInput.connection.isConnected()) { + barrierInput.attachShadowBlock(barriers[barrier], 'NUM', 'math_number'); + } else if (useDefault) { + const connectedBlock = barrierInput.connection.targetBlock(); + if (connectedBlock.isShadow()) { + connectedBlock.setFieldValue(barriers[barrier], 'NUM'); + } } } }); // Check if number of barriers returned by API is less than barrier inputs on our workspace - // Remove leftover barrierBlockNames from the workspace + // If any, remove leftover barrierBlockNames from the workspace if (barrierKeys.length < barrierBlockNames.length) { barrierBlockNames .slice(barrierKeys.length) @@ -183,13 +188,14 @@ export default () => { }); }); }, - updateDurationLists(contracts, defaultToMin = false, updateOnly = []) { + updateDurationLists(contracts, useDefaultUnit = false, setMinDuration = false, updateOnly = []) { getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => { if (tradeOptionsBlock.disabled) return; if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return; const tradeType = getParentValue(tradeOptionsBlock, 'TRADETYPE_LIST'); const durationTypeList = tradeOptionsBlock.getField('DURATIONTYPE_LIST'); + const selectedDuration = durationTypeList.getValue(); const durations = getDurationsForContracts(contracts, tradeType); const durationOptions = durations.map(duration => [duration.label, duration.unit]); @@ -203,16 +209,14 @@ export default () => { // Set duration to previous selected duration (required for imported strategies) // eslint-disable-next-line no-underscore-dangle - const prevSelectedDuration = durationTypeList.menuGenerator_.find( - d => d[1] === durationTypeList.getValue() - ); - if (prevSelectedDuration) { + const prevSelectedDuration = durationTypeList.menuGenerator_.find(d => d[1] === selectedDuration); + if (!useDefaultUnit && prevSelectedDuration) { durationTypeList.setValue(''); durationTypeList.setValue(prevSelectedDuration[1]); // eslint-disable-next-line no-underscore-dangle } else if (durationTypeList.menuGenerator_.length) { durationTypeList.setValue(''); - durationTypeList.setValue(durationTypeList.menuGenerator_[0][1]); // eslint-disable-line no-underscore-dangle + durationTypeList.setValue(durationTypeList.menuGenerator_[0][1]); } // Attach shadow block with min value (only when user hasn't already attached another output block) @@ -220,9 +224,10 @@ export default () => { const durationInput = tradeOptionsBlock.getInput('DURATION'); if (!durationInput.connection.isConnected()) { durationInput.attachShadowBlock(durations[0].minimum, 'NUM', 'math_number'); - } else if (defaultToMin) { + } else if (setMinDuration) { const connectedBlock = durationInput.connection.targetBlock(); - const minDuration = durations.find(d => d.unit === durationTypeList.getValue()); + const minDuration = durations.find(d => d.unit === selectedDuration); + if (connectedBlock.isShadow() && minDuration) { connectedBlock.setFieldValue(minDuration.minimum, 'NUM'); } From 07fa8fc92b6035510623c34c08b06e3b2b119122 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 16:53:36 +0800 Subject: [PATCH 14/60] Remove default duration value --- static/xml/main.xml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/static/xml/main.xml b/static/xml/main.xml index 88140c3ad1..fb7fb2f874 100644 --- a/static/xml/main.xml +++ b/static/xml/main.xml @@ -4,11 +4,7 @@ t USD - - - 5 - - + 1 From e89288ed8d6a8db85247fece84fe4ad040f89b01 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 16:56:47 +0800 Subject: [PATCH 15/60] Add eslint exception --- src/botPage/view/blockly/blocks/trade/tradeOptions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index 2b82c88729..96f71ae690 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -216,6 +216,7 @@ export default () => { // eslint-disable-next-line no-underscore-dangle } else if (durationTypeList.menuGenerator_.length) { durationTypeList.setValue(''); + // eslint-disable-next-line no-underscore-dangle durationTypeList.setValue(durationTypeList.menuGenerator_[0][1]); } From 5bd5659d3958da8aa698f68536925c5d5e188578 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 21 Feb 2019 17:29:32 +0800 Subject: [PATCH 16/60] Small refactor --- src/botPage/view/blockly/blocks/trade/components.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/components.js b/src/botPage/view/blockly/blocks/trade/components.js index a843b2a317..fe7604b4d2 100644 --- a/src/botPage/view/blockly/blocks/trade/components.js +++ b/src/botPage/view/blockly/blocks/trade/components.js @@ -72,7 +72,6 @@ export const payout = block => { const barrierOffsetGenerator = (inputName, block) => { if (!block.getInput(inputName)) { // Determine amount of barrier-blocks on workspace - const labelPrefix = translate('Barrier Offset'); const barrierNumber = block.inputList.filter(input => /BARRIEROFFSET$/.test(input.name)).length; // Set barrier options according to barrierNumber (i.e. + and -) @@ -82,7 +81,7 @@ const barrierOffsetGenerator = (inputName, block) => { block .appendValueInput(inputName) .setCheck('Number') - .appendField(`${labelPrefix} ${barrierNumber + 1}:`) + .appendField(`${translate('Barrier Offset')} ${barrierNumber + 1}:`) .appendField(barrierOffsetList, `${inputName}TYPE_LIST`); const input = block.getInput(inputName); From 3d61458b7188b22c6a3881e26996fe5f5c421aba Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Fri, 22 Feb 2019 09:37:39 +0800 Subject: [PATCH 17/60] Default to smallest durations when changing tradeType --- src/botPage/view/blockly/blocks/trade/tradeOptions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index 96f71ae690..b78287901f 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -78,7 +78,7 @@ export default () => { this.updatePredictionBlocks(contracts); this.updateBarrierOffsetBlocks(contracts, true); // Called to default to smallest durations for symbol - this.updateDurationLists(contracts, true); + this.updateDurationLists(contracts, true, true); } else if (ev.name === 'DURATIONTYPE_LIST' && ev.oldValue !== ev.newValue) { // Called to set barriers based on duration this.updateBarrierOffsetBlocks(contracts, true, [ev.blockId]); From 0d3c321c5a61077ac1b03bcffd9a2e73f87b699b Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Fri, 22 Feb 2019 10:39:53 +0800 Subject: [PATCH 18/60] Don't let haveContractForSymbol bypass isDifferentAccount() and isExpiredData() checks --- src/botPage/view/blockly/blocks/shared.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index 5d749ae3a5..db875bb0cd 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -236,27 +236,30 @@ export const getDurationsForContracts = (contractsAvailable, selectedContractTyp ); }; -export const haveContractsForSymbol = underlyingSymbol => contractsForStore.find(c => c.symbol === underlyingSymbol); - -export const getContractsAvailableForSymbol = async underlyingSymbol => { - const contractsForSymbol = haveContractsForSymbol(underlyingSymbol); +export const haveContractsForSymbol = underlyingSymbol => { + const contractsForSymbol = contractsForStore.find(c => c.symbol === underlyingSymbol); if (!contractsForSymbol) { - const contractsAvailableForSymbol = await getContractsAvailableForSymbolFromApi(underlyingSymbol); - return Promise.resolve(contractsAvailableForSymbol.available); + return false; } - // Different accounts have access to different markets, request and serve new data const tokenList = getTokenList(); const isDifferentAccount = () => tokenList.length && contractsForSymbol.accountName !== tokenList[0].accountName; if (isDifferentAccount()) { - const contractsAvailableForSymbol = await getContractsAvailableForSymbolFromApi(underlyingSymbol); - return Promise.resolve(contractsAvailableForSymbol.available); + return false; } // Data expired, serve cached data, but retrieve updated data async const isExpiredData = () => Math.floor((Date.now() - contractsForSymbol.timestamp) / 1000) > 600; if (isExpiredData()) { getContractsAvailableForSymbolFromApi(underlyingSymbol); } - // Not a different account or expired, return what we have. + return contractsForSymbol; +}; + +export const getContractsAvailableForSymbol = async underlyingSymbol => { + const contractsForSymbol = haveContractsForSymbol(underlyingSymbol); + if (!contractsForSymbol) { + const contractsAvailableForSymbol = await getContractsAvailableForSymbolFromApi(underlyingSymbol); + return Promise.resolve(contractsAvailableForSymbol.available); + } return Promise.resolve(contractsForSymbol.available); }; From abd1e4363ec9c31f0fca9a7fb705d8a6155040af Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Fri, 22 Feb 2019 10:45:31 +0800 Subject: [PATCH 19/60] Only get updated data if not already doing so --- src/botPage/view/blockly/blocks/shared.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index db875bb0cd..d4f07ba6cf 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -246,10 +246,12 @@ export const haveContractsForSymbol = underlyingSymbol => { if (isDifferentAccount()) { return false; } - // Data expired, serve cached data, but retrieve updated data async + // Data expired, return cached data, retrieve updated data in background (if not already doing so) const isExpiredData = () => Math.floor((Date.now() - contractsForSymbol.timestamp) / 1000) > 600; if (isExpiredData()) { - getContractsAvailableForSymbolFromApi(underlyingSymbol); + if (!globalObserver.isRegistered(`contractsLoaded.${underlyingSymbol}`)) { + getContractsAvailableForSymbolFromApi(underlyingSymbol); + } } return contractsForSymbol; }; From 23945c93922a10a69cf6aae33df56d2c553ad2ad Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Fri, 22 Feb 2019 15:08:53 +0800 Subject: [PATCH 20/60] Lock call to getContractsForSymbolFromApi --- src/botPage/view/blockly/blocks/shared.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index d4f07ba6cf..aaa09ae959 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -249,7 +249,9 @@ export const haveContractsForSymbol = underlyingSymbol => { // Data expired, return cached data, retrieve updated data in background (if not already doing so) const isExpiredData = () => Math.floor((Date.now() - contractsForSymbol.timestamp) / 1000) > 600; if (isExpiredData()) { - if (!globalObserver.isRegistered(`contractsLoaded.${underlyingSymbol}`)) { + const event = `contractsLoaded.${underlyingSymbol}`; + if (!globalObserver.isRegistered(event)) { + globalObserver.register(event); getContractsAvailableForSymbolFromApi(underlyingSymbol); } } @@ -298,6 +300,7 @@ export const getContractsAvailableForSymbolFromApi = async underlyingSymbol => { ); contractsForStore.push(contractsForSymbol); setStorage('contractsForStore', JSON.stringify(contractsForStore)); + globalObserver.emit(`contractsLoaded.${underlyingSymbol}`); } } catch (e) { if (window.trackJs) { From 38335d0d8f61473484a87a1b01599bfe1490a388 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Fri, 22 Feb 2019 15:24:29 +0800 Subject: [PATCH 21/60] Suppress incorrect error --- src/botPage/view/blockly/blocks/shared.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index aaa09ae959..7f069d4ca1 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -306,7 +306,6 @@ export const getContractsAvailableForSymbolFromApi = async underlyingSymbol => { if (window.trackJs) { trackJs.addMetadata('getContractsAvailableForSymbolFromApi Error', e.message); } - globalObserver.emit('ui.log.error', translate('Could not retrieve contracts')); } if (typeof api.disconnect === 'function') { api.disconnect(); From ca5236aa4d65dab20ddd324b7041890553bbd89b Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Fri, 22 Feb 2019 15:41:21 +0800 Subject: [PATCH 22/60] One-time events don't work --- src/botPage/view/blockly/blocks/shared.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index 7f069d4ca1..f43422e7f8 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -300,7 +300,7 @@ export const getContractsAvailableForSymbolFromApi = async underlyingSymbol => { ); contractsForStore.push(contractsForSymbol); setStorage('contractsForStore', JSON.stringify(contractsForStore)); - globalObserver.emit(`contractsLoaded.${underlyingSymbol}`); + globalObserver.unregisterAll(`contractsLoaded.${underlyingSymbol}`); } } catch (e) { if (window.trackJs) { From 4f701731d9f06bb89339de89f371d64ee50a2417 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 09:46:02 +0800 Subject: [PATCH 23/60] Maintain duration unit when changing tradeType --- src/botPage/view/blockly/blocks/trade/tradeOptions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index b78287901f..a8b04c7778 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -78,7 +78,7 @@ export default () => { this.updatePredictionBlocks(contracts); this.updateBarrierOffsetBlocks(contracts, true); // Called to default to smallest durations for symbol - this.updateDurationLists(contracts, true, true); + this.updateDurationLists(contracts, false, true); } else if (ev.name === 'DURATIONTYPE_LIST' && ev.oldValue !== ev.newValue) { // Called to set barriers based on duration this.updateBarrierOffsetBlocks(contracts, true, [ev.blockId]); From 2188a8b8027f5fde08a368f6ca00efdd3a0d1be6 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 09:49:17 +0800 Subject: [PATCH 24/60] Disable frxUSDSEK as we don't offer forward-starting contracts --- src/botPage/view/blockly/blocks/shared.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index f43422e7f8..035c8a821d 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -117,7 +117,7 @@ fieldGeneratorMapping.SYMBOL_LIST = block => () => { Object.keys(symbols) .map(e => [symbols[e].display, symbols[e].symbol]) // Filter out symbols we don't have contracts for (these symbols have only forward-starting) - .filter(symbol => !['frxGBPNOK', 'frxUSDNOK', 'frxUSDNEK'].includes(symbol[1])) + .filter(symbol => !['frxGBPNOK', 'frxUSDNOK', 'frxUSDNEK', 'frxUSDSEK'].includes(symbol[1])) ); }; From de86a463689259d2ed17474b8eb3c1e2a4f19d7e Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 13:01:55 +0800 Subject: [PATCH 25/60] Add absoluteBarrier --- src/botPage/bot/TradeEngine/Proposal.js | 1 + src/botPage/bot/__tests__/UI.js | 1 + .../view/blockly/blocks/trade/backwardCompatibility.js | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/botPage/bot/TradeEngine/Proposal.js b/src/botPage/bot/TradeEngine/Proposal.js index 01334e4a4d..3aa102f357 100644 --- a/src/botPage/bot/TradeEngine/Proposal.js +++ b/src/botPage/bot/TradeEngine/Proposal.js @@ -112,6 +112,7 @@ export default Engine => isNotEqual('duration') || isNotEqual('amount') || isNotEqual('prediction') || + isNotEqual('absoluteBarrier') || isNotEqual('barrierOffset') || isNotEqual('secondBarrierOffset') ); diff --git a/src/botPage/bot/__tests__/UI.js b/src/botPage/bot/__tests__/UI.js index d37f3bef43..89b790ef62 100644 --- a/src/botPage/bot/__tests__/UI.js +++ b/src/botPage/bot/__tests__/UI.js @@ -36,6 +36,7 @@ expectReturnTrue( currency: 'USD', amount: 1, prediction: undefined, + absoluteBarrier: undefined, barrierOffset: undefined, secondBarrierOffset: undefined, }); diff --git a/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js b/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js index db7dd350ab..607d74df19 100644 --- a/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js +++ b/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js @@ -2,7 +2,7 @@ import { translate } from '../../../../../common/i18n'; import config from '../../../../common/const'; import { symbolApi } from '../../../shared'; import { setInputList, marketDefPlaceHolders, marketToTradeOption } from './tools'; -import { duration, payout, prediction, barrierOffset, secondBarrierOffset } from './components'; +import { duration, payout, prediction, barrierOffset, secondBarrierOffset, absoluteBarrier } from './components'; const isBlockCreationEvent = (ev, block) => ev.type === Blockly.Events.CREATE && ev.ids.indexOf(block.id) >= 0; @@ -47,6 +47,7 @@ export default () => { duration(this); payout(this); prediction(this); + absoluteBarrier(this); barrierOffset(this); secondBarrierOffset(this); this.setInputsInline(false); @@ -64,6 +65,7 @@ export default () => { duration(this); payout(this); prediction(this); + absoluteBarrier(this); barrierOffset(this); secondBarrierOffset(this); this.setInputsInline(false); From 923cd4eb5f582160d159559c1129229db65895b2 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 13:02:45 +0800 Subject: [PATCH 26/60] Refactor tradeOptionToProposal() and add absoluteBarrier prop --- src/botPage/bot/tools.js | 50 ++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/botPage/bot/tools.js b/src/botPage/bot/tools.js index ae48022011..e564bfb23b 100644 --- a/src/botPage/bot/tools.js +++ b/src/botPage/bot/tools.js @@ -8,29 +8,33 @@ export const noop = () => {}; const castBarrierToString = barrier => (barrier > 0 ? `+${barrier}` : `${barrier}`); export const tradeOptionToProposal = tradeOption => - tradeOption.contractTypes.map(type => ({ - duration_unit: tradeOption.duration_unit, - basis : 'stake', - currency : tradeOption.currency, - symbol : tradeOption.symbol, - duration : tradeOption.duration, - amount : roundBalance({ currency: tradeOption.currency, balance: tradeOption.amount }), - contract_type: type, - ...(tradeOption.prediction !== undefined && { - selected_tick: tradeOption.prediction, - }), - ...(type !== 'TICKLOW' && - type !== 'TICKHIGH' && - tradeOption.prediction !== undefined && { - barrier: tradeOption.prediction, - }), - ...(tradeOption.barrierOffset !== undefined && { - barrier: castBarrierToString(tradeOption.barrierOffset), - }), - ...(tradeOption.secondBarrierOffset !== undefined && { - barrier2: castBarrierToString(tradeOption.secondBarrierOffset), - }), - })); + tradeOption.contractTypes.map(type => { + const proposal = { + duration_unit: tradeOption.duration_unit, + basis : 'stake', + currency : tradeOption.currency, + symbol : tradeOption.symbol, + duration : tradeOption.duration, + amount : roundBalance({ currency: tradeOption.currency, balance: tradeOption.amount }), + contract_type: type, + }; + if (tradeOption.prediction !== undefined) { + proposal.selected_tick = tradeOption.prediction; + } + + if (!['TICKLOW', 'TICKHIGH'].includes(type) && tradeOption.prediction !== undefined) { + proposal.barrier = castBarrierToString(tradeOption.barrierOffset); + } else if (tradeOption.absoluteBarrier !== undefined) { + proposal.barrier = `${tradeOption.absoluteBarrier}`; + } else if (tradeOption.barrierOffset !== undefined) { + proposal.barrier = castBarrierToString(tradeOption.barrierOffset); + } + + if (tradeOption.secondBarrierOffset !== undefined) { + proposal.barrier2 = castBarrierToString(tradeOption.secondBarrierOffset); + } + return proposal; + }); export const getDirection = ticks => { const { length } = ticks; From 441375456030bbd2ed480e012e7636ab48f0a866 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 13:05:03 +0800 Subject: [PATCH 27/60] Set barrier equal to contracts.barrier (req for day-durations) --- src/botPage/view/blockly/blocks/shared.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index 035c8a821d..c79b2b8a4b 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -361,9 +361,7 @@ export const getBarriersForContracts = (contracts, selectedContractType, selecte if (barrierOffsetMatch) { barriers.barrier = barrierOffsetMatch[1]; // eslint-disable-line prefer-destructuring } else { - // Return default value if we only get a barrier based on price (e.g. 234.1231) - // as we can't calculate the offset since we don't stream price like SmartTrader - barriers.barrier = 0.1; + barriers.barrier = contract.barrier; } } } From 8db78cf9472f91cac0c97f2c645c7d9ffee4be4a Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 13:05:35 +0800 Subject: [PATCH 28/60] Add absoluteBarrier() block generator function --- .../view/blockly/blocks/trade/components.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/botPage/view/blockly/blocks/trade/components.js b/src/botPage/view/blockly/blocks/trade/components.js index fe7604b4d2..61b244f0bc 100644 --- a/src/botPage/view/blockly/blocks/trade/components.js +++ b/src/botPage/view/blockly/blocks/trade/components.js @@ -71,7 +71,7 @@ export const payout = block => { const barrierOffsetGenerator = (inputName, block) => { if (!block.getInput(inputName)) { - // Determine amount of barrier-blocks on workspace + // Determine amount of barrierOffset-blocks on workspace const barrierNumber = block.inputList.filter(input => /BARRIEROFFSET$/.test(input.name)).length; // Set barrier options according to barrierNumber (i.e. + and -) @@ -110,6 +110,19 @@ export const prediction = block => { } }; +export const absoluteBarrier = block => { + const inputName = 'ABSOLUTEBARRIER'; + if (!block.getInput(inputName)) { + block + .appendValueInput(inputName) + .setCheck('Number') + .appendField(`${translate('Barrier')}:`); + + const input = block.getInput(inputName); + input.setVisible(false); + } +}; + export const restart = block => { block .appendDummyInput() From f87af5e08c5aa2b6afe792317246a7db08faec33 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 13:06:25 +0800 Subject: [PATCH 29/60] Create logic to determine ABSOLUTEBARRIER vs (SECOND)BARRIEROFFSET --- .../view/blockly/blocks/trade/tradeOptions.js | 40 ++++++++++++------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index a8b04c7778..89475becf9 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -156,14 +156,8 @@ export default () => { const barriers = getBarriersForContracts(contracts, tradeType, selectedDuration); hideInteractionsFromBlockly(() => { - const barrierBlockNames = ['BARRIEROFFSET', 'SECONDBARRIEROFFSET']; - if (!Object.keys(barriers).length) { - barrierBlockNames.forEach(barrierInputName => tradeOptionsBlock.removeInput(barrierInputName)); - return; - } - const barrierKeys = Object.keys(barriers); - barrierKeys.forEach((barrier, index) => { - const barrierInput = tradeOptionsBlock.getInput(barrierBlockNames[index]); + const revealBarrierBlock = (barrier, inputName) => { + const barrierInput = tradeOptionsBlock.getInput(inputName); if (barrierInput) { barrierInput.setVisible(true); @@ -177,13 +171,30 @@ export default () => { } } } - }); - // Check if number of barriers returned by API is less than barrier inputs on our workspace - // If any, remove leftover barrierBlockNames from the workspace - if (barrierKeys.length < barrierBlockNames.length) { + }; + + const barrierBlockNames = ['BARRIEROFFSET', 'SECONDBARRIEROFFSET', 'ABSOLUTEBARRIER']; + if (!Object.keys(barriers).length) { + barrierBlockNames.forEach(barrierInputName => tradeOptionsBlock.removeInput(barrierInputName)); + return; + } + + const barrierKeys = Object.keys(barriers); + if (barrierKeys.length === 1 && selectedDuration === 'd') { + revealBarrierBlock(barrierKeys[0], 'ABSOLUTEBARRIER'); barrierBlockNames - .slice(barrierKeys.length) - .forEach(barrierName => tradeOptionsBlock.removeInput(barrierName)); + .slice(0, 2) + .forEach(barrierBlockName => tradeOptionsBlock.removeInput(barrierBlockName)); + } else { + barrierKeys.forEach((barrier, index) => revealBarrierBlock(barrier, barrierBlockNames[index])); + + // Check if number of barriers returned by API is less than barrier inputs on our workspace + // If any, remove leftover barrierBlockNames from the workspace + if (barrierKeys.length < barrierBlockNames.length) { + barrierBlockNames + .slice(barrierKeys.length) + .forEach(barrierName => tradeOptionsBlock.removeInput(barrierName)); + } } }); }); @@ -253,6 +264,7 @@ export default () => { currency: '${block.getFieldValue('CURRENCY_LIST')}', amount: ${expectValue(block, 'AMOUNT')}, prediction: ${getInputValue('PREDICTION')}, + absoluteBarrier: ${getInputValue('ABSOLUTEBARRIER')}, barrierOffset: ${getInputValue('BARRIEROFFSET')}, secondBarrierOffset: ${getInputValue('SECONDBARRIEROFFSET')}, }); From a49c17a30cfc8d0dc9d7b4dc22d49231f035a108 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 16:21:20 +0800 Subject: [PATCH 30/60] Create secondAbsoluteBarrier --- src/botPage/bot/TradeEngine/Proposal.js | 1 + src/botPage/bot/__tests__/UI.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/botPage/bot/TradeEngine/Proposal.js b/src/botPage/bot/TradeEngine/Proposal.js index 3aa102f357..0ce4c3da57 100644 --- a/src/botPage/bot/TradeEngine/Proposal.js +++ b/src/botPage/bot/TradeEngine/Proposal.js @@ -113,6 +113,7 @@ export default Engine => isNotEqual('amount') || isNotEqual('prediction') || isNotEqual('absoluteBarrier') || + isNotEqual('secondAbsoluteBarrier') || isNotEqual('barrierOffset') || isNotEqual('secondBarrierOffset') ); diff --git a/src/botPage/bot/__tests__/UI.js b/src/botPage/bot/__tests__/UI.js index 89b790ef62..b7445df503 100644 --- a/src/botPage/bot/__tests__/UI.js +++ b/src/botPage/bot/__tests__/UI.js @@ -37,6 +37,7 @@ expectReturnTrue( amount: 1, prediction: undefined, absoluteBarrier: undefined, + secondAbsoluteBarrier: undefined; barrierOffset: undefined, secondBarrierOffset: undefined, }); From 157fa57961bd7d75231591d0d797eac1a1ab3f5a Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 16:22:03 +0800 Subject: [PATCH 31/60] Add secondAbsoluteBarrier to proposal --- src/botPage/bot/tools.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/botPage/bot/tools.js b/src/botPage/bot/tools.js index e564bfb23b..dc5a874e9b 100644 --- a/src/botPage/bot/tools.js +++ b/src/botPage/bot/tools.js @@ -5,8 +5,6 @@ import { notify } from './broadcast'; export const noop = () => {}; -const castBarrierToString = barrier => (barrier > 0 ? `+${barrier}` : `${barrier}`); - export const tradeOptionToProposal = tradeOption => tradeOption.contractTypes.map(type => { const proposal = { @@ -23,15 +21,17 @@ export const tradeOptionToProposal = tradeOption => } if (!['TICKLOW', 'TICKHIGH'].includes(type) && tradeOption.prediction !== undefined) { - proposal.barrier = castBarrierToString(tradeOption.barrierOffset); + proposal.barrier = tradeOption.prediction; } else if (tradeOption.absoluteBarrier !== undefined) { proposal.barrier = `${tradeOption.absoluteBarrier}`; } else if (tradeOption.barrierOffset !== undefined) { - proposal.barrier = castBarrierToString(tradeOption.barrierOffset); + proposal.barrier = tradeOption.barrierOffset; } - if (tradeOption.secondBarrierOffset !== undefined) { - proposal.barrier2 = castBarrierToString(tradeOption.secondBarrierOffset); + if (tradeOption.secondAbsoluteBarrier !== undefined) { + proposal.barrier2 = tradeOption.secondAbsoluteBarrier; + } else if (tradeOption.secondBarrierOffset !== undefined) { + proposal.barrier2 = tradeOption.secondBarrierOffset; } return proposal; }); From 660e9373cab6cf2517a81f410e62128ed4bb8481 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 16:22:29 +0800 Subject: [PATCH 32/60] Add default inputs --- .../blocks/trade/backwardCompatibility.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js b/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js index 607d74df19..2c25677b0c 100644 --- a/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js +++ b/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js @@ -2,7 +2,7 @@ import { translate } from '../../../../../common/i18n'; import config from '../../../../common/const'; import { symbolApi } from '../../../shared'; import { setInputList, marketDefPlaceHolders, marketToTradeOption } from './tools'; -import { duration, payout, prediction, barrierOffset, secondBarrierOffset, absoluteBarrier } from './components'; +import { duration, payout, prediction, absoluteBarrierGenerator, barrierOffsetGenerator } from './components'; const isBlockCreationEvent = (ev, block) => ev.type === Blockly.Events.CREATE && ev.ids.indexOf(block.id) >= 0; @@ -47,9 +47,10 @@ export default () => { duration(this); payout(this); prediction(this); - absoluteBarrier(this); - barrierOffset(this); - secondBarrierOffset(this); + absoluteBarrierGenerator('ABSOLUTEBARRIER', this); + absoluteBarrierGenerator('SECONDABSOLUTEBARRIER', this); + barrierOffsetGenerator('BARRIEROFFSET', this); + barrierOffsetGenerator('SECONDBARRIEROFFSET', this); this.setInputsInline(false); this.setPreviousStatement(true, 'Condition'); }, @@ -65,9 +66,10 @@ export default () => { duration(this); payout(this); prediction(this); - absoluteBarrier(this); - barrierOffset(this); - secondBarrierOffset(this); + absoluteBarrierGenerator('ABSOLUTEBARRIER', this); + absoluteBarrierGenerator('SECONDABSOLUTEBARRIER', this); + barrierOffsetGenerator('BARRIEROFFSET', this); + barrierOffsetGenerator('SECONDBARRIEROFFSET', this); this.setInputsInline(false); this.setPreviousStatement(true, 'Condition'); }, From dbe9e2ae8165be0b70252c57ff4c278098455877 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 16:23:10 +0800 Subject: [PATCH 33/60] Remove wrapper functions, create common function for generating absolute and offset barriers --- .../view/blockly/blocks/trade/components.js | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/components.js b/src/botPage/view/blockly/blocks/trade/components.js index 61b244f0bc..0a80ebc6a9 100644 --- a/src/botPage/view/blockly/blocks/trade/components.js +++ b/src/botPage/view/blockly/blocks/trade/components.js @@ -69,7 +69,7 @@ export const payout = block => { } }; -const barrierOffsetGenerator = (inputName, block) => { +export const barrierOffsetGenerator = (inputName, block) => { if (!block.getInput(inputName)) { // Determine amount of barrierOffset-blocks on workspace const barrierNumber = block.inputList.filter(input => /BARRIEROFFSET$/.test(input.name)).length; @@ -89,34 +89,28 @@ const barrierOffsetGenerator = (inputName, block) => { } }; -export const barrierOffset = block => { - barrierOffsetGenerator('BARRIEROFFSET', block); -}; - -export const secondBarrierOffset = block => { - barrierOffsetGenerator('SECONDBARRIEROFFSET', block); -}; - -export const prediction = block => { - const inputName = 'PREDICTION'; +export const absoluteBarrierGenerator = (inputName, block, labels) => { if (!block.getInput(inputName)) { + const barrierNumber = block.inputList.filter(input => /ABSOLUTEBARRIER$/.test(input.name)).length; + block .appendValueInput(inputName) .setCheck('Number') - .appendField(`${translate('Prediction')}:`); + // Label is a fallback value, proper labels are set in tradeOptions + .appendField(`${translate('Barrier')} ${barrierNumber + 1}:`); const input = block.getInput(inputName); input.setVisible(false); } }; -export const absoluteBarrier = block => { - const inputName = 'ABSOLUTEBARRIER'; +export const prediction = block => { + const inputName = 'PREDICTION'; if (!block.getInput(inputName)) { block .appendValueInput(inputName) .setCheck('Number') - .appendField(`${translate('Barrier')}:`); + .appendField(`${translate('Prediction')}:`); const input = block.getInput(inputName); input.setVisible(false); From 73973f94a08aa47432fadeba8b192a587134a3c1 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 16:23:30 +0800 Subject: [PATCH 34/60] Move absoluteBarrierLabels to const.js --- src/botPage/common/const.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/botPage/common/const.js b/src/botPage/common/const.js index 44b6871014..80d384c793 100644 --- a/src/botPage/common/const.js +++ b/src/botPage/common/const.js @@ -142,8 +142,9 @@ const config = { }, ], }, - barrierTypes: [['+', '+'], ['-', '-']], - ohlcFields : [ + barrierTypes : [['+', '+'], ['-', '-']], + absoluteBarrierLabels: [translate('High barrier'), translate('Low barrier')], + ohlcFields : [ [translate('Open'), 'open'], [translate('High'), 'high'], [translate('Low'), 'low'], From 42cb8a0446891570d49095acf7acb92b2711301f Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 16:24:11 +0800 Subject: [PATCH 35/60] Add support for absoluteBarriers for day-durations, revert to old blocksToCode logic --- .../view/blockly/blocks/trade/tradeOptions.js | 113 +++++++++++++----- 1 file changed, 81 insertions(+), 32 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index 89475becf9..b8c9ef5f43 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -11,6 +11,7 @@ import { insideTrade } from '../../relationChecker'; import { findTopParentBlock, hideInteractionsFromBlockly, getBlocksByType } from '../../utils'; import { translate } from '../../../../../common/i18n'; import { observer as globalObserver } from '../../../../../common/utils/observer'; +import config from '../../../../common/const'; export default () => { Blockly.Blocks.tradeOptions = { @@ -173,28 +174,43 @@ export default () => { } }; - const barrierBlockNames = ['BARRIEROFFSET', 'SECONDBARRIEROFFSET', 'ABSOLUTEBARRIER']; - if (!Object.keys(barriers).length) { - barrierBlockNames.forEach(barrierInputName => tradeOptionsBlock.removeInput(barrierInputName)); - return; - } + const absoluteBarrierNames = ['ABSOLUTEBARRIER', 'SECONDABSOLUTEBARRIER']; + const barrierOffsetNames = ['BARRIEROFFSET', 'SECONDBARRIEROFFSET']; const barrierKeys = Object.keys(barriers); - if (barrierKeys.length === 1 && selectedDuration === 'd') { - revealBarrierBlock(barrierKeys[0], 'ABSOLUTEBARRIER'); - barrierBlockNames - .slice(0, 2) - .forEach(barrierBlockName => tradeOptionsBlock.removeInput(barrierBlockName)); - } else { - barrierKeys.forEach((barrier, index) => revealBarrierBlock(barrier, barrierBlockNames[index])); + if (!barrierKeys.length) { + [...absoluteBarrierNames, ...barrierOffsetNames].forEach(barrierInputName => + tradeOptionsBlock.removeInput(barrierInputName) + ); + return; + } - // Check if number of barriers returned by API is less than barrier inputs on our workspace - // If any, remove leftover barrierBlockNames from the workspace - if (barrierKeys.length < barrierBlockNames.length) { - barrierBlockNames - .slice(barrierKeys.length) - .forEach(barrierName => tradeOptionsBlock.removeInput(barrierName)); + // Draw absolute barriers for day-durations + if (selectedDuration === 'd') { + if (barrierKeys.length === 1) { + revealBarrierBlock(barrierKeys[0], absoluteBarrierNames[0]); + // Update label to show 'Barrier' only + const input = tradeOptionsBlock.getInput(absoluteBarrierNames[0]); + input.fieldRow[0].setText(`${translate('Barrier')}:`); + } else { + barrierKeys.forEach((barrier, index) => + revealBarrierBlock(barrier, absoluteBarrierNames[index]) + ); + // Update labels to show 'High barrier' + 'Low barrier' + absoluteBarrierNames.forEach((inputName, index) => { + const input = tradeOptionsBlock.getInput(inputName); + input.fieldRow[0].setText(`${config.absoluteBarrierLabels[index]}:`); + }); } + [...barrierOffsetNames, ...absoluteBarrierNames.slice(barrierKeys.length)].forEach(inputName => + tradeOptionsBlock.removeInput(inputName) + ); + } else { + // Draw barrier offsets for all other durations + barrierKeys.forEach((barrier, index) => revealBarrierBlock(barrier, barrierOffsetNames[index])); + [...absoluteBarrierNames, ...barrierOffsetNames.slice(barrierKeys.length)].forEach(inputName => + tradeOptionsBlock.removeInput(inputName) + ); } }); }); @@ -254,21 +270,54 @@ export default () => { if (!tradeDefBlock) { return ''; } - const getInputValue = fieldName => - Blockly.JavaScript.valueToCode(block, fieldName, Blockly.JavaScript.ORDER_ATOMIC) || 'undefined'; + + const durationValue = expectValue(block, 'DURATION'); + const durationType = block.getFieldValue('DURATIONTYPE_LIST'); + const currency = block.getFieldValue('CURRENCY_LIST'); + const amount = expectValue(block, 'AMOUNT'); + + const isVisibleField = field => block.getInput(field) && block.getInput(field).isVisible(); + + let predictionValue = 'undefined'; + let absoluteBarrierValue = 'undefined'; + const secondAbsoluteBarrierValue = 'undefined'; + let barrierOffsetValue = 'undefined'; + let secondBarrierOffsetValue = 'undefined'; + + if (isVisibleField('PREDICTION')) { + predictionValue = expectValue(block, 'PREDICTION'); + } + if (isVisibleField('ABSOLUTEBARRIER')) { + absoluteBarrierValue = expectValue(block, 'ABSOLUTEBARRIER'); + } + if (isVisibleField('SECONDABSOLUTEBARRIER')) { + absoluteBarrierValue = expectValue(block, 'SECONDABSOLUTEBARRIER'); + } + if (isVisibleField('BARRIEROFFSET')) { + const barrierOffsetType = block.getFieldValue('BARRIEROFFSETTYPE_LIST'); + const value = expectValue(block, 'BARRIEROFFSET'); + barrierOffsetValue = `'${barrierOffsetType}${value}'`; + } + if (isVisibleField('SECONDBARRIEROFFSET')) { + const barrierOffsetType = block.getFieldValue('SECONDBARRIEROFFSETTYPE_LIST'); + const value = expectValue(block, 'SECONDBARRIEROFFSET'); + secondBarrierOffsetValue = `'${barrierOffsetType}${value}'`; + } + const code = ` - Bot.start({ - limitations: BinaryBotPrivateLimitations, - duration: ${expectValue(block, 'DURATION')}, - duration_unit: '${block.getFieldValue('DURATIONTYPE_LIST')}', - currency: '${block.getFieldValue('CURRENCY_LIST')}', - amount: ${expectValue(block, 'AMOUNT')}, - prediction: ${getInputValue('PREDICTION')}, - absoluteBarrier: ${getInputValue('ABSOLUTEBARRIER')}, - barrierOffset: ${getInputValue('BARRIEROFFSET')}, - secondBarrierOffset: ${getInputValue('SECONDBARRIEROFFSET')}, - }); - `; + Bot.start({ + limitations: BinaryBotPrivateLimitations, + duration: ${durationValue}, + duration_unit: '${durationType}', + currency: '${currency}', + amount: ${amount}, + prediction: ${predictionValue}, + absoluteBarrier: ${absoluteBarrierValue}, + secondAbsoluteBarrier: ${secondAbsoluteBarrierValue}, + barrierOffset: ${barrierOffsetValue}, + secondBarrierOffset: ${secondBarrierOffsetValue}, + }); + `; return code; }; }; From 5b98cd9c96366f0daf07e06632f01a78d1abc932 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 16:33:26 +0800 Subject: [PATCH 36/60] Return API values rather than calculating the barrier value --- src/botPage/view/blockly/blocks/shared.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index c79b2b8a4b..2b9a050ed9 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -346,15 +346,8 @@ export const getBarriersForContracts = (contracts, selectedContractType, selecte barriers.high_barrier = highBarrierOffsetMatch[1]; // eslint-disable-line prefer-destructuring barriers.low_barrier = lowBarrierOffsetMatch[1]; // eslint-disable-line prefer-destructuring } else { - const countDecimals = number => { - if (Math.floor(number) === number) return 0; - return number.toString().split('.')[1].length || 0; - }; - const highBarrier = parseFloat(contract.high_barrier); - const lowBarrier = parseFloat(contract.low_barrier); - const barrier = ((highBarrier - lowBarrier) / 2).toFixed(countDecimals(contract.high_barrier)); - barriers.high_barrier = barrier; - barriers.low_barrier = barrier; + barriers.high_barrier = contract.high_barrier; + barriers.low_barrier = contract.low_barrier; } } else if (contract.barriers === 1 && contract.barrier) { const barrierOffsetMatch = contract.barrier.toString().match(offsetRegex); From dc66d16cdaa567a6bf325b28e68307b83a0266cd Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 16:33:51 +0800 Subject: [PATCH 37/60] Typo --- src/botPage/view/blockly/blocks/trade/tradeOptions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index b8c9ef5f43..c4b6b298cb 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -280,7 +280,7 @@ export default () => { let predictionValue = 'undefined'; let absoluteBarrierValue = 'undefined'; - const secondAbsoluteBarrierValue = 'undefined'; + let secondAbsoluteBarrierValue = 'undefined'; let barrierOffsetValue = 'undefined'; let secondBarrierOffsetValue = 'undefined'; @@ -291,7 +291,7 @@ export default () => { absoluteBarrierValue = expectValue(block, 'ABSOLUTEBARRIER'); } if (isVisibleField('SECONDABSOLUTEBARRIER')) { - absoluteBarrierValue = expectValue(block, 'SECONDABSOLUTEBARRIER'); + secondAbsoluteBarrierValue = expectValue(block, 'SECONDABSOLUTEBARRIER'); } if (isVisibleField('BARRIEROFFSET')) { const barrierOffsetType = block.getFieldValue('BARRIEROFFSETTYPE_LIST'); From 5ad9480ed14f2a1edf9030b61d5e841f4b06e42a Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 17:05:26 +0800 Subject: [PATCH 38/60] Remove unused labels var --- src/botPage/view/blockly/blocks/trade/components.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/botPage/view/blockly/blocks/trade/components.js b/src/botPage/view/blockly/blocks/trade/components.js index 0a80ebc6a9..98a3bdd615 100644 --- a/src/botPage/view/blockly/blocks/trade/components.js +++ b/src/botPage/view/blockly/blocks/trade/components.js @@ -89,7 +89,7 @@ export const barrierOffsetGenerator = (inputName, block) => { } }; -export const absoluteBarrierGenerator = (inputName, block, labels) => { +export const absoluteBarrierGenerator = (inputName, block) => { if (!block.getInput(inputName)) { const barrierNumber = block.inputList.filter(input => /ABSOLUTEBARRIER$/.test(input.name)).length; From 882615a7c35191317dfafbe71be6a7f47f03ca2f Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 27 Feb 2019 17:58:13 +0800 Subject: [PATCH 39/60] Fix test --- src/botPage/bot/__tests__/UI.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/botPage/bot/__tests__/UI.js b/src/botPage/bot/__tests__/UI.js index b7445df503..e8af883849 100644 --- a/src/botPage/bot/__tests__/UI.js +++ b/src/botPage/bot/__tests__/UI.js @@ -37,7 +37,7 @@ expectReturnTrue( amount: 1, prediction: undefined, absoluteBarrier: undefined, - secondAbsoluteBarrier: undefined; + secondAbsoluteBarrier: undefined, barrierOffset: undefined, secondBarrierOffset: undefined, }); From b837494c04ba6a7663d03f0ae886e2c1009bb7ff Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 28 Feb 2019 15:21:39 +0800 Subject: [PATCH 40/60] Remove absoluteBarriers --- src/botPage/bot/TradeEngine/Proposal.js | 2 -- src/botPage/bot/__tests__/UI.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/botPage/bot/TradeEngine/Proposal.js b/src/botPage/bot/TradeEngine/Proposal.js index 0ce4c3da57..01334e4a4d 100644 --- a/src/botPage/bot/TradeEngine/Proposal.js +++ b/src/botPage/bot/TradeEngine/Proposal.js @@ -112,8 +112,6 @@ export default Engine => isNotEqual('duration') || isNotEqual('amount') || isNotEqual('prediction') || - isNotEqual('absoluteBarrier') || - isNotEqual('secondAbsoluteBarrier') || isNotEqual('barrierOffset') || isNotEqual('secondBarrierOffset') ); diff --git a/src/botPage/bot/__tests__/UI.js b/src/botPage/bot/__tests__/UI.js index e8af883849..d37f3bef43 100644 --- a/src/botPage/bot/__tests__/UI.js +++ b/src/botPage/bot/__tests__/UI.js @@ -36,8 +36,6 @@ expectReturnTrue( currency: 'USD', amount: 1, prediction: undefined, - absoluteBarrier: undefined, - secondAbsoluteBarrier: undefined, barrierOffset: undefined, secondBarrierOffset: undefined, }); From 5534e6cccc64416fdb2dc67c4f952973cf30c2f4 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 28 Feb 2019 15:22:13 +0800 Subject: [PATCH 41/60] Prefix barrier types, remove absoluteBarrier related --- src/botPage/common/const.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/botPage/common/const.js b/src/botPage/common/const.js index 80d384c793..bd4b246509 100644 --- a/src/botPage/common/const.js +++ b/src/botPage/common/const.js @@ -142,9 +142,8 @@ const config = { }, ], }, - barrierTypes : [['+', '+'], ['-', '-']], - absoluteBarrierLabels: [translate('High barrier'), translate('Low barrier')], - ohlcFields : [ + barrierTypes: [['Offset +', '+'], ['Offset -', '-']], + ohlcFields : [ [translate('Open'), 'open'], [translate('High'), 'high'], [translate('Low'), 'low'], From ac091a57ed82cf42ea3ee9753951fbb77b1f26e4 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 28 Feb 2019 15:31:35 +0800 Subject: [PATCH 42/60] Remove references to absoluteBarriers --- src/botPage/bot/tools.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/botPage/bot/tools.js b/src/botPage/bot/tools.js index dc5a874e9b..881f705d86 100644 --- a/src/botPage/bot/tools.js +++ b/src/botPage/bot/tools.js @@ -19,18 +19,12 @@ export const tradeOptionToProposal = tradeOption => if (tradeOption.prediction !== undefined) { proposal.selected_tick = tradeOption.prediction; } - if (!['TICKLOW', 'TICKHIGH'].includes(type) && tradeOption.prediction !== undefined) { proposal.barrier = tradeOption.prediction; - } else if (tradeOption.absoluteBarrier !== undefined) { - proposal.barrier = `${tradeOption.absoluteBarrier}`; } else if (tradeOption.barrierOffset !== undefined) { proposal.barrier = tradeOption.barrierOffset; } - - if (tradeOption.secondAbsoluteBarrier !== undefined) { - proposal.barrier2 = tradeOption.secondAbsoluteBarrier; - } else if (tradeOption.secondBarrierOffset !== undefined) { + if (tradeOption.secondBarrierOffset !== undefined) { proposal.barrier2 = tradeOption.secondBarrierOffset; } return proposal; From 9b021bfe1894d0e29b1b9bdfa50a5ad0b968f186 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 28 Feb 2019 15:32:18 +0800 Subject: [PATCH 43/60] Create logic for allowing absolute barriers --- src/botPage/view/blockly/blocks/shared.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index 2b9a050ed9..fa91c0f98b 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -316,7 +316,7 @@ export const getContractsAvailableForSymbolFromApi = async underlyingSymbol => { export const getBarriersForContracts = (contracts, selectedContractType, selectedDuration) => { const category = getContractCategory(selectedContractType); const contractsForContractCategory = filterContractsByCategory(contracts, category, selectedContractType); - const barriers = {}; + const barriers = { values: [] }; if (contractsForContractCategory) { let contract; if (selectedDuration) { @@ -343,20 +343,27 @@ export const getBarriersForContracts = (contracts, selectedContractType, selecte const lowBarrierOffsetMatch = contract.low_barrier.toString().match(offsetRegex); if (highBarrierOffsetMatch && lowBarrierOffsetMatch) { - barriers.high_barrier = highBarrierOffsetMatch[1]; // eslint-disable-line prefer-destructuring - barriers.low_barrier = lowBarrierOffsetMatch[1]; // eslint-disable-line prefer-destructuring + // eslint-disable-next-line prefer-destructuring + barriers.values.push(...[highBarrierOffsetMatch[1], lowBarrierOffsetMatch[1]]); } else { - barriers.high_barrier = contract.high_barrier; - barriers.low_barrier = contract.low_barrier; + barriers.values.push(...[contract.high_barrier, contract.low_barrier]); } } else if (contract.barriers === 1 && contract.barrier) { const barrierOffsetMatch = contract.barrier.toString().match(offsetRegex); if (barrierOffsetMatch) { - barriers.barrier = barrierOffsetMatch[1]; // eslint-disable-line prefer-destructuring + // eslint-disable-next-line prefer-destructuring + barriers.values.push(barrierOffsetMatch[1]); } else { - barriers.barrier = contract.barrier; + barriers.values.push(contract.barrier); } } + const hasOffset = () => + [contract.barrier, contract.high_barrier, contract.low_barrier].some( + input => input && offsetRegex.test(input.toString()) + ); + if (['intraday', 'tick'].includes(contract.expiry_type) && hasOffset()) { + barriers.allowBothTypes = true; // Allow both offset + absolute barriers + } } } return barriers; From 729cb2cee8f2631d3890ccd7cf0fe593fc39b35a Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 28 Feb 2019 15:32:38 +0800 Subject: [PATCH 44/60] Remove absoluteBarriers --- .../view/blockly/blocks/trade/backwardCompatibility.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js b/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js index 2c25677b0c..0a7715ac9b 100644 --- a/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js +++ b/src/botPage/view/blockly/blocks/trade/backwardCompatibility.js @@ -2,7 +2,7 @@ import { translate } from '../../../../../common/i18n'; import config from '../../../../common/const'; import { symbolApi } from '../../../shared'; import { setInputList, marketDefPlaceHolders, marketToTradeOption } from './tools'; -import { duration, payout, prediction, absoluteBarrierGenerator, barrierOffsetGenerator } from './components'; +import { duration, payout, prediction, barrierOffsetGenerator } from './components'; const isBlockCreationEvent = (ev, block) => ev.type === Blockly.Events.CREATE && ev.ids.indexOf(block.id) >= 0; @@ -47,8 +47,6 @@ export default () => { duration(this); payout(this); prediction(this); - absoluteBarrierGenerator('ABSOLUTEBARRIER', this); - absoluteBarrierGenerator('SECONDABSOLUTEBARRIER', this); barrierOffsetGenerator('BARRIEROFFSET', this); barrierOffsetGenerator('SECONDBARRIEROFFSET', this); this.setInputsInline(false); @@ -66,8 +64,6 @@ export default () => { duration(this); payout(this); prediction(this); - absoluteBarrierGenerator('ABSOLUTEBARRIER', this); - absoluteBarrierGenerator('SECONDABSOLUTEBARRIER', this); barrierOffsetGenerator('BARRIEROFFSET', this); barrierOffsetGenerator('SECONDBARRIEROFFSET', this); this.setInputsInline(false); From cc08325a75442a9ed549beb96b48db991c814ac6 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 28 Feb 2019 15:33:15 +0800 Subject: [PATCH 45/60] Update barrierOffsetGenerator, remove absoluteBarrierGenerator --- .../view/blockly/blocks/trade/components.js | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/components.js b/src/botPage/view/blockly/blocks/trade/components.js index 98a3bdd615..a11467343e 100644 --- a/src/botPage/view/blockly/blocks/trade/components.js +++ b/src/botPage/view/blockly/blocks/trade/components.js @@ -74,14 +74,17 @@ export const barrierOffsetGenerator = (inputName, block) => { // Determine amount of barrierOffset-blocks on workspace const barrierNumber = block.inputList.filter(input => /BARRIEROFFSET$/.test(input.name)).length; - // Set barrier options according to barrierNumber (i.e. + and -) + // Set barrier options according to barrierNumber (i.e. Offset + and Offset -) const barrierOffsetList = new Blockly.FieldDropdown(config.barrierTypes); + barrierOffsetList.prefixField = null; + barrierOffsetList.menuGenerator_ = config.barrierTypes; // eslint-disable-line no-underscore-dangle + barrierOffsetList.setValue(''); barrierOffsetList.setValue(config.barrierTypes[barrierNumber % config.barrierTypes.length][1]); block .appendValueInput(inputName) .setCheck('Number') - .appendField(`${translate('Barrier Offset')} ${barrierNumber + 1}:`) + .appendField(`${translate('Barrier')} ${barrierNumber + 1}:`) .appendField(barrierOffsetList, `${inputName}TYPE_LIST`); const input = block.getInput(inputName); @@ -89,21 +92,6 @@ export const barrierOffsetGenerator = (inputName, block) => { } }; -export const absoluteBarrierGenerator = (inputName, block) => { - if (!block.getInput(inputName)) { - const barrierNumber = block.inputList.filter(input => /ABSOLUTEBARRIER$/.test(input.name)).length; - - block - .appendValueInput(inputName) - .setCheck('Number') - // Label is a fallback value, proper labels are set in tradeOptions - .appendField(`${translate('Barrier')} ${barrierNumber + 1}:`); - - const input = block.getInput(inputName); - input.setVisible(false); - } -}; - export const prediction = block => { const inputName = 'PREDICTION'; if (!block.getInput(inputName)) { From 1ed1905545b95978bd3a06f868bace7cc0ccb0f8 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Thu, 28 Feb 2019 15:34:25 +0800 Subject: [PATCH 46/60] Refactor barrier logic to accomodate absolute barriers --- .../view/blockly/blocks/trade/tradeOptions.js | 83 ++++++++----------- 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index c4b6b298cb..a690e3bbf6 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -147,7 +147,7 @@ export default () => { }); }); }, - updateBarrierOffsetBlocks(contracts, useDefault = false, updateOnly = []) { + updateBarrierOffsetBlocks(contracts, useDefaultType = false, updateOnly = []) { getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => { if (tradeOptionsBlock.disabled) return; if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return; @@ -157,61 +157,58 @@ export default () => { const barriers = getBarriersForContracts(contracts, tradeType, selectedDuration); hideInteractionsFromBlockly(() => { - const revealBarrierBlock = (barrier, inputName) => { + const revealBarrierBlock = (barrierValue, inputName) => { const barrierInput = tradeOptionsBlock.getInput(inputName); if (barrierInput) { barrierInput.setVisible(true); // Attach shadow block with API-returned barrier-value (only if user hasn't defined a value) if (!barrierInput.connection.isConnected()) { - barrierInput.attachShadowBlock(barriers[barrier], 'NUM', 'math_number'); - } else if (useDefault) { + barrierInput.attachShadowBlock(barrierValue, 'NUM', 'math_number'); + } else if (useDefaultType) { const connectedBlock = barrierInput.connection.targetBlock(); if (connectedBlock.isShadow()) { - connectedBlock.setFieldValue(barriers[barrier], 'NUM'); + connectedBlock.setFieldValue(barrierValue, 'NUM'); } } } }; - const absoluteBarrierNames = ['ABSOLUTEBARRIER', 'SECONDABSOLUTEBARRIER']; const barrierOffsetNames = ['BARRIEROFFSET', 'SECONDBARRIEROFFSET']; + const removeInput = inputName => tradeOptionsBlock.removeInput(inputName); + const updateList = (list, options, selected = null) => { + list.menuGenerator_ = options; // eslint-disable-line no-underscore-dangle, no-param-reassign + list.setValue(''); + list.setValue(selected || list.menuGenerator_[0][1]); // eslint-disable-line no-underscore-dangle + }; - const barrierKeys = Object.keys(barriers); - if (!barrierKeys.length) { - [...absoluteBarrierNames, ...barrierOffsetNames].forEach(barrierInputName => - tradeOptionsBlock.removeInput(barrierInputName) - ); + if (!barriers.values.length) { + barrierOffsetNames.forEach(removeInput); return; } - // Draw absolute barriers for day-durations - if (selectedDuration === 'd') { - if (barrierKeys.length === 1) { - revealBarrierBlock(barrierKeys[0], absoluteBarrierNames[0]); - // Update label to show 'Barrier' only - const input = tradeOptionsBlock.getInput(absoluteBarrierNames[0]); - input.fieldRow[0].setText(`${translate('Barrier')}:`); - } else { - barrierKeys.forEach((barrier, index) => - revealBarrierBlock(barrier, absoluteBarrierNames[index]) - ); - // Update labels to show 'High barrier' + 'Low barrier' - absoluteBarrierNames.forEach((inputName, index) => { - const input = tradeOptionsBlock.getInput(inputName); - input.fieldRow[0].setText(`${config.absoluteBarrierLabels[index]}:`); - }); - } - [...barrierOffsetNames, ...absoluteBarrierNames.slice(barrierKeys.length)].forEach(inputName => - tradeOptionsBlock.removeInput(inputName) - ); + if (barriers.allowBothTypes || selectedDuration === 'd') { + barriers.values.forEach((barrierValue, index) => { + const absoluteType = [[translate('Absolute'), 'absolute']]; + const typeList = tradeOptionsBlock.getField(`${barrierOffsetNames[index]}TYPE_LIST`); + const selectedType = typeList.getValue(); + + if (selectedDuration === 'd') { + updateList(typeList, absoluteType, 'absolute'); + } else { + updateList(typeList, config.barrierTypes.concat(absoluteType), selectedType); + } + revealBarrierBlock(barrierValue, barrierOffsetNames[index]); + }); } else { - // Draw barrier offsets for all other durations - barrierKeys.forEach((barrier, index) => revealBarrierBlock(barrier, barrierOffsetNames[index])); - [...absoluteBarrierNames, ...barrierOffsetNames.slice(barrierKeys.length)].forEach(inputName => - tradeOptionsBlock.removeInput(inputName) - ); + barriers.values.forEach((barrierValue, index) => { + const typeList = tradeOptionsBlock.getField(`${barrierOffsetNames[index]}TYPE_LIST`); + const selectedType = config.barrierTypes[index][1]; + updateList(typeList, config.barrierTypes, selectedType); + revealBarrierBlock(barrierValue, barrierOffsetNames[index]); + }); } + barrierOffsetNames.slice(barriers.values.length).forEach(removeInput); }); }); }, @@ -279,29 +276,21 @@ export default () => { const isVisibleField = field => block.getInput(field) && block.getInput(field).isVisible(); let predictionValue = 'undefined'; - let absoluteBarrierValue = 'undefined'; - let secondAbsoluteBarrierValue = 'undefined'; let barrierOffsetValue = 'undefined'; let secondBarrierOffsetValue = 'undefined'; if (isVisibleField('PREDICTION')) { predictionValue = expectValue(block, 'PREDICTION'); } - if (isVisibleField('ABSOLUTEBARRIER')) { - absoluteBarrierValue = expectValue(block, 'ABSOLUTEBARRIER'); - } - if (isVisibleField('SECONDABSOLUTEBARRIER')) { - secondAbsoluteBarrierValue = expectValue(block, 'SECONDABSOLUTEBARRIER'); - } if (isVisibleField('BARRIEROFFSET')) { const barrierOffsetType = block.getFieldValue('BARRIEROFFSETTYPE_LIST'); const value = expectValue(block, 'BARRIEROFFSET'); - barrierOffsetValue = `'${barrierOffsetType}${value}'`; + barrierOffsetValue = barrierOffsetType === 'absolute' ? `${value}` : `'${barrierOffsetType}${value}'`; } if (isVisibleField('SECONDBARRIEROFFSET')) { const barrierOffsetType = block.getFieldValue('SECONDBARRIEROFFSETTYPE_LIST'); const value = expectValue(block, 'SECONDBARRIEROFFSET'); - secondBarrierOffsetValue = `'${barrierOffsetType}${value}'`; + secondBarrierOffsetValue = barrierOffsetType === 'absolute' ? `${value}` : `'${barrierOffsetType}${value}'`; } const code = ` @@ -312,8 +301,6 @@ export default () => { currency: '${currency}', amount: ${amount}, prediction: ${predictionValue}, - absoluteBarrier: ${absoluteBarrierValue}, - secondAbsoluteBarrier: ${secondAbsoluteBarrierValue}, barrierOffset: ${barrierOffsetValue}, secondBarrierOffset: ${secondBarrierOffsetValue}, }); From c7ccae3826b9c7265e6e9d5cfefbe514e279b307 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Fri, 1 Mar 2019 11:09:52 +0800 Subject: [PATCH 47/60] Do not pass barrier returned by 'reset' barrier_category --- src/botPage/view/blockly/blocks/shared.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index fa91c0f98b..4661ec76fb 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -336,7 +336,7 @@ export const getBarriersForContracts = (contracts, selectedContractType, selecte }) .shift(); } - if (contract) { + if (contract && !['reset'].includes(contract.barrier_category)) { const offsetRegex = new RegExp('^[-|+]([0-9]+.[0-9]+)$'); if (contract.barriers === 2 && contract.high_barrier && contract.low_barrier) { const highBarrierOffsetMatch = contract.high_barrier.toString().match(offsetRegex); From f8cf4107c68524c6bd8d1aecd6bbd680df1d7e4a Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Fri, 1 Mar 2019 11:13:43 +0800 Subject: [PATCH 48/60] Change barrier labels based on barrier number + type, add handlebars --- .../view/blockly/blocks/trade/tradeOptions.js | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index a690e3bbf6..484b255df7 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -29,7 +29,7 @@ export default () => { // After creation emit change event so API-dependent fields become populated updateInputList(this); const block = Blockly.mainWorkspace.getBlockById(ev.blockId); - if (ev.workspaceId === Blockly.mainWorkspace.id && block.type === 'trade') { + if (block && block.type === 'trade' && ev.workspaceId === Blockly.mainWorkspace.id) { const symbol = block.getFieldValue('SYMBOL_LIST'); if (!symbol) return; @@ -85,6 +85,25 @@ export default () => { this.updateBarrierOffsetBlocks(contracts, true, [ev.blockId]); // Called to set min durations for selected unit this.updateDurationLists(contracts, false, true, [ev.blockId]); + } else if ( + ['BARRIEROFFSETTYPE_LIST', 'SECONDBARRIEROFFSETTYPE_LIST'].includes(ev.name) && + ev.oldValue !== ev.newValue + ) { + const otherBarrierListName = () => { + if (ev.name === 'BARRIEROFFSETTYPE_LIST') { + return 'SECONDBARRIEROFFSETTYPE_LIST'; + } + return 'BARRIEROFFSETTYPE_LIST'; + }; + const otherBarrierList = this.getInput(otherBarrierListName()); + if (otherBarrierList) { + if (ev.newValue === 'absolute') { + otherBarrierList.setValue(ev.newValue); + } else { + const value = config.barrierTypes.find(type => type[1] !== ev.newValue); + otherBarrierList.setValue(value[1]); + } + } } updateInputList(this); }); @@ -187,27 +206,37 @@ export default () => { return; } - if (barriers.allowBothTypes || selectedDuration === 'd') { - barriers.values.forEach((barrierValue, index) => { + barriers.values.forEach((barrierValue, index) => { + const typeList = tradeOptionsBlock.getField(`${barrierOffsetNames[index]}TYPE_LIST`); + const typeInput = tradeOptionsBlock.getInput(barrierOffsetNames[index]); + const absoluteLabels = [translate('High barrier'), translate('Low barrier')]; + + if (barriers.allowBothTypes || selectedDuration === 'd') { const absoluteType = [[translate('Absolute'), 'absolute']]; const typeList = tradeOptionsBlock.getField(`${barrierOffsetNames[index]}TYPE_LIST`); - const selectedType = typeList.getValue(); if (selectedDuration === 'd') { updateList(typeList, absoluteType, 'absolute'); + if (barriers.values.length === 2) { + typeInput.fieldRow[0].setText(`${absoluteLabels[index]}:`); + } else { + typeInput.fieldRow[0].setText(`${translate('Barrier')}:`); + } } else { - updateList(typeList, config.barrierTypes.concat(absoluteType), selectedType); + updateList( + typeList, + config.barrierTypes.concat(absoluteType), + config.barrierTypes[index][1] + ); + typeInput.fieldRow[0].setText(`${translate('Barrier')} ${index + 1}:`); } revealBarrierBlock(barrierValue, barrierOffsetNames[index]); - }); - } else { - barriers.values.forEach((barrierValue, index) => { - const typeList = tradeOptionsBlock.getField(`${barrierOffsetNames[index]}TYPE_LIST`); - const selectedType = config.barrierTypes[index][1]; - updateList(typeList, config.barrierTypes, selectedType); + } else { + updateList(typeList, config.barrierTypes, config.barrierTypes[index][1]); + typeInput.fieldRow[0].setText(`${translate('Barrier')} ${index + 1}:`); revealBarrierBlock(barrierValue, barrierOffsetNames[index]); - }); - } + } + }); barrierOffsetNames.slice(barriers.values.length).forEach(removeInput); }); }); From b5421357f971de03711bda814975564691731c7d Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Fri, 1 Mar 2019 11:41:40 +0800 Subject: [PATCH 49/60] Remove duplicate declaration --- src/botPage/view/blockly/blocks/trade/tradeOptions.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index 484b255df7..6dae280ddf 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -213,7 +213,6 @@ export default () => { if (barriers.allowBothTypes || selectedDuration === 'd') { const absoluteType = [[translate('Absolute'), 'absolute']]; - const typeList = tradeOptionsBlock.getField(`${barrierOffsetNames[index]}TYPE_LIST`); if (selectedDuration === 'd') { updateList(typeList, absoluteType, 'absolute'); From 092f1fedf9f5febb0a0775bd5946ab68eab328cb Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Fri, 1 Mar 2019 12:03:23 +0800 Subject: [PATCH 50/60] Allow same offset types to be set, but don't allow both offset/absolute combo --- .../view/blockly/blocks/trade/tradeOptions.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index 6dae280ddf..3cfd12ed2d 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -95,11 +95,14 @@ export default () => { } return 'BARRIEROFFSETTYPE_LIST'; }; - const otherBarrierList = this.getInput(otherBarrierListName()); + const otherBarrierList = this.getField(otherBarrierListName()); if (otherBarrierList) { if (ev.newValue === 'absolute') { otherBarrierList.setValue(ev.newValue); - } else { + } else if ( + config.barrierTypes.findIndex(type => type[1] === otherBarrierList.getValue()) === + -1 + ) { const value = config.barrierTypes.find(type => type[1] !== ev.newValue); otherBarrierList.setValue(value[1]); } @@ -194,6 +197,7 @@ export default () => { }; const barrierOffsetNames = ['BARRIEROFFSET', 'SECONDBARRIEROFFSET']; + const barrierLabels = [translate('High barrier'), translate('Low barrier')]; const removeInput = inputName => tradeOptionsBlock.removeInput(inputName); const updateList = (list, options, selected = null) => { list.menuGenerator_ = options; // eslint-disable-line no-underscore-dangle, no-param-reassign @@ -209,32 +213,28 @@ export default () => { barriers.values.forEach((barrierValue, index) => { const typeList = tradeOptionsBlock.getField(`${barrierOffsetNames[index]}TYPE_LIST`); const typeInput = tradeOptionsBlock.getInput(barrierOffsetNames[index]); - const absoluteLabels = [translate('High barrier'), translate('Low barrier')]; if (barriers.allowBothTypes || selectedDuration === 'd') { const absoluteType = [[translate('Absolute'), 'absolute']]; if (selectedDuration === 'd') { updateList(typeList, absoluteType, 'absolute'); - if (barriers.values.length === 2) { - typeInput.fieldRow[0].setText(`${absoluteLabels[index]}:`); - } else { - typeInput.fieldRow[0].setText(`${translate('Barrier')}:`); - } } else { updateList( typeList, config.barrierTypes.concat(absoluteType), config.barrierTypes[index][1] ); - typeInput.fieldRow[0].setText(`${translate('Barrier')} ${index + 1}:`); } - revealBarrierBlock(barrierValue, barrierOffsetNames[index]); } else { updateList(typeList, config.barrierTypes, config.barrierTypes[index][1]); - typeInput.fieldRow[0].setText(`${translate('Barrier')} ${index + 1}:`); - revealBarrierBlock(barrierValue, barrierOffsetNames[index]); } + if (barriers.values.length === 1) { + typeInput.fieldRow[0].setText(`${translate('Barrier')}:`); + } else { + typeInput.fieldRow[0].setText(`${barrierLabels[index]}:`); + } + revealBarrierBlock(barrierValue, barrierOffsetNames[index]); }); barrierOffsetNames.slice(barriers.values.length).forEach(removeInput); }); From fa92ffb19221c2ed96cee96694c855f61b093094 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Mon, 4 Mar 2019 15:20:52 +0800 Subject: [PATCH 51/60] Refactor logic for getting barrier values --- src/botPage/view/blockly/blocks/shared.js | 97 +++++++++++++---------- 1 file changed, 55 insertions(+), 42 deletions(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index 4661ec76fb..f3f11905b7 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -313,57 +313,70 @@ export const getContractsAvailableForSymbolFromApi = async underlyingSymbol => { return contractsForSymbol; }; -export const getBarriersForContracts = (contracts, selectedContractType, selectedDuration) => { +export const getBarriersForContracts = (contracts, selectedContractType, selectedDuration, selectedBarrierTypes) => { + const barriers = { values: [] }; const category = getContractCategory(selectedContractType); const contractsForContractCategory = filterContractsByCategory(contracts, category, selectedContractType); - const barriers = { values: [] }; + + const offsetRegex = new RegExp('^[-|+]([0-9]+.[0-9]+)$'); + const isOffset = input => input && offsetRegex.test(input.toString()); + if (contractsForContractCategory) { - let contract; - if (selectedDuration) { - // Find barriers based on selected duration (e.g. Hours & days can have different barrier values) + const barrierProps = ['high_barrier', 'low_barrier']; + + selectedBarrierTypes.forEach((barrierType, index) => { + const selectedOffset = ['+', '-'].includes(barrierType); + + // Find barriers based on selected duration & by selected barrier type + // i.e. Hours & days can have different barrier values, offset + absolute sometimes have different values + let contract; contract = contractsForContractCategory.find(c => { const durations = getDurationsForContracts([c], selectedContractType); - return durations.map(duration => duration.unit).includes(selectedDuration); + if (durations.map(duration => duration.unit).includes(selectedDuration)) { + const barrierIsOffset = () => isOffset(c.barrier || c[barrierProps[index]]); + return (selectedOffset && barrierIsOffset()) || (!selectedOffset && !barrierIsOffset()); + } + return false; }); - } - if (!contract) { - // Default to smallest barriers available - contract = contractsForContractCategory - .sort((a, b) => { - const barrierValueA = a.barrier || a.high_barrier; - const barrierValueB = b.barrier || b.high_barrier; - return parseFloat(barrierValueA) - parseFloat(barrierValueB); - }) - .shift(); - } - if (contract && !['reset'].includes(contract.barrier_category)) { - const offsetRegex = new RegExp('^[-|+]([0-9]+.[0-9]+)$'); - if (contract.barriers === 2 && contract.high_barrier && contract.low_barrier) { - const highBarrierOffsetMatch = contract.high_barrier.toString().match(offsetRegex); - const lowBarrierOffsetMatch = contract.low_barrier.toString().match(offsetRegex); - - if (highBarrierOffsetMatch && lowBarrierOffsetMatch) { - // eslint-disable-next-line prefer-destructuring - barriers.values.push(...[highBarrierOffsetMatch[1], lowBarrierOffsetMatch[1]]); - } else { - barriers.values.push(...[contract.high_barrier, contract.low_barrier]); + if (!contract) { + contract = contractsForContractCategory.find(c => barrierType === 'absolute' && !isOffset(c.barrier || c.high_barrier)); + } + // Fallback to contract with smallest barriers + if (!contract) { + contract = contractsForContractCategory + .sort((a, b) => { + const c = a.barrier || a.high_barrier; + const d = b.barrier || b.high_barrier; + return parseFloat(c) - parseFloat(d); + }) + .shift(); + } + const barrierlessCategories = ['reset']; + if (contract && !barrierlessCategories.includes(contract.barrier_category)) { + const propName = contract.barriers === 1 ? 'barrier' : barrierProps[index]; + if (contract[propName]) { + const barrierMatch = contract[propName].toString().match(offsetRegex); + barriers.values[index] = barrierMatch ? barrierMatch[1] : contract[propName]; } - } else if (contract.barriers === 1 && contract.barrier) { - const barrierOffsetMatch = contract.barrier.toString().match(offsetRegex); - if (barrierOffsetMatch) { - // eslint-disable-next-line prefer-destructuring - barriers.values.push(barrierOffsetMatch[1]); - } else { - barriers.values.push(contract.barrier); + + if (['intraday', 'tick'].includes(contract.expiry_type) && isOffset(contract[propName])) { + barriers.allowBothTypes = true; // Allow both offset + absolute barriers + } else if (barrierType === 'absolute' && !isOffset(contract[propName])) { + barriers.allowAbsoluteType = true; + } + + if (contract.barriers === 1) { + selectedBarrierTypes.splice(index + 1, 1); } } - const hasOffset = () => - [contract.barrier, contract.high_barrier, contract.low_barrier].some( - input => input && offsetRegex.test(input.toString()) - ); - if (['intraday', 'tick'].includes(contract.expiry_type) && hasOffset()) { - barriers.allowBothTypes = true; // Allow both offset + absolute barriers - } + }); + if ( + barriers.values.length === 2 && + selectedBarrierTypes.every(val => val === selectedBarrierTypes[0]) && + barriers.values.every(val => val === barriers.values[0]) + ) { + // Set distinct values if equal barrier types have equal values + barriers.values[1] = (barriers.values[0] * 0.95).toFixed(1); } } return barriers; From 95810ce75169fb9e37690b7584c93795015c828a Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Mon, 4 Mar 2019 15:23:19 +0800 Subject: [PATCH 52/60] Move handlebar logic to its own function, refactor barrier logic --- .../view/blockly/blocks/trade/tradeOptions.js | 103 ++++++++++-------- 1 file changed, 60 insertions(+), 43 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index 3cfd12ed2d..341bba878a 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -34,7 +34,7 @@ export default () => { if (!symbol) return; this.pollForContracts(symbol).then(contracts => { - this.updateBarrierOffsetBlocks(contracts); + this.updateBarrierOffsetBlocks(contracts, false, false); this.updatePredictionBlocks(contracts); this.updateDurationLists(contracts, false, false); // false because we want to maintain user's values on import }); @@ -52,7 +52,8 @@ export default () => { const getNestedTradeOptions = block => { if (block.type === 'tradeOptions') { this.pollForContracts(symbol).then(contracts => { - this.updateBarrierOffsetBlocks(contracts, true, [block.id]); + this.updateBarrierOffsetBlocks(contracts, true, true, [block.id]); + this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', [ev.blockId], true); this.updatePredictionBlocks(contracts, [block.id]); this.updateDurationLists(contracts, true, true, [block.id]); }); @@ -77,36 +78,23 @@ export default () => { } else if (['TRADETYPE_LIST'].includes(ev.name) && ev.oldValue !== ev.newValue) { // Both are called to check if these blocks are required this.updatePredictionBlocks(contracts); - this.updateBarrierOffsetBlocks(contracts, true); + this.updateBarrierOffsetBlocks(contracts, false, true); // Called to default to smallest durations for symbol this.updateDurationLists(contracts, false, true); } else if (ev.name === 'DURATIONTYPE_LIST' && ev.oldValue !== ev.newValue) { // Called to set barriers based on duration - this.updateBarrierOffsetBlocks(contracts, true, [ev.blockId]); + this.updateBarrierOffsetBlocks(contracts, true, true, [ev.blockId]); + this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', [ev.blockId], true); // Called to set min durations for selected unit this.updateDurationLists(contracts, false, true, [ev.blockId]); } else if ( ['BARRIEROFFSETTYPE_LIST', 'SECONDBARRIEROFFSETTYPE_LIST'].includes(ev.name) && ev.oldValue !== ev.newValue ) { - const otherBarrierListName = () => { - if (ev.name === 'BARRIEROFFSETTYPE_LIST') { - return 'SECONDBARRIEROFFSETTYPE_LIST'; - } - return 'BARRIEROFFSETTYPE_LIST'; - }; - const otherBarrierList = this.getField(otherBarrierListName()); - if (otherBarrierList) { - if (ev.newValue === 'absolute') { - otherBarrierList.setValue(ev.newValue); - } else if ( - config.barrierTypes.findIndex(type => type[1] === otherBarrierList.getValue()) === - -1 - ) { - const value = config.barrierTypes.find(type => type[1] !== ev.newValue); - otherBarrierList.setValue(value[1]); - } - } + // Called to set default values based on barrier type + this.updateBarrierOffsetBlocks(contracts, false, true, [ev.blockId]); + // Suggests same type barriers + this.applyBarrierHandlebars(ev.name, [ev.blockId], false); } updateInputList(this); }); @@ -169,25 +157,27 @@ export default () => { }); }); }, - updateBarrierOffsetBlocks(contracts, useDefaultType = false, updateOnly = []) { + updateBarrierOffsetBlocks(contracts, useDefaultType = false, setDefaultValue = false, updateOnly = []) { getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => { if (tradeOptionsBlock.disabled) return; if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return; const tradeType = getParentValue(tradeOptionsBlock, 'TRADETYPE_LIST'); const selectedDuration = tradeOptionsBlock.getFieldValue('DURATIONTYPE_LIST'); - const barriers = getBarriersForContracts(contracts, tradeType, selectedDuration); + const selectedBarrierTypes = [ + tradeOptionsBlock.getFieldValue('BARRIEROFFSETTYPE_LIST'), + tradeOptionsBlock.getFieldValue('SECONDBARRIEROFFSETTYPE_LIST'), + ]; + const barriers = getBarriersForContracts(contracts, tradeType, selectedDuration, selectedBarrierTypes); hideInteractionsFromBlockly(() => { const revealBarrierBlock = (barrierValue, inputName) => { const barrierInput = tradeOptionsBlock.getInput(inputName); if (barrierInput) { barrierInput.setVisible(true); - - // Attach shadow block with API-returned barrier-value (only if user hasn't defined a value) if (!barrierInput.connection.isConnected()) { barrierInput.attachShadowBlock(barrierValue, 'NUM', 'math_number'); - } else if (useDefaultType) { + } else if (setDefaultValue) { const connectedBlock = barrierInput.connection.targetBlock(); if (connectedBlock.isShadow()) { connectedBlock.setFieldValue(barrierValue, 'NUM'); @@ -199,10 +189,18 @@ export default () => { const barrierOffsetNames = ['BARRIEROFFSET', 'SECONDBARRIEROFFSET']; const barrierLabels = [translate('High barrier'), translate('Low barrier')]; const removeInput = inputName => tradeOptionsBlock.removeInput(inputName); - const updateList = (list, options, selected = null) => { + + const updateList = (list, options) => { + const prevSelectedType = options.find(option => option[1] === list.getValue()); list.menuGenerator_ = options; // eslint-disable-line no-underscore-dangle, no-param-reassign - list.setValue(''); - list.setValue(selected || list.menuGenerator_[0][1]); // eslint-disable-line no-underscore-dangle + if (!useDefaultType && prevSelectedType) { + list.setValue(''); + list.setValue(prevSelectedType[1]); + // eslint-disable-next-line no-underscore-dangle + } else if (list.menuGenerator_.length) { + list.setValue(''); + list.setValue(list.menuGenerator_[0][1]); // eslint-disable-line no-underscore-dangle + } }; if (!barriers.values.length) { @@ -213,22 +211,16 @@ export default () => { barriers.values.forEach((barrierValue, index) => { const typeList = tradeOptionsBlock.getField(`${barrierOffsetNames[index]}TYPE_LIST`); const typeInput = tradeOptionsBlock.getInput(barrierOffsetNames[index]); + const absoluteType = [[translate('Absolute'), 'absolute']]; - if (barriers.allowBothTypes || selectedDuration === 'd') { - const absoluteType = [[translate('Absolute'), 'absolute']]; - - if (selectedDuration === 'd') { - updateList(typeList, absoluteType, 'absolute'); - } else { - updateList( - typeList, - config.barrierTypes.concat(absoluteType), - config.barrierTypes[index][1] - ); - } + if (selectedDuration === 'd') { + updateList(typeList, absoluteType); + } else if (barriers.allowBothTypes || barriers.allowAbsoluteType) { + updateList(typeList, [...config.barrierTypes, ...absoluteType]); } else { - updateList(typeList, config.barrierTypes, config.barrierTypes[index][1]); + updateList(typeList, config.barrierTypes); } + if (barriers.values.length === 1) { typeInput.fieldRow[0].setText(`${translate('Barrier')}:`); } else { @@ -289,6 +281,31 @@ export default () => { }); }); }, + applyBarrierHandlebars(barrierFieldName, blockId, forceDistinct = false) { + const block = Blockly.mainWorkspace.getBlockById(blockId); + const newValue = block.getFieldValue(barrierFieldName); + + const otherBarrierListName = () => { + if (barrierFieldName === 'BARRIEROFFSETTYPE_LIST') { + return 'SECONDBARRIEROFFSETTYPE_LIST'; + } + return 'BARRIEROFFSETTYPE_LIST'; + }; + + const otherBarrierList = block.getField(otherBarrierListName()); + if (otherBarrierList) { + const otherBarrierType = otherBarrierList.getValue(); + if ( + config.barrierTypes.findIndex(t => t[1] === newValue) !== -1 && + (otherBarrierType === 'absolute' || forceDistinct) + ) { + const otherValue = config.barrierTypes.find(t => t[1] !== newValue); + otherBarrierList.setValue(otherValue[1]); + } else if (newValue === 'absolute' && otherBarrierType !== 'absolute') { + otherBarrierList.setValue('absolute'); + } + } + }, }; Blockly.JavaScript.tradeOptions = block => { const tradeDefBlock = findTopParentBlock(block); From c81578ccf5d42c776df129d410e167dbdfd2ccda Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Mon, 4 Mar 2019 17:27:02 +0800 Subject: [PATCH 53/60] Maintain values on moving tradeOptions back into trade if possible --- src/botPage/view/blockly/blocks/trade/tradeOptions.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index 341bba878a..a1fb4be221 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -26,7 +26,6 @@ export default () => { return; } if (ev.type === Blockly.Events.CREATE) { - // After creation emit change event so API-dependent fields become populated updateInputList(this); const block = Blockly.mainWorkspace.getBlockById(ev.blockId); if (block && block.type === 'trade' && ev.workspaceId === Blockly.mainWorkspace.id) { @@ -34,9 +33,9 @@ export default () => { if (!symbol) return; this.pollForContracts(symbol).then(contracts => { - this.updateBarrierOffsetBlocks(contracts, false, false); + this.updateBarrierOffsetBlocks(contracts, false, false); // false false to maintain user's values on import this.updatePredictionBlocks(contracts); - this.updateDurationLists(contracts, false, false); // false because we want to maintain user's values on import + this.updateDurationLists(contracts, false, false); // false false to maintain user's values on import }); } } else if (ev.type === Blockly.Events.MOVE) { @@ -52,10 +51,10 @@ export default () => { const getNestedTradeOptions = block => { if (block.type === 'tradeOptions') { this.pollForContracts(symbol).then(contracts => { - this.updateBarrierOffsetBlocks(contracts, true, true, [block.id]); + this.updateBarrierOffsetBlocks(contracts, false, false, [block.id]); this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', [ev.blockId], true); this.updatePredictionBlocks(contracts, [block.id]); - this.updateDurationLists(contracts, true, true, [block.id]); + this.updateDurationLists(contracts, false, false, [block.id]); }); } block.getChildren().forEach(childBlock => { From 984e4a3aeb8bd0d5863a88471a8a766df737d60c Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Mon, 4 Mar 2019 17:56:18 +0800 Subject: [PATCH 54/60] Check barrier types on tradeType change --- src/botPage/view/blockly/blocks/trade/tradeOptions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index a1fb4be221..f786657ec5 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -78,6 +78,7 @@ export default () => { // Both are called to check if these blocks are required this.updatePredictionBlocks(contracts); this.updateBarrierOffsetBlocks(contracts, false, true); + this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', ev.blockId, true); // Called to default to smallest durations for symbol this.updateDurationLists(contracts, false, true); } else if (ev.name === 'DURATIONTYPE_LIST' && ev.oldValue !== ev.newValue) { From ebbc45752b54124f0281b0460fe2506414f23f24 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Mon, 4 Mar 2019 18:07:22 +0800 Subject: [PATCH 55/60] Fixes --- src/botPage/view/blockly/blocks/trade/tradeOptions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index f786657ec5..d4a97ad82d 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -77,8 +77,8 @@ export default () => { } else if (['TRADETYPE_LIST'].includes(ev.name) && ev.oldValue !== ev.newValue) { // Both are called to check if these blocks are required this.updatePredictionBlocks(contracts); - this.updateBarrierOffsetBlocks(contracts, false, true); - this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', ev.blockId, true); + this.updateBarrierOffsetBlocks(contracts, true, true); + this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', [ev.blockId], true); // Called to default to smallest durations for symbol this.updateDurationLists(contracts, false, true); } else if (ev.name === 'DURATIONTYPE_LIST' && ev.oldValue !== ev.newValue) { From 01827d471760b565760d696bd0bf98b793620434 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Tue, 5 Mar 2019 11:41:15 +0800 Subject: [PATCH 56/60] Support barrier handlebars for multiple and individual tradeOptions blocks --- .../view/blockly/blocks/trade/tradeOptions.js | 52 ++++++++++--------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index d4a97ad82d..415b3ec114 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -78,13 +78,14 @@ export default () => { // Both are called to check if these blocks are required this.updatePredictionBlocks(contracts); this.updateBarrierOffsetBlocks(contracts, true, true); - this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', [ev.blockId], true); + // Handlebars for all tradeOptions barrier-blocks + this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', true); // Called to default to smallest durations for symbol this.updateDurationLists(contracts, false, true); } else if (ev.name === 'DURATIONTYPE_LIST' && ev.oldValue !== ev.newValue) { // Called to set barriers based on duration this.updateBarrierOffsetBlocks(contracts, true, true, [ev.blockId]); - this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', [ev.blockId], true); + this.applyBarrierHandlebars('BARRIEROFFSETTYPE_LIST', true, [ev.blockId]); // Called to set min durations for selected unit this.updateDurationLists(contracts, false, true, [ev.blockId]); } else if ( @@ -94,7 +95,7 @@ export default () => { // Called to set default values based on barrier type this.updateBarrierOffsetBlocks(contracts, false, true, [ev.blockId]); // Suggests same type barriers - this.applyBarrierHandlebars(ev.name, [ev.blockId], false); + this.applyBarrierHandlebars(ev.name, false, [ev.blockId]); } updateInputList(this); }); @@ -281,30 +282,33 @@ export default () => { }); }); }, - applyBarrierHandlebars(barrierFieldName, blockId, forceDistinct = false) { - const block = Blockly.mainWorkspace.getBlockById(blockId); - const newValue = block.getFieldValue(barrierFieldName); + applyBarrierHandlebars(barrierFieldName, forceDistinct = false, updateOnly = []) { + getBlocksByType('tradeOptions').forEach(tradeOptionsBlock => { + if (tradeOptionsBlock.disabled) return; + if (updateOnly.length && !updateOnly.includes(tradeOptionsBlock.id)) return; - const otherBarrierListName = () => { - if (barrierFieldName === 'BARRIEROFFSETTYPE_LIST') { - return 'SECONDBARRIEROFFSETTYPE_LIST'; - } - return 'BARRIEROFFSETTYPE_LIST'; - }; + const newValue = tradeOptionsBlock.getFieldValue(barrierFieldName); + const otherBarrierListName = () => { + if (barrierFieldName === 'BARRIEROFFSETTYPE_LIST') { + return 'SECONDBARRIEROFFSETTYPE_LIST'; + } + return 'BARRIEROFFSETTYPE_LIST'; + }; - const otherBarrierList = block.getField(otherBarrierListName()); - if (otherBarrierList) { - const otherBarrierType = otherBarrierList.getValue(); - if ( - config.barrierTypes.findIndex(t => t[1] === newValue) !== -1 && - (otherBarrierType === 'absolute' || forceDistinct) - ) { - const otherValue = config.barrierTypes.find(t => t[1] !== newValue); - otherBarrierList.setValue(otherValue[1]); - } else if (newValue === 'absolute' && otherBarrierType !== 'absolute') { - otherBarrierList.setValue('absolute'); + const otherBarrierList = tradeOptionsBlock.getField(otherBarrierListName()); + if (otherBarrierList) { + const otherBarrierType = otherBarrierList.getValue(); + if ( + config.barrierTypes.findIndex(t => t[1] === newValue) !== -1 && + (otherBarrierType === 'absolute' || forceDistinct) + ) { + const otherValue = config.barrierTypes.find(t => t[1] !== newValue); + otherBarrierList.setValue(otherValue[1]); + } else if (newValue === 'absolute' && otherBarrierType !== 'absolute') { + otherBarrierList.setValue('absolute'); + } } - } + }); }, }; Blockly.JavaScript.tradeOptions = block => { From ebedb9f28f57b0d968059e650ea92af07fc98268 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Tue, 5 Mar 2019 15:25:33 +0800 Subject: [PATCH 57/60] Send absolute barrier as string --- src/botPage/view/blockly/blocks/trade/tradeOptions.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index 415b3ec114..a9a5f44157 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -334,12 +334,13 @@ export default () => { if (isVisibleField('BARRIEROFFSET')) { const barrierOffsetType = block.getFieldValue('BARRIEROFFSETTYPE_LIST'); const value = expectValue(block, 'BARRIEROFFSET'); - barrierOffsetValue = barrierOffsetType === 'absolute' ? `${value}` : `'${barrierOffsetType}${value}'`; + barrierOffsetValue = barrierOffsetType === 'absolute' ? `'${value}'` : `'${barrierOffsetType}${value}'`; } if (isVisibleField('SECONDBARRIEROFFSET')) { const barrierOffsetType = block.getFieldValue('SECONDBARRIEROFFSETTYPE_LIST'); const value = expectValue(block, 'SECONDBARRIEROFFSET'); - secondBarrierOffsetValue = barrierOffsetType === 'absolute' ? `${value}` : `'${barrierOffsetType}${value}'`; + secondBarrierOffsetValue = + barrierOffsetType === 'absolute' ? `'${value}'` : `'${barrierOffsetType}${value}'`; } const code = ` From 7f9fb4655e997b7a1aef8ba0bcb7c1e89ff0cccf Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 6 Mar 2019 13:38:46 +0800 Subject: [PATCH 58/60] Keep bot running on ContractValidationError --- src/botPage/bot/TradeEngine/Proposal.js | 48 ++++++++++++++++++------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/botPage/bot/TradeEngine/Proposal.js b/src/botPage/bot/TradeEngine/Proposal.js index 01334e4a4d..242d1f365a 100644 --- a/src/botPage/bot/TradeEngine/Proposal.js +++ b/src/botPage/bot/TradeEngine/Proposal.js @@ -21,7 +21,11 @@ export default Engine => this.data.get('proposals').forEach(proposal => { if (proposal.contractType === contractType) { - toBuy = proposal; + if (proposal.error) { + throw Error(proposal.error.error.error.message); + } else { + toBuy = proposal; + } } }); @@ -42,19 +46,34 @@ export default Engine => this.store.dispatch(clearProposals()); } requestProposals() { - Promise.all( - this.proposalTemplates.map(proposal => - doUntilDone(() => - this.api.subscribeToPriceForContractProposal({ + this.proposalTemplates.map(proposal => { + doUntilDone(() => { + this.api + .subscribeToPriceForContractProposal({ ...proposal, passthrough: { contractType: proposal.contract_type, uuid : getUUID(), }, }) - ) - ) - ).catch(e => this.$scope.observer.emit('Error', e)); + .catch(e => { + if (e.error.error.code === 'ContractBuyValidationError') { + const proposal = e.error.echo_req; + const passthrough = proposal.passthrough; + + if (!this.data.hasIn(['forgetProposals', passthrough.uuid])) { + this.data = this.data.setIn(['proposals', passthrough.uuid], { + ...proposal, + ...passthrough, + error: e, + }); + } + } else { + this.$scope.observer.emit('Error', e); + } + }); + }); + }); } observeProposals() { this.listen('proposal', r => { @@ -84,12 +103,15 @@ export default Engine => return Promise.all( proposals.map(proposal => { const { uuid: id } = proposal; + const removeProposal = uuid => { + this.data = this.data.deleteIn(['forgetProposals', uuid]); + }; - this.data = this.data.setIn(['forgetProposals', id], true); - - return doUntilDone(() => this.api.unsubscribeByID(proposal.id)).then(() => { - this.data = this.data.deleteIn(['forgetProposals', id]); - }); + if (proposal.error) { + removeProposal(id); + return Promise.resolve(); + } + return doUntilDone(() => this.api.unsubscribeByID(proposal.id)).then(() => removeProposal(id)); }) ); } From 73f84e8dfee5e07bd7e661a50a3d289ab4cc1c8d Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 6 Mar 2019 14:06:37 +0800 Subject: [PATCH 59/60] Fix eslint errors --- src/botPage/bot/TradeEngine/Proposal.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/botPage/bot/TradeEngine/Proposal.js b/src/botPage/bot/TradeEngine/Proposal.js index 242d1f365a..05a0fa257e 100644 --- a/src/botPage/bot/TradeEngine/Proposal.js +++ b/src/botPage/bot/TradeEngine/Proposal.js @@ -46,8 +46,8 @@ export default Engine => this.store.dispatch(clearProposals()); } requestProposals() { - this.proposalTemplates.map(proposal => { - doUntilDone(() => { + this.proposalTemplates.map(proposal => + doUntilDone(() => this.api .subscribeToPriceForContractProposal({ ...proposal, @@ -58,22 +58,21 @@ export default Engine => }) .catch(e => { if (e.error.error.code === 'ContractBuyValidationError') { - const proposal = e.error.echo_req; - const passthrough = proposal.passthrough; + const { uuid } = e.error.echo_req.passthrough; - if (!this.data.hasIn(['forgetProposals', passthrough.uuid])) { - this.data = this.data.setIn(['proposals', passthrough.uuid], { + if (!this.data.hasIn(['forgetProposals', uuid])) { + this.data = this.data.setIn(['proposals', uuid], { ...proposal, - ...passthrough, - error: e, + contractType: proposal.contract_type, + error : e, }); } } else { this.$scope.observer.emit('Error', e); } - }); - }); - }); + }) + ) + ); } observeProposals() { this.listen('proposal', r => { From cb821ba55c8bd5cff32513a06764b2df3e023df5 Mon Sep 17 00:00:00 2001 From: Aaron Imming Date: Wed, 6 Mar 2019 14:43:50 +0800 Subject: [PATCH 60/60] Check if contract is available --- src/botPage/view/blockly/blocks/shared.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/botPage/view/blockly/blocks/shared.js b/src/botPage/view/blockly/blocks/shared.js index f3f11905b7..a2ed49c817 100644 --- a/src/botPage/view/blockly/blocks/shared.js +++ b/src/botPage/view/blockly/blocks/shared.js @@ -339,7 +339,9 @@ export const getBarriersForContracts = (contracts, selectedContractType, selecte return false; }); if (!contract) { - contract = contractsForContractCategory.find(c => barrierType === 'absolute' && !isOffset(c.barrier || c.high_barrier)); + contract = contractsForContractCategory.find( + c => barrierType === 'absolute' && !isOffset(c.barrier || c.high_barrier) + ); } // Fallback to contract with smallest barriers if (!contract) { @@ -399,7 +401,7 @@ export const getPredictionForContracts = (contracts, selectedContractType) => { const contract = contractsForContractCategory.find(c => contractMapping[selectedContractType].includes(c.contract_type) ); - if (contract.last_digit_range) { + if (contract && contract.last_digit_range) { predictionRange.push(...contract.last_digit_range); } else { predictionRange.push(0);