From e7e34814049a954af83cf5db1127d0e742def50c Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Fri, 28 Jul 2023 14:04:41 +0300 Subject: [PATCH 01/47] Restore Recurring order on FE --- src/components/OrderForm/OrderForm.orders.helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/OrderForm/OrderForm.orders.helpers.js b/src/components/OrderForm/OrderForm.orders.helpers.js index 692549cd8..a36286743 100644 --- a/src/components/OrderForm/OrderForm.orders.helpers.js +++ b/src/components/OrderForm/OrderForm.orders.helpers.js @@ -16,7 +16,7 @@ import timeFrames from '../../util/time_frames' import rawOrders from '../../orders' -const DEV_ONLY_ALGO_ORDERS = [AccumulateDistribute] +const DEV_ONLY_ALGO_ORDERS = [AccumulateDistribute, Recurring] const getAlgoOrdersForStandalone = (isBeta) => [ ...(isBeta ? DEV_ONLY_ALGO_ORDERS : []), From b2f3a8ece31af1e7fd9a2185a28431aae04e17c3 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Mon, 14 Aug 2023 13:22:11 +0300 Subject: [PATCH 02/47] HistoryButton: moved to separated component --- .../AlgoOrdersHistoryButton.js | 21 ++--------- src/ui/HistoryButton/HistoryButton.js | 35 +++++++++++++++++++ src/ui/HistoryButton/index.js | 1 + .../HistoryButton}/style.scss | 0 4 files changed, 39 insertions(+), 18 deletions(-) create mode 100644 src/ui/HistoryButton/HistoryButton.js create mode 100644 src/ui/HistoryButton/index.js rename src/{components/AlgoOrdersHistoryButton => ui/HistoryButton}/style.scss (100%) diff --git a/src/components/AlgoOrdersHistoryButton/AlgoOrdersHistoryButton.js b/src/components/AlgoOrdersHistoryButton/AlgoOrdersHistoryButton.js index 09be539fe..7be5a482b 100644 --- a/src/components/AlgoOrdersHistoryButton/AlgoOrdersHistoryButton.js +++ b/src/components/AlgoOrdersHistoryButton/AlgoOrdersHistoryButton.js @@ -1,24 +1,16 @@ import React, { useRef, useEffect } from 'react' -import _toUpper from 'lodash/toUpper' import { useDispatch, useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { Spinner } from '@ufx-ui/core' -import PanelButton from '../../ui/Panel/Panel.Button' import { getShowAOsHistory } from '../../redux/selectors/ao' import { getIsAOsHistoryLoaded } from '../../redux/selectors/ws' import AOActions from '../../redux/actions/ao' import useToggle from '../../hooks/useToggle' -import HistoryIcon from '../../ui/Icons/HistoryIcon' - -import './style.css' +import HistoryButton from '../../ui/HistoryButton' const AlgoOrdersHistoryButton = () => { const [isHistoryLoading, , setIsLoading, setStopLoading] = useToggle(false) const timeoutId = useRef(null) - const { t } = useTranslation() - const isHistoryActive = useSelector(getShowAOsHistory) const isHistoryLoaded = useSelector(getIsAOsHistoryLoaded) @@ -62,17 +54,10 @@ const AlgoOrdersHistoryButton = () => { }, []) return ( - - ) : ( - - ) - } + isLoading={isHistoryLoading} /> ) } diff --git a/src/ui/HistoryButton/HistoryButton.js b/src/ui/HistoryButton/HistoryButton.js new file mode 100644 index 000000000..5330dbea0 --- /dev/null +++ b/src/ui/HistoryButton/HistoryButton.js @@ -0,0 +1,35 @@ +import React from 'react' +import _toUpper from 'lodash/toUpper' +import PropTypes from 'prop-types' +import { useTranslation } from 'react-i18next' +import { Spinner } from '@ufx-ui/core' +import PanelButton from '../Panel/Panel.Button' +import HistoryIcon from '../Icons/HistoryIcon' + +import './style.css' + +const HistoryButton = ({ onClick, isActive, isLoading }) => { + const { t } = useTranslation() + return ( + + ) : ( + + ) + } + /> + ) +} + +HistoryButton.propTypes = { + onClick: PropTypes.func.isRequired, + isActive: PropTypes.bool.isRequired, + isLoading: PropTypes.bool.isRequired, +} + +export default HistoryButton diff --git a/src/ui/HistoryButton/index.js b/src/ui/HistoryButton/index.js new file mode 100644 index 000000000..e71d40225 --- /dev/null +++ b/src/ui/HistoryButton/index.js @@ -0,0 +1 @@ +export { default } from './HistoryButton' diff --git a/src/components/AlgoOrdersHistoryButton/style.scss b/src/ui/HistoryButton/style.scss similarity index 100% rename from src/components/AlgoOrdersHistoryButton/style.scss rename to src/ui/HistoryButton/style.scss From d85e64c7d8d76795e226ac1b0331c2e7f48a1140 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Mon, 14 Aug 2023 13:43:32 +0300 Subject: [PATCH 03/47] Backtest: add history/new test toggler --- .../BacktestOptionsPanel.js | 17 +++++++++++++++++ src/components/BacktestOptionsPanel/style.scss | 6 ++++++ .../StrategiesGridLayout.constants.js | 2 +- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js index f2efc1641..e0d93a164 100644 --- a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js +++ b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js @@ -1,5 +1,6 @@ import React, { useMemo, useState } from 'react' import PropTypes from 'prop-types' +import _toUpper from 'lodash/toUpper' import _debounce from 'lodash/debounce' import { useTranslation } from 'react-i18next' import { @@ -16,6 +17,9 @@ import ClockIcon from '../../ui/Icons/ClockIcon' import CalendarIcon from '../../ui/Icons/CalendarIcon' import './style.css' +import HistoryButton from '../../ui/HistoryButton/HistoryButton' +import useToggle from '../../hooks/useToggle' +import PanelButton from '../../ui/Panel/Panel.Button' const MAX_DATE = new Date() @@ -55,6 +59,7 @@ const BacktestOptionsPanel = ({ const { t } = useTranslation() const [seedError, setSeedError] = useState(null) const [candleSeedValue, setCandleSeedValue] = useState(candleSeed) + const [isHistoryTabActive,, setHistoryTabActive, setNewTestTabActive] = useToggle(false) const [isCustomDatePicker, setIsCustomDatePicker] = useState(false) const timePeriods = getTimePeriods(t) @@ -135,6 +140,18 @@ const BacktestOptionsPanel = ({ return (
+
+ + +
Date: Mon, 14 Aug 2023 16:10:03 +0300 Subject: [PATCH 04/47] PanelButton: align text and icon --- src/components/TradingStatePanel/style.scss | 1 - src/ui/HistoryButton/HistoryButton.js | 2 +- src/ui/HistoryButton/style.scss | 4 ---- src/ui/Panel/style.scss | 1 + 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/TradingStatePanel/style.scss b/src/components/TradingStatePanel/style.scss index 037b9a55a..a3a31a8f2 100644 --- a/src/components/TradingStatePanel/style.scss +++ b/src/components/TradingStatePanel/style.scss @@ -52,7 +52,6 @@ } .hfui-tradingstatepanel__ccy-icon { - margin-top: 5px; width: 20px; height: 20px; // primary-8 color diff --git a/src/ui/HistoryButton/HistoryButton.js b/src/ui/HistoryButton/HistoryButton.js index 5330dbea0..eaffc14ea 100644 --- a/src/ui/HistoryButton/HistoryButton.js +++ b/src/ui/HistoryButton/HistoryButton.js @@ -19,7 +19,7 @@ const HistoryButton = ({ onClick, isActive, isLoading }) => { isLoading ? ( ) : ( - + ) } /> diff --git a/src/ui/HistoryButton/style.scss b/src/ui/HistoryButton/style.scss index e934f6b54..511b698c4 100644 --- a/src/ui/HistoryButton/style.scss +++ b/src/ui/HistoryButton/style.scss @@ -1,7 +1,3 @@ -.hfui-history-button__icon { - padding-top: 6px; -} - .hfui-history-button__spinner { margin-top: 0; color: var(--primary-8); diff --git a/src/ui/Panel/style.scss b/src/ui/Panel/style.scss index a0f639233..8ae5e464c 100644 --- a/src/ui/Panel/style.scss +++ b/src/ui/Panel/style.scss @@ -328,6 +328,7 @@ .hfui-panel__button { display: flex; + align-items: center; font-size: 13px; font-weight: 600; line-height: 32px; From e6b136ed2f5715e90789df0868c4808c55574fff Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Tue, 15 Aug 2023 01:00:43 +0300 Subject: [PATCH 05/47] fetch strategy backtests --- .../StrategyEditor.container.js | 11 +++++++ .../StrategyEditor/StrategyEditor.helpers.js | 2 ++ .../StrategyEditor/StrategyEditor.js | 33 ++++++++++++++++--- src/redux/actions/ws.js | 5 +++ src/redux/middleware/ws/on_message.js | 7 ++++ src/redux/reducers/ws/backtest.js | 21 +++++++++--- .../ws/get_current_strategy_backtests_list.js | 13 ++++++++ .../selectors/ws/get_strategies_backtests.js | 8 +++++ src/redux/selectors/ws/index.js | 4 +++ 9 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 src/redux/selectors/ws/get_current_strategy_backtests_list.js create mode 100644 src/redux/selectors/ws/get_strategies_backtests.js diff --git a/src/components/StrategyEditor/StrategyEditor.container.js b/src/components/StrategyEditor/StrategyEditor.container.js index 999d1fccc..7c6781363 100644 --- a/src/components/StrategyEditor/StrategyEditor.container.js +++ b/src/components/StrategyEditor/StrategyEditor.container.js @@ -2,6 +2,7 @@ import { connect } from 'react-redux' import { v4 as uuidv4 } from 'uuid' import _omitBy from 'lodash/omitBy' import _isEmpty from 'lodash/isEmpty' +import _isArray from 'lodash/isArray' import WSActions from '../../redux/actions/ws' import UIActions from '../../redux/actions/ui' @@ -10,6 +11,7 @@ import WSTypes from '../../redux/constants/ws' import { getAuthToken, getBacktestResults, + getCurrentStrategyBacktestsList, getCurrentStrategyExecutionState, getSavedStrategies, } from '../../redux/selectors/ws' @@ -43,6 +45,7 @@ const mapStateToProps = (state = {}) => { pendingLiveStrategy: getUIState(state, UI_KEYS.pendingLiveStrategy, null), serviceStatus: getServicesStatus(state), markets: getMarketsSortedByVolumeForExecution(state), + isBacktestsListFetched: _isArray(getCurrentStrategyBacktestsList(state)), } } @@ -99,6 +102,7 @@ const mapDispatchToProps = (dispatch) => ({ strategyContent, constraints, label, + id, } = data const processedStrategy = _omitBy(strategyContent, _isEmpty) const sync = true @@ -111,6 +115,7 @@ const mapDispatchToProps = (dispatch) => ({ 'exec.str', [ 'bitfinex', + id, startNum, endNum, symbol, @@ -184,6 +189,12 @@ const mapDispatchToProps = (dispatch) => ({ logInformation: (message, level, action, trace) => { dispatch(UIActions.logInformation(message, level, action, trace)) }, + fetchBacktestsList: (id) => { + dispatch(WSActions.send({ + alias: WSTypes.ALIAS_DATA_SERVER, + data: ['get.bt.history.list', id], + })) + }, }) export default connect(mapStateToProps, mapDispatchToProps)(StrategyEditor) diff --git a/src/components/StrategyEditor/StrategyEditor.helpers.js b/src/components/StrategyEditor/StrategyEditor.helpers.js index 20e3ee0bb..f0a923606 100644 --- a/src/components/StrategyEditor/StrategyEditor.helpers.js +++ b/src/components/StrategyEditor/StrategyEditor.helpers.js @@ -121,6 +121,7 @@ export const prepareStrategyBacktestingArgs = (strategy) => { endDate, }, strategyContent, + id, } = strategy return { @@ -130,6 +131,7 @@ export const prepareStrategyBacktestingArgs = (strategy) => { [STRATEGY_OPTIONS_KEYS.TIMEFRAME]: timeframe, [STRATEGY_OPTIONS_KEYS.TRADES]: trades, strategyContent, + id, [STRATEGY_OPTIONS_KEYS.CANDLES]: candles, [STRATEGY_OPTIONS_KEYS.CANDLE_SEED]: candleSeed, constraints: { diff --git a/src/components/StrategyEditor/StrategyEditor.js b/src/components/StrategyEditor/StrategyEditor.js index 1e61eae08..bda329bd0 100644 --- a/src/components/StrategyEditor/StrategyEditor.js +++ b/src/components/StrategyEditor/StrategyEditor.js @@ -98,8 +98,13 @@ const StrategyEditor = (props) => { setSectionErrors, serviceStatus, logInformation, + fetchBacktestsList, + isBacktestsListFetched, } = props - const { t, i18n: { language } } = useTranslation() + const { + t, + i18n: { language }, + } = useTranslation() const [isRemoveModalOpen, , openRemoveModal, closeRemoveModal] = useToggle(false) const [ @@ -270,10 +275,15 @@ const StrategyEditor = (props) => { delete preparedStrategy.strategyContent.id } onCreateStrategyFromExisted(preparedStrategy.label, preparedStrategy) - logInformation(`New strategy draft created (${preparedStrategy.label})`, LOG_LEVELS.INFO, 'strategy_draft_init', { - source: 'json', - from: preparedStrategy.label, - }) + logInformation( + `New strategy draft created (${preparedStrategy.label})`, + LOG_LEVELS.INFO, + 'strategy_draft_init', + { + source: 'json', + from: preparedStrategy.label, + }, + ) } } catch (e) { debug('Error while importing strategy: %s', e) @@ -555,6 +565,7 @@ const StrategyEditor = (props) => { [sectionErrors], ) + // Start strategy executing after change mode useEffect(() => { if ( isPaperTrading @@ -574,6 +585,16 @@ const StrategyEditor = (props) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [savedStrategies, pendingLiveStrategy, isStrategyManagerRunning]) + // Fetch backtests list of the strategy + useEffect(() => { + console.log(isBacktestsListFetched) + if (!isPaperTrading || !strategyId || isBacktestsListFetched) { + return + } + + fetchBacktestsList(strategyId) + }, [isPaperTrading, fetchBacktestsList, strategyId, isBacktestsListFetched]) + const sbtitleStrategy = useCallback( ({ selectedTab, sidebarOpened }) => ( ({ + type: t.ADD_STRATEGY_BACKTESTS_LIST, + payload: { strategyId, backtestsList }, + }), + recvAPICredentialsConfigured: (state) => ({ type: t.DATA_API_CREDENTIALS_CONFIGURED, payload: { state }, diff --git a/src/redux/middleware/ws/on_message.js b/src/redux/middleware/ws/on_message.js index d392617fb..2b1d89644 100644 --- a/src/redux/middleware/ws/on_message.js +++ b/src/redux/middleware/ws/on_message.js @@ -485,6 +485,13 @@ export default (alias, store) => (e = {}) => { break } + case 'data.bt.history.list': { + const [, strategyId, backtestsList] = payload + store.dispatch(WSActions.recvStrategyBacktestsList(strategyId, backtestsList)) + + break + } + case 'algo.reload': { store.dispatch(WSActions.clearAlgoOrders()) break diff --git a/src/redux/reducers/ws/backtest.js b/src/redux/reducers/ws/backtest.js index 908ddd63c..39fc77beb 100644 --- a/src/redux/reducers/ws/backtest.js +++ b/src/redux/reducers/ws/backtest.js @@ -2,14 +2,15 @@ import types from '../../constants/ws' function getInitialState() { return { - currentTest: null, loading: false, executing: false, finished: false, - trades: [], - candles: [], gid: null, progressPerc: 0, + results: { + }, + strategiesBacktests: { + }, } } @@ -78,6 +79,18 @@ function reducer(state = getInitialState(), action = {}) { } } + case types.ADD_STRATEGY_BACKTESTS_LIST: { + const { strategyId, backtestsList } = payload + + return { + ...state, + strategiesBacktests: { + ...state.strategiesBacktests, + [strategyId]: backtestsList, + }, + } + } + case types.DISCONNECTED: case types.RESET_DATA_BACKTEST: { return getInitialState() @@ -85,8 +98,6 @@ function reducer(state = getInitialState(), action = {}) { case types.PURGE_DATA_BACKTEST: { return { - candles: [], - trades: [], loading: false, executing: false, progressPerc: 0, diff --git a/src/redux/selectors/ws/get_current_strategy_backtests_list.js b/src/redux/selectors/ws/get_current_strategy_backtests_list.js new file mode 100644 index 000000000..600608796 --- /dev/null +++ b/src/redux/selectors/ws/get_current_strategy_backtests_list.js @@ -0,0 +1,13 @@ +import { createSelector } from 'reselect' +import _get from 'lodash/get' +import { getCurrentStrategy } from '../ui' +import { getStrategiesBacktests } from '.' + +const getCurrentStrategyBacktestsList = createSelector( + [getCurrentStrategy, getStrategiesBacktests], + ({ id }, strategiesBacktests) => { + return _get(strategiesBacktests, id, null) + }, +) + +export default getCurrentStrategyBacktestsList diff --git a/src/redux/selectors/ws/get_strategies_backtests.js b/src/redux/selectors/ws/get_strategies_backtests.js new file mode 100644 index 000000000..52dc51fb7 --- /dev/null +++ b/src/redux/selectors/ws/get_strategies_backtests.js @@ -0,0 +1,8 @@ +import _get from 'lodash/get' +import { REDUCER_PATHS } from '../../config' + +const path = REDUCER_PATHS.WS + +const EMPTY_OBJ = {} + +export default (state) => _get(state, `${path}.backtest.strategiesBacktests`, EMPTY_OBJ) diff --git a/src/redux/selectors/ws/index.js b/src/redux/selectors/ws/index.js index acbc8109a..b9f631cc8 100644 --- a/src/redux/selectors/ws/index.js +++ b/src/redux/selectors/ws/index.js @@ -31,6 +31,8 @@ import getCurrentStrategyOpenPositions from './get_current_strategy_open_positio import getBacktestState from './get_backtest_state' import getBacktestData from './get_backtest_data' import getBacktestResults from './get_backtest_results' +import getStrategiesBacktests from './get_strategies_backtests' +import getCurrentStrategyBacktestsList from './get_current_strategy_backtests_list' import { apiClientConnected, @@ -77,6 +79,8 @@ export { getBacktestState, getBacktestData, getBacktestResults, + getStrategiesBacktests, + getCurrentStrategyBacktestsList, apiClientConnected, apiClientConnecting, From 7b5bd2ec8a07efdd2009a957bde14799db3b1adb Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Tue, 15 Aug 2023 22:26:17 +0300 Subject: [PATCH 06/47] BT history: recieve data from data-server --- public/locales/en-US/translations.json | 6 +- .../BacktestOptionsPanel/style.scss | 13 +++ src/redux/actions/ws.js | 8 ++ src/redux/constants/ws.js | 3 + src/redux/middleware/ws/on_message.js | 11 +++ src/redux/reducers/ws/backtest.js | 21 ++--- src/redux/reducers/ws/backtestHistory.js | 86 +++++++++++++++++++ src/redux/reducers/ws/index.js | 2 + ...s_backtests.js => get_backtest_history.js} | 2 +- .../ws/get_current_strategy_backtests_list.js | 18 +++- src/redux/selectors/ws/index.js | 4 +- 11 files changed, 149 insertions(+), 25 deletions(-) create mode 100644 src/redux/reducers/ws/backtestHistory.js rename src/redux/selectors/ws/{get_strategies_backtests.js => get_backtest_history.js} (59%) diff --git a/public/locales/en-US/translations.json b/public/locales/en-US/translations.json index eb3d83ec6..33f9baf03 100644 --- a/public/locales/en-US/translations.json +++ b/public/locales/en-US/translations.json @@ -871,7 +871,8 @@ "lastQuarter": "Last quarter", "lastYear": "Last year", "lastXyears": "Last {{amount}} years", - "elapsedTime": "Elapsed time" + "elapsedTime": "Elapsed time", + "noHistoryBacktests": "No backtests in history" }, "ui": { "ok": "Okay", @@ -913,7 +914,8 @@ "saveAndLaunch": "Save & Launch", "launchNoSave": "Launch without saving", "updateAndRestart": "Update and Restart", - "saveAndContinue": "Save and Continue" + "saveAndContinue": "Save and Continue", + "starred": "Starred" }, "crashHandler": { "text1": "An error occurred that caused the Bitfinex Honey UI to halt. Please, restart the application to proceed working with it", diff --git a/src/components/BacktestOptionsPanel/style.scss b/src/components/BacktestOptionsPanel/style.scss index 2652b30d5..c7a582426 100644 --- a/src/components/BacktestOptionsPanel/style.scss +++ b/src/components/BacktestOptionsPanel/style.scss @@ -20,6 +20,19 @@ } } + .backtest-history-table { + @extend .item; + width: 100%; + height: 100%; + + .ReactVirtualized__Table__row { + &:hover { + background-color: var(--background-color); + cursor: pointer; + } + } + } + .tabs-menu { @extend .item; display: flex; diff --git a/src/redux/actions/ws.js b/src/redux/actions/ws.js index 068d4f34c..8236ae709 100644 --- a/src/redux/actions/ws.js +++ b/src/redux/actions/ws.js @@ -391,6 +391,14 @@ export default { type: t.EXPORT_STRATEGIES_ON_RESET, payload: { password }, }), + changeBacktestFavoriteState: (backtestId, isFavorite) => ({ + type: t.BACKTEST_SET_FAVORITE, + payload: { backtestId, isFavorite }, + }), + removeBacktest: (backtestId) => ({ + type: t.BACKTEST_REMOVE, + payload: { backtestId }, + }), initAuth: (password) => send(['auth.init', password, MAIN_MODE, getScope()]), auth: (password, mode) => send(['auth.submit', password, mode, getScope()]), diff --git a/src/redux/constants/ws.js b/src/redux/constants/ws.js index 959d8a443..a111bfc96 100644 --- a/src/redux/constants/ws.js +++ b/src/redux/constants/ws.js @@ -28,6 +28,7 @@ export default { DATA_STRATEGY: 'WS_DATA_STRATEGY', DATA_REMOVE_STRATEGY: 'WS_DATA_REMOVE_STRATEGY', DATA_STRATEGIES: 'WS_DATA_STRATEGIES', + ADD_STRATEGY_BACKTESTS_LIST: 'WS_ADD_STRATEGY_BACKTESTS_LIST', DATA_API_CREDENTIALS_CONFIGURED: 'WS_DATA_API_CREDENTIALS_CONFIGURED', UPDATE_API_CREDENTIALS_CONFIGURED: 'WS_UPDATE_API_CREDENTIALS_CONFIGURED', DATA_CLIENT_STATUS_UPDATE: 'WS_DATA_CLIENT_STATUS_UPDATE', @@ -71,6 +72,8 @@ export default { BACKTEST_RESULTS: 'WS_BACKTEST_RESULTS', BACKTEST_STOPPED: 'WS_BACKTEST_STOPPED', BACKTEST_STARTED: 'WS_BACKTEST_STARTED', + BACKTEST_SET_FAVORITE: 'WS_BACKTEST_SET_FAVORITE', + BACKTEST_REMOVE: 'WS_BACKTEST_REMOVE', EXECUTION_LOADING: 'WS_EXECUTION_LOADING', EXECUTION_LOADING_GID: 'WS_EXECUTION_LOADING_GID', EXECUTION_CONNECTION_LOST: 'WS_EXECUTION_CONNECTION_LOST', diff --git a/src/redux/middleware/ws/on_message.js b/src/redux/middleware/ws/on_message.js index 2b1d89644..fb7fea650 100644 --- a/src/redux/middleware/ws/on_message.js +++ b/src/redux/middleware/ws/on_message.js @@ -488,7 +488,18 @@ export default (alias, store) => (e = {}) => { case 'data.bt.history.list': { const [, strategyId, backtestsList] = payload store.dispatch(WSActions.recvStrategyBacktestsList(strategyId, backtestsList)) + break + } + + case 'data.bt.history.favorite': { + const [, backtestId, isFavorite] = payload + store.dispatch(WSActions.changeBacktestFavoriteState(backtestId, isFavorite)) + break + } + case 'data.bt.history.deleted': { + const [, backtestId] = payload + store.dispatch(WSActions.removeBacktest(backtestId)) break } diff --git a/src/redux/reducers/ws/backtest.js b/src/redux/reducers/ws/backtest.js index 39fc77beb..908ddd63c 100644 --- a/src/redux/reducers/ws/backtest.js +++ b/src/redux/reducers/ws/backtest.js @@ -2,15 +2,14 @@ import types from '../../constants/ws' function getInitialState() { return { + currentTest: null, loading: false, executing: false, finished: false, + trades: [], + candles: [], gid: null, progressPerc: 0, - results: { - }, - strategiesBacktests: { - }, } } @@ -79,18 +78,6 @@ function reducer(state = getInitialState(), action = {}) { } } - case types.ADD_STRATEGY_BACKTESTS_LIST: { - const { strategyId, backtestsList } = payload - - return { - ...state, - strategiesBacktests: { - ...state.strategiesBacktests, - [strategyId]: backtestsList, - }, - } - } - case types.DISCONNECTED: case types.RESET_DATA_BACKTEST: { return getInitialState() @@ -98,6 +85,8 @@ function reducer(state = getInitialState(), action = {}) { case types.PURGE_DATA_BACKTEST: { return { + candles: [], + trades: [], loading: false, executing: false, progressPerc: 0, diff --git a/src/redux/reducers/ws/backtestHistory.js b/src/redux/reducers/ws/backtestHistory.js new file mode 100644 index 000000000..2e72ff021 --- /dev/null +++ b/src/redux/reducers/ws/backtestHistory.js @@ -0,0 +1,86 @@ +import _get from 'lodash/get' +import _forEach from 'lodash/forEach' +import _omit from 'lodash/omit' +import _filter from 'lodash/filter' +import types from '../../constants/ws' + +const getInitialState = () => ({ + mappedKeysByStrategyIds: {}, + backtestResults: {}, +}) + +const reducer = (state = getInitialState(), action = {}) => { + const { type, payload = {} } = action + + switch (type) { + case types.ADD_STRATEGY_BACKTESTS_LIST: { + const { strategyId, backtestsList } = payload + + const mappedBacktestKeys = [] + const backtestResults = { + ...state.backtestResults, + } + + _forEach(backtestsList, (_backtest) => { + const { executionId, isFavorite } = _backtest + mappedBacktestKeys.push(executionId) + const backtest = { ..._backtest } + + // normalize types after SQLite + backtest.isFavorite = !!isFavorite + + backtestResults[executionId] = backtest + }) + + return { + ...state, + mappedKeysByStrategyIds: { + ...state.mappedKeysByStrategyIds, + [strategyId]: mappedBacktestKeys, + }, + backtestResults, + } + } + + case types.BACKTEST_SET_FAVORITE: { + const { backtestId, isFavorite } = payload + + const backtest = _get(state, `backtestResults.${backtestId}`, {}) + + return { + ...state, + backtestResults: { + ...state.backtestResults, + [backtestId]: { + ...backtest, + isFavorite: !!isFavorite, + }, + }, + } + } + + case types.BACKTEST_REMOVE: { + const { backtestId } = payload + + const backtest = _get(state, `backtestResults.${backtestId}`, {}) + const { strategyId } = backtest + const backtestKeys = state.mappedKeysByStrategyIds[strategyId] + const filteredKeys = _filter(backtestKeys, (id) => backtestId !== id) + + return { + ...state, + mappedKeysByStrategyIds: { + ...state.mappedKeysByStrategyIds, + [strategyId]: filteredKeys, + }, + backtestResults: _omit(state.backtestResults, backtestId), + } + } + + default: { + return state + } + } +} + +export default reducer diff --git a/src/redux/reducers/ws/index.js b/src/redux/reducers/ws/index.js index 4a576e395..a9405123f 100644 --- a/src/redux/reducers/ws/index.js +++ b/src/redux/reducers/ws/index.js @@ -11,6 +11,7 @@ import orderHistory from './order_history' import algoOrders from './algo_orders' import algoOrdersHistory from './algo_orders_history' import backtest from './backtest' +import backtestHistory from './backtestHistory' import execution from './execution' import favoriteTradingPairs from './favorite_pairs' @@ -26,6 +27,7 @@ export default combineReducers({ socket, auth, backtest, + backtestHistory, execution, favoriteTradingPairs, }) diff --git a/src/redux/selectors/ws/get_strategies_backtests.js b/src/redux/selectors/ws/get_backtest_history.js similarity index 59% rename from src/redux/selectors/ws/get_strategies_backtests.js rename to src/redux/selectors/ws/get_backtest_history.js index 52dc51fb7..7274fec55 100644 --- a/src/redux/selectors/ws/get_strategies_backtests.js +++ b/src/redux/selectors/ws/get_backtest_history.js @@ -5,4 +5,4 @@ const path = REDUCER_PATHS.WS const EMPTY_OBJ = {} -export default (state) => _get(state, `${path}.backtest.strategiesBacktests`, EMPTY_OBJ) +export default (state) => _get(state, `${path}.backtestHistory`, EMPTY_OBJ) diff --git a/src/redux/selectors/ws/get_current_strategy_backtests_list.js b/src/redux/selectors/ws/get_current_strategy_backtests_list.js index 600608796..f6c11fed8 100644 --- a/src/redux/selectors/ws/get_current_strategy_backtests_list.js +++ b/src/redux/selectors/ws/get_current_strategy_backtests_list.js @@ -1,12 +1,22 @@ import { createSelector } from 'reselect' import _get from 'lodash/get' +import _isArray from 'lodash/isArray' +import _map from 'lodash/map' import { getCurrentStrategy } from '../ui' -import { getStrategiesBacktests } from '.' +import { getBacktestHistory } from '.' const getCurrentStrategyBacktestsList = createSelector( - [getCurrentStrategy, getStrategiesBacktests], - ({ id }, strategiesBacktests) => { - return _get(strategiesBacktests, id, null) + [getCurrentStrategy, getBacktestHistory], + ({ id }, backtestHistory) => { + const { mappedKeysByStrategyIds, backtestResults } = backtestHistory + const mappedKeys = _get(mappedKeysByStrategyIds, id, null) + + if (!_isArray(mappedKeys)) { + return mappedKeys + } + return _map(mappedKeys, (executionId) => { + return _get(backtestResults, executionId, {}) + }) }, ) diff --git a/src/redux/selectors/ws/index.js b/src/redux/selectors/ws/index.js index b9f631cc8..32a69c5f1 100644 --- a/src/redux/selectors/ws/index.js +++ b/src/redux/selectors/ws/index.js @@ -31,7 +31,7 @@ import getCurrentStrategyOpenPositions from './get_current_strategy_open_positio import getBacktestState from './get_backtest_state' import getBacktestData from './get_backtest_data' import getBacktestResults from './get_backtest_results' -import getStrategiesBacktests from './get_strategies_backtests' +import getBacktestHistory from './get_backtest_history' import getCurrentStrategyBacktestsList from './get_current_strategy_backtests_list' import { @@ -79,7 +79,7 @@ export { getBacktestState, getBacktestData, getBacktestResults, - getStrategiesBacktests, + getBacktestHistory, getCurrentStrategyBacktestsList, apiClientConnected, From 22889b64ee5af283270f595026cf12a394552948 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Tue, 15 Aug 2023 22:27:46 +0300 Subject: [PATCH 07/47] BT history: layout list --- .../BacktestOptions.History.columns.js | 58 ++++ .../BacktestOptions.History.js | 111 +++++++ .../BacktestOptions.NewTest.js | 309 ++++++++++++++++++ .../BacktestOptionsPanel.js | 308 +---------------- 4 files changed, 488 insertions(+), 298 deletions(-) create mode 100644 src/components/BacktestOptionsPanel/BacktestOptions.History.columns.js create mode 100644 src/components/BacktestOptionsPanel/BacktestOptions.History.js create mode 100644 src/components/BacktestOptionsPanel/BacktestOptions.NewTest.js diff --git a/src/components/BacktestOptionsPanel/BacktestOptions.History.columns.js b/src/components/BacktestOptionsPanel/BacktestOptions.History.columns.js new file mode 100644 index 000000000..86eff88c8 --- /dev/null +++ b/src/components/BacktestOptionsPanel/BacktestOptions.History.columns.js @@ -0,0 +1,58 @@ +import React from 'react' +import { Button } from '@ufx-ui/core' +import { Icon } from 'react-fa' +import { renderDate } from '../../util/ui' +import FavoriteIcon from '../../ui/Icons/FavoriteIcon' + +export default ({ toggleFavorite, formatTime, removeBacktest }) => [ + { + dataKey: 'executionId', + width: 25, + flexGrow: 0.1, + disableSort: true, + cellRenderer: ({ rowData: { isFavorite, executionId } = {} }) => { + const handleFavIconClick = (e) => { + e.stopPropagation() + toggleFavorite(executionId, !isFavorite) + } + + return ( +
+ +
+ ) + }, + }, + { + dataKey: 'timestamp', + width: 100, + flexGrow: 1, + style: { justifyContent: 'center' }, + cellRenderer: ({ rowData = {} }) => { + return renderDate(rowData.timestamp, formatTime) + }, + disableSort: true, + }, + { + dataKey: 'id', + flexGrow: 0.1, + width: 25, + cellRenderer: ({ rowData: { executionId } = {} }) => { + const handleRemoveBacktestButton = (e) => { + e.stopPropagation() + removeBacktest(executionId) + } + return ( + + ) + }, + }, +] diff --git a/src/components/BacktestOptionsPanel/BacktestOptions.History.js b/src/components/BacktestOptionsPanel/BacktestOptions.History.js new file mode 100644 index 000000000..89f7631ee --- /dev/null +++ b/src/components/BacktestOptionsPanel/BacktestOptions.History.js @@ -0,0 +1,111 @@ +import React, { useCallback, useMemo } from 'react' +import _toUpper from 'lodash/toUpper' +import _filter from 'lodash/filter' +import _isEmpty from 'lodash/isEmpty' +import { useTranslation } from 'react-i18next' +import { VirtualTable } from '@ufx-ui/core' +import { useDispatch, useSelector } from 'react-redux' +import FavoriteIcon from '../../ui/Icons/FavoriteIcon' +import PanelButton from '../../ui/Panel/Panel.Button' +import useToggle from '../../hooks/useToggle' +import { getCurrentStrategyBacktestsList } from '../../redux/selectors/ws' +import WSActions from '../../redux/actions/ws' +import WSTypes from '../../redux/constants/ws' +import BacktestOptionsHistoryColumns from './BacktestOptions.History.columns' +import { getFormatTimeFn } from '../../redux/selectors/ui' + +const BacktestOptionsHistory = () => { + const [isFavoriteSelected, toggleFavoritesList] = useToggle(false) + + const { t } = useTranslation() + const dispatch = useDispatch() + + const backtests = useSelector(getCurrentStrategyBacktestsList) + const formatTime = useSelector(getFormatTimeFn) + + const toggleFavorite = useCallback( + (id, isFavorite) => { + dispatch( + WSActions.send({ + alias: WSTypes.ALIAS_DATA_SERVER, + data: ['set.bt.history.favorite', id, isFavorite], + }), + ) + }, + [dispatch], + ) + + const removeBacktest = useCallback( + (id) => { + dispatch( + WSActions.send({ + alias: WSTypes.ALIAS_DATA_SERVER, + data: ['delete.bt.history', id], + }), + ) + }, + [dispatch], + ) + + const onRowClick = ({ rowData }) => { + dispatch( + WSActions.send({ + alias: WSTypes.ALIAS_DATA_SERVER, + data: ['get.bt.history.details', rowData.executionId], + }), + ) + } + + const columns = useMemo( + () => BacktestOptionsHistoryColumns({ + formatTime, + toggleFavorite, + removeBacktest, + }), + [toggleFavorite, formatTime, removeBacktest], + ) + + const data = useMemo(() => { + return isFavoriteSelected + ? _filter(backtests, ({ isFavorite }) => isFavorite) + : backtests + }, [isFavoriteSelected, backtests]) + + return ( + <> +
+
+ + )} + /> +
+
+
+ {_isEmpty(data) ? ( +

{t('strategyEditor.noHistoryBacktests')}

+ ) : ( + + )} +
+ + ) +} + +export default BacktestOptionsHistory diff --git a/src/components/BacktestOptionsPanel/BacktestOptions.NewTest.js b/src/components/BacktestOptionsPanel/BacktestOptions.NewTest.js new file mode 100644 index 000000000..bb2eba2b9 --- /dev/null +++ b/src/components/BacktestOptionsPanel/BacktestOptions.NewTest.js @@ -0,0 +1,309 @@ +import React, { useMemo, useState } from 'react' +import PropTypes from 'prop-types' +import _debounce from 'lodash/debounce' +import { useTranslation } from 'react-i18next' +import { + Checkbox, Button, Spinner, Intent, +} from '@ufx-ui/core' +import cx from 'clsx' +import AmountInput from '../OrderForm/FieldComponents/input.amount' +import DateInput from '../OrderForm/FieldComponents/input.date' +import TimeFrameDropdown from '../TimeFrameDropdown' +import Dropdown from '../../ui/Dropdown' +import { STRATEGY_OPTIONS_KEYS } from '../StrategyEditor/StrategyEditor.helpers' +import { STRATEGY_SHAPE } from '../../constants/prop-types-shapes' +import ClockIcon from '../../ui/Icons/ClockIcon' +import CalendarIcon from '../../ui/Icons/CalendarIcon' + +const MAX_DATE = new Date() + +const TIME_MAPPING = { + '168h': 168, + '720h': 720, + '2160h': 2160, + '8640h': 8640, + '25920h': 25920, +} + +const getTimePeriods = (t) => ([ + { value: '168h', label: t('strategyEditor.lastWeek') }, + { value: '720h', label: t('strategyEditor.lastMonth') }, + { value: '2160h', label: t('strategyEditor.lastQuarter') }, + { value: '8640h', label: t('strategyEditor.lastYear') }, + { value: '25920h', label: t('strategyEditor.lastXyears', { amount: 3 }) }, +]) + +const BacktestOptionsNewTest = ({ + onBacktestStart, + strategy, + saveStrategyOptions, + isLoading, + onCancelProcess, +}) => { + const { + strategyOptions: { + timeframe, + trades, + candleSeed, + startDate: _startDate, + endDate: _endDate, + candles, + }, + } = strategy + const { t } = useTranslation() + const [seedError, setSeedError] = useState(null) + const [candleSeedValue, setCandleSeedValue] = useState(candleSeed) + const [isCustomDatePicker, setIsCustomDatePicker] = useState(false) + const timePeriods = getTimePeriods(t) + + // eslint-disable-next-line react-hooks/exhaustive-deps + const startDate = new Date(_startDate) + + // eslint-disable-next-line react-hooks/exhaustive-deps + const endDate = new Date(_endDate) + + const setTimeframe = (value) => saveStrategyOptions({ [STRATEGY_OPTIONS_KEYS.TIMEFRAME]: value }) + + const timePeriod = useMemo(() => { + const diff = endDate.getTime() - startDate.getTime() + // Apparently the dates sometimes overlap on Chrome so there's a few extra hours in the diff, let's subtract them. + const diffHours = Math.ceil(diff / (1000 * 60 * 60)) - 2 + + if (diffHours <= TIME_MAPPING['168h']) { + return '168h' + } + + if (diffHours <= TIME_MAPPING['720h']) { + return '720h' + } + + if (diffHours <= TIME_MAPPING['2160h']) { + return '2160h' + } + + if (diffHours <= TIME_MAPPING['8640h']) { + return '8640h' + } + + return '25920h' + }, [startDate, endDate]) + + const setTimePeriod = (value) => { + const end = new Date() + const start = new Date() + const hours = TIME_MAPPING[value] + start.setHours(start.getHours() - hours) + + saveStrategyOptions({ + [STRATEGY_OPTIONS_KEYS.START_DATE]: start, + [STRATEGY_OPTIONS_KEYS.END_DATE]: end, + }) + } + + // https://kyleshevlin.com/debounce-and-throttle-callbacks-with-react-hooks + const setCandleSeed = useMemo( + () => _debounce( + (value) => saveStrategyOptions({ [STRATEGY_OPTIONS_KEYS.CANDLE_SEED]: value }), + 1000, + ), + [saveStrategyOptions], + ) + const setStartDate = (value) => saveStrategyOptions({ [STRATEGY_OPTIONS_KEYS.START_DATE]: value }) + const setEndDate = (value) => saveStrategyOptions({ [STRATEGY_OPTIONS_KEYS.END_DATE]: value }) + const setTrades = (value) => saveStrategyOptions({ + [STRATEGY_OPTIONS_KEYS.TRADES]: value, + [STRATEGY_OPTIONS_KEYS.CANDLES]: !value, + }) + const setCandles = (value) => saveStrategyOptions({ + [STRATEGY_OPTIONS_KEYS.CANDLES]: value, + [STRATEGY_OPTIONS_KEYS.TRADES]: !value, + }) + + const updateSeed = (v) => { + const error = AmountInput.validateValue(v, t) + const processed = AmountInput.processValue(v) + + setSeedError(error) + if (error) { + return + } + setCandleSeedValue(processed) + setCandleSeed(processed) + } + + return ( + <> +
+
+ setIsCustomDatePicker(false)} + /> + setIsCustomDatePicker(true)} + /> +
+
+ {isCustomDatePicker ? ( + <> +
+ +
+
+ +
+ + ) : ( +
+ +

+ {t('strategyEditor.timePeriod')} +

+
+ )} +
+ +
+ {t('strategyEditor.useCandlesCheckboxDescription')} +
+
+ {candles && ( + <> +
+ +

+ {t('strategyEditor.selectCandleDurationDescription')} +

+
+ + + )} + {/* {!isPaperTrading && _includes(symbol?.contexts, 'm') && ( +
+ +

{t('strategyEditor.useMarginCheckboxDescription')}

+
+ )} */} +
+ +
+ {t('strategyEditor.useTradesCheckboxDescription')} +
+
+ + {isLoading && ( +
+ +
+ )} +
+ {isLoading ? ( + + ) : ( + + )} +
+ + + ) +} + +BacktestOptionsNewTest.propTypes = { + strategy: PropTypes.shape(STRATEGY_SHAPE).isRequired, + onCancelProcess: PropTypes.func.isRequired, + onBacktestStart: PropTypes.func, + isLoading: PropTypes.bool.isRequired, + saveStrategyOptions: PropTypes.func.isRequired, +} + +BacktestOptionsNewTest.defaultProps = { + onBacktestStart: () => { }, +} + +export default BacktestOptionsNewTest diff --git a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js index e0d93a164..407517e2e 100644 --- a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js +++ b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js @@ -1,142 +1,17 @@ -import React, { useMemo, useState } from 'react' -import PropTypes from 'prop-types' -import _toUpper from 'lodash/toUpper' -import _debounce from 'lodash/debounce' +import React from 'react' import { useTranslation } from 'react-i18next' -import { - Checkbox, Button, Spinner, Intent, -} from '@ufx-ui/core' -import cx from 'clsx' -import AmountInput from '../OrderForm/FieldComponents/input.amount' -import DateInput from '../OrderForm/FieldComponents/input.date' -import TimeFrameDropdown from '../TimeFrameDropdown' -import Dropdown from '../../ui/Dropdown' -import { STRATEGY_OPTIONS_KEYS } from '../StrategyEditor/StrategyEditor.helpers' -import { STRATEGY_SHAPE } from '../../constants/prop-types-shapes' -import ClockIcon from '../../ui/Icons/ClockIcon' -import CalendarIcon from '../../ui/Icons/CalendarIcon' - -import './style.css' +import _toUpper from 'lodash/toUpper' +import BacktestOptionsNewTest from './BacktestOptions.NewTest' import HistoryButton from '../../ui/HistoryButton/HistoryButton' import useToggle from '../../hooks/useToggle' +import BacktestOptionsHistory from './BacktestOptions.History' import PanelButton from '../../ui/Panel/Panel.Button' -const MAX_DATE = new Date() - -const TIME_MAPPING = { - '168h': 168, - '720h': 720, - '2160h': 2160, - '8640h': 8640, - '25920h': 25920, -} - -const getTimePeriods = (t) => ([ - { value: '168h', label: t('strategyEditor.lastWeek') }, - { value: '720h', label: t('strategyEditor.lastMonth') }, - { value: '2160h', label: t('strategyEditor.lastQuarter') }, - { value: '8640h', label: t('strategyEditor.lastYear') }, - { value: '25920h', label: t('strategyEditor.lastXyears', { amount: 3 }) }, -]) +import './style.css' -const BacktestOptionsPanel = ({ - onBacktestStart, - strategy, - saveStrategyOptions, - isLoading, - onCancelProcess, -}) => { - const { - strategyOptions: { - timeframe, - trades, - candleSeed, - startDate: _startDate, - endDate: _endDate, - candles, - }, - } = strategy +const BacktestOptionsPanel = (props) => { + const [isHistoryTabActive, , setHistoryTabActive, setNewTestTabActive] = useToggle(false) const { t } = useTranslation() - const [seedError, setSeedError] = useState(null) - const [candleSeedValue, setCandleSeedValue] = useState(candleSeed) - const [isHistoryTabActive,, setHistoryTabActive, setNewTestTabActive] = useToggle(false) - const [isCustomDatePicker, setIsCustomDatePicker] = useState(false) - const timePeriods = getTimePeriods(t) - - // eslint-disable-next-line react-hooks/exhaustive-deps - const startDate = new Date(_startDate) - - // eslint-disable-next-line react-hooks/exhaustive-deps - const endDate = new Date(_endDate) - - const setTimeframe = (value) => saveStrategyOptions({ [STRATEGY_OPTIONS_KEYS.TIMEFRAME]: value }) - - const timePeriod = useMemo(() => { - const diff = endDate.getTime() - startDate.getTime() - // Apparently the dates sometimes overlap on Chrome so there's a few extra hours in the diff, let's subtract them. - const diffHours = Math.ceil(diff / (1000 * 60 * 60)) - 2 - - if (diffHours <= TIME_MAPPING['168h']) { - return '168h' - } - - if (diffHours <= TIME_MAPPING['720h']) { - return '720h' - } - - if (diffHours <= TIME_MAPPING['2160h']) { - return '2160h' - } - - if (diffHours <= TIME_MAPPING['8640h']) { - return '8640h' - } - - return '25920h' - }, [startDate, endDate]) - - const setTimePeriod = (value) => { - const end = new Date() - const start = new Date() - const hours = TIME_MAPPING[value] - start.setHours(start.getHours() - hours) - - saveStrategyOptions({ - [STRATEGY_OPTIONS_KEYS.START_DATE]: start, - [STRATEGY_OPTIONS_KEYS.END_DATE]: end, - }) - } - - // https://kyleshevlin.com/debounce-and-throttle-callbacks-with-react-hooks - const setCandleSeed = useMemo( - () => _debounce( - (value) => saveStrategyOptions({ [STRATEGY_OPTIONS_KEYS.CANDLE_SEED]: value }), - 1000, - ), - [saveStrategyOptions], - ) - const setStartDate = (value) => saveStrategyOptions({ [STRATEGY_OPTIONS_KEYS.START_DATE]: value }) - const setEndDate = (value) => saveStrategyOptions({ [STRATEGY_OPTIONS_KEYS.END_DATE]: value }) - const setTrades = (value) => saveStrategyOptions({ - [STRATEGY_OPTIONS_KEYS.TRADES]: value, - [STRATEGY_OPTIONS_KEYS.CANDLES]: !value, - }) - const setCandles = (value) => saveStrategyOptions({ - [STRATEGY_OPTIONS_KEYS.CANDLES]: value, - [STRATEGY_OPTIONS_KEYS.TRADES]: !value, - }) - - const updateSeed = (v) => { - const error = AmountInput.validateValue(v, t) - const processed = AmountInput.processValue(v) - - setSeedError(error) - if (error) { - return - } - setCandleSeedValue(processed) - setCandleSeed(processed) - } return (
@@ -152,176 +27,13 @@ const BacktestOptionsPanel = ({ isLoading={false} />
-
-
- setIsCustomDatePicker(false)} - /> - setIsCustomDatePicker(true)} - /> -
-
- {isCustomDatePicker ? ( - <> -
- -
-
- -
- + {isHistoryTabActive ? ( + ) : ( -
- -

- {t('strategyEditor.timePeriod')} -

-
- )} -
- -
- {t('strategyEditor.useCandlesCheckboxDescription')} -
-
- {candles && ( - <> -
- -

- {t('strategyEditor.selectCandleDurationDescription')} -

-
- - + )} - {/* {!isPaperTrading && _includes(symbol?.contexts, 'm') && ( -
- -

{t('strategyEditor.useMarginCheckboxDescription')}

-
- )} */} -
- -
- {t('strategyEditor.useTradesCheckboxDescription')} -
-
- - {isLoading && ( -
- -
- )} -
- {isLoading ? ( - - ) : ( - - )} -
) } -BacktestOptionsPanel.propTypes = { - strategy: PropTypes.shape(STRATEGY_SHAPE).isRequired, - onCancelProcess: PropTypes.func.isRequired, - onBacktestStart: PropTypes.func, - isLoading: PropTypes.bool.isRequired, - saveStrategyOptions: PropTypes.func.isRequired, -} - -BacktestOptionsPanel.defaultProps = { - onBacktestStart: () => { }, -} - export default BacktestOptionsPanel From d14151e8e439ad1cb3fce703a608ccd0d7198f11 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Wed, 16 Aug 2023 15:09:22 +0300 Subject: [PATCH 08/47] BT history: restructure components and folders --- .../BacktestOptionsPanel.js | 31 ++++++++++++------- .../BacktestHistoryList.columns.js} | 4 +-- .../BacktestHistoryList.js} | 22 ++++++------- .../NewTest.js} | 16 +++++----- 4 files changed, 40 insertions(+), 33 deletions(-) rename src/components/BacktestOptionsPanel/{BacktestOptions.History.columns.js => tabs/BacktestHistoryList.columns.js} (93%) rename src/components/BacktestOptionsPanel/{BacktestOptions.History.js => tabs/BacktestHistoryList.js} (81%) rename src/components/BacktestOptionsPanel/{BacktestOptions.NewTest.js => tabs/NewTest.js} (94%) diff --git a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js index 407517e2e..751a4dab7 100644 --- a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js +++ b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js @@ -1,37 +1,44 @@ -import React from 'react' +import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import _toUpper from 'lodash/toUpper' -import BacktestOptionsNewTest from './BacktestOptions.NewTest' +import _bind from 'lodash/bind' +import BacktestOptionsNewTest from './tabs/NewTest' import HistoryButton from '../../ui/HistoryButton/HistoryButton' -import useToggle from '../../hooks/useToggle' -import BacktestOptionsHistory from './BacktestOptions.History' +import BacktestHistoryList from './tabs/BacktestHistoryList' import PanelButton from '../../ui/Panel/Panel.Button' import './style.css' +const BACKTEST_OPTIONS_TABS = { + NEW_TEST: 'NEW TEST', + HISTORY_LIST: 'HISTORY_LIST', + HISTORY_DETAILS: 'HISTORY_DETAILS', +} + const BacktestOptionsPanel = (props) => { - const [isHistoryTabActive, , setHistoryTabActive, setNewTestTabActive] = useToggle(false) + const [activeTab, setActiveTab] = useState(BACKTEST_OPTIONS_TABS.NEW_TEST) const { t } = useTranslation() return (
- {isHistoryTabActive ? ( - - ) : ( + {activeTab === BACKTEST_OPTIONS_TABS.NEW_TEST && ( )} + {activeTab === BACKTEST_OPTIONS_TABS.HISTORY_LIST && ( + + )}
) } diff --git a/src/components/BacktestOptionsPanel/BacktestOptions.History.columns.js b/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.columns.js similarity index 93% rename from src/components/BacktestOptionsPanel/BacktestOptions.History.columns.js rename to src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.columns.js index 86eff88c8..19e9c5ec3 100644 --- a/src/components/BacktestOptionsPanel/BacktestOptions.History.columns.js +++ b/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.columns.js @@ -1,8 +1,8 @@ import React from 'react' import { Button } from '@ufx-ui/core' import { Icon } from 'react-fa' -import { renderDate } from '../../util/ui' -import FavoriteIcon from '../../ui/Icons/FavoriteIcon' +import { renderDate } from '../../../util/ui' +import FavoriteIcon from '../../../ui/Icons/FavoriteIcon' export default ({ toggleFavorite, formatTime, removeBacktest }) => [ { diff --git a/src/components/BacktestOptionsPanel/BacktestOptions.History.js b/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js similarity index 81% rename from src/components/BacktestOptionsPanel/BacktestOptions.History.js rename to src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js index 89f7631ee..eef7e9115 100644 --- a/src/components/BacktestOptionsPanel/BacktestOptions.History.js +++ b/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js @@ -5,16 +5,16 @@ import _isEmpty from 'lodash/isEmpty' import { useTranslation } from 'react-i18next' import { VirtualTable } from '@ufx-ui/core' import { useDispatch, useSelector } from 'react-redux' -import FavoriteIcon from '../../ui/Icons/FavoriteIcon' -import PanelButton from '../../ui/Panel/Panel.Button' -import useToggle from '../../hooks/useToggle' -import { getCurrentStrategyBacktestsList } from '../../redux/selectors/ws' -import WSActions from '../../redux/actions/ws' -import WSTypes from '../../redux/constants/ws' -import BacktestOptionsHistoryColumns from './BacktestOptions.History.columns' -import { getFormatTimeFn } from '../../redux/selectors/ui' +import FavoriteIcon from '../../../ui/Icons/FavoriteIcon' +import PanelButton from '../../../ui/Panel/Panel.Button' +import useToggle from '../../../hooks/useToggle' +import { getCurrentStrategyBacktestsList } from '../../../redux/selectors/ws' +import WSActions from '../../../redux/actions/ws' +import WSTypes from '../../../redux/constants/ws' +import BacktestHistoryListColumns from './BacktestHistoryList.columns' +import { getFormatTimeFn } from '../../../redux/selectors/ui' -const BacktestOptionsHistory = () => { +const BacktestHistoryList = () => { const [isFavoriteSelected, toggleFavoritesList] = useToggle(false) const { t } = useTranslation() @@ -57,7 +57,7 @@ const BacktestOptionsHistory = () => { } const columns = useMemo( - () => BacktestOptionsHistoryColumns({ + () => BacktestHistoryListColumns({ formatTime, toggleFavorite, removeBacktest, @@ -108,4 +108,4 @@ const BacktestOptionsHistory = () => { ) } -export default BacktestOptionsHistory +export default BacktestHistoryList diff --git a/src/components/BacktestOptionsPanel/BacktestOptions.NewTest.js b/src/components/BacktestOptionsPanel/tabs/NewTest.js similarity index 94% rename from src/components/BacktestOptionsPanel/BacktestOptions.NewTest.js rename to src/components/BacktestOptionsPanel/tabs/NewTest.js index bb2eba2b9..c12d06fdf 100644 --- a/src/components/BacktestOptionsPanel/BacktestOptions.NewTest.js +++ b/src/components/BacktestOptionsPanel/tabs/NewTest.js @@ -6,14 +6,14 @@ import { Checkbox, Button, Spinner, Intent, } from '@ufx-ui/core' import cx from 'clsx' -import AmountInput from '../OrderForm/FieldComponents/input.amount' -import DateInput from '../OrderForm/FieldComponents/input.date' -import TimeFrameDropdown from '../TimeFrameDropdown' -import Dropdown from '../../ui/Dropdown' -import { STRATEGY_OPTIONS_KEYS } from '../StrategyEditor/StrategyEditor.helpers' -import { STRATEGY_SHAPE } from '../../constants/prop-types-shapes' -import ClockIcon from '../../ui/Icons/ClockIcon' -import CalendarIcon from '../../ui/Icons/CalendarIcon' +import AmountInput from '../../OrderForm/FieldComponents/input.amount' +import DateInput from '../../OrderForm/FieldComponents/input.date' +import TimeFrameDropdown from '../../TimeFrameDropdown' +import Dropdown from '../../../ui/Dropdown' +import { STRATEGY_OPTIONS_KEYS } from '../../StrategyEditor/StrategyEditor.helpers' +import { STRATEGY_SHAPE } from '../../../constants/prop-types-shapes' +import ClockIcon from '../../../ui/Icons/ClockIcon' +import CalendarIcon from '../../../ui/Icons/CalendarIcon' const MAX_DATE = new Date() From 23ee4e5b99c69bd6a279266030a4be23a416038e Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Wed, 16 Aug 2023 15:39:12 +0300 Subject: [PATCH 09/47] BT history: launch to details tab --- .../BacktestOptionsPanel.js | 47 +++++++++++++++---- .../tabs/BacktestDetails.js | 12 +++++ .../tabs/BacktestHistoryList.js | 18 +++---- 3 files changed, 57 insertions(+), 20 deletions(-) create mode 100644 src/components/BacktestOptionsPanel/tabs/BacktestDetails.js diff --git a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js index 751a4dab7..f76e1ba30 100644 --- a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js +++ b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js @@ -1,11 +1,12 @@ import React, { useState } from 'react' import { useTranslation } from 'react-i18next' import _toUpper from 'lodash/toUpper' -import _bind from 'lodash/bind' +import { Icon } from 'react-fa' import BacktestOptionsNewTest from './tabs/NewTest' import HistoryButton from '../../ui/HistoryButton/HistoryButton' import BacktestHistoryList from './tabs/BacktestHistoryList' import PanelButton from '../../ui/Panel/Panel.Button' +import BacktestDetails from './tabs/BacktestDetails' import './style.css' @@ -17,19 +18,44 @@ const BACKTEST_OPTIONS_TABS = { const BacktestOptionsPanel = (props) => { const [activeTab, setActiveTab] = useState(BACKTEST_OPTIONS_TABS.NEW_TEST) + const [backtestDetails, setBacktestDetails] = useState(null) const { t } = useTranslation() + const onNewTestTabClick = () => setActiveTab(BACKTEST_OPTIONS_TABS.NEW_TEST) + const onHistoryTabClick = () => { + if (activeTab !== BACKTEST_OPTIONS_TABS.NEW_TEST) { + return + } + setActiveTab(BACKTEST_OPTIONS_TABS.HISTORY_LIST) + } + const onBackButtonClick = () => setActiveTab(BACKTEST_OPTIONS_TABS.HISTORY_LIST) + + const onBacktestRowClick = ({ rowData }) => { + setBacktestDetails(rowData) + setActiveTab(BACKTEST_OPTIONS_TABS.HISTORY_DETAILS) + } + return (
- + {activeTab === BACKTEST_OPTIONS_TABS.HISTORY_DETAILS ? ( + } + /> + ) : ( + + )} +
@@ -37,7 +63,10 @@ const BacktestOptionsPanel = (props) => { )} {activeTab === BACKTEST_OPTIONS_TABS.HISTORY_LIST && ( - + + )} + {activeTab === BACKTEST_OPTIONS_TABS.HISTORY_DETAILS && ( + )}
) diff --git a/src/components/BacktestOptionsPanel/tabs/BacktestDetails.js b/src/components/BacktestOptionsPanel/tabs/BacktestDetails.js new file mode 100644 index 000000000..5e3dd26b8 --- /dev/null +++ b/src/components/BacktestOptionsPanel/tabs/BacktestDetails.js @@ -0,0 +1,12 @@ +import React from 'react' +import PropTypes from 'prop-types' + +const BacktestDetails = ({ backtest }) => { + return ( + <> +
+ + ) +} + +export default BacktestDetails diff --git a/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js b/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js index eef7e9115..4f52c76ba 100644 --- a/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js +++ b/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js @@ -1,4 +1,5 @@ import React, { useCallback, useMemo } from 'react' +import PropTypes from 'prop-types' import _toUpper from 'lodash/toUpper' import _filter from 'lodash/filter' import _isEmpty from 'lodash/isEmpty' @@ -14,7 +15,7 @@ import WSTypes from '../../../redux/constants/ws' import BacktestHistoryListColumns from './BacktestHistoryList.columns' import { getFormatTimeFn } from '../../../redux/selectors/ui' -const BacktestHistoryList = () => { +const BacktestHistoryList = ({ onBacktestRowClick }) => { const [isFavoriteSelected, toggleFavoritesList] = useToggle(false) const { t } = useTranslation() @@ -47,15 +48,6 @@ const BacktestHistoryList = () => { [dispatch], ) - const onRowClick = ({ rowData }) => { - dispatch( - WSActions.send({ - alias: WSTypes.ALIAS_DATA_SERVER, - data: ['get.bt.history.details', rowData.executionId], - }), - ) - } - const columns = useMemo( () => BacktestHistoryListColumns({ formatTime, @@ -98,7 +90,7 @@ const BacktestHistoryList = () => { columns={columns} defaultSortDirection='DESC' defaultSortBy='dataKey' - onRowClick={onRowClick} + onRowClick={onBacktestRowClick} headerHeight={0} rowHeight={30} /> @@ -108,4 +100,8 @@ const BacktestHistoryList = () => { ) } +BacktestHistoryList.propTypes = { + onBacktestRowClick: PropTypes.func.isRequired, +} + export default BacktestHistoryList From fbdb4f05f75953e9f41790615ee4eaac024035a1 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Wed, 16 Aug 2023 19:35:14 +0300 Subject: [PATCH 10/47] BT history: details layout" --- public/locales/en-US/translations.json | 7 ++- .../BacktestOptionsPanel/style.scss | 14 ++++++ .../tabs/BacktestDetails.fields.js | 49 ++++++++++++++++++ .../tabs/BacktestDetails.js | 50 ++++++++++++++++++- .../tabs/BacktestHistoryList.columns.js | 6 ++- .../tabs/BacktestHistoryList.js | 5 +- 6 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 src/components/BacktestOptionsPanel/tabs/BacktestDetails.fields.js diff --git a/public/locales/en-US/translations.json b/public/locales/en-US/translations.json index 33f9baf03..66b36f5f5 100644 --- a/public/locales/en-US/translations.json +++ b/public/locales/en-US/translations.json @@ -872,7 +872,12 @@ "lastYear": "Last year", "lastXyears": "Last {{amount}} years", "elapsedTime": "Elapsed time", - "noHistoryBacktests": "No backtests in history" + "noHistoryBacktests": "No backtests in history", + "executedAt": "Executed at", + "period": "Period", + "tradingPair": "Trading Pair", + "source": "Source", + "candlesSource": "Candles ({{timeframe}}),\n\n Seed count = {{candleSeed}}" }, "ui": { "ok": "Okay", diff --git a/src/components/BacktestOptionsPanel/style.scss b/src/components/BacktestOptionsPanel/style.scss index c7a582426..2f583e72b 100644 --- a/src/components/BacktestOptionsPanel/style.scss +++ b/src/components/BacktestOptionsPanel/style.scss @@ -20,6 +20,20 @@ } } + .details-field { + margin-bottom: 10px; + font-size: 15px; + + .label { + font-weight: 400; + margin-bottom: 3px; + } + + .value { + font-weight: 700; + } + } + .backtest-history-table { @extend .item; width: 100%; diff --git a/src/components/BacktestOptionsPanel/tabs/BacktestDetails.fields.js b/src/components/BacktestOptionsPanel/tabs/BacktestDetails.fields.js new file mode 100644 index 000000000..c6f25107c --- /dev/null +++ b/src/components/BacktestOptionsPanel/tabs/BacktestDetails.fields.js @@ -0,0 +1,49 @@ +import { renderDate } from '../../../util/ui' + +const getBacktestSourceField = (rowData, t) => { + const { includeCandles } = rowData + if (includeCandles) { + return t('strategyEditor.candlesSource', { + timeframe: t(`time.${rowData.timeframe}`), + candleSeed: rowData.candleSeed, + }) + } + return t('strategyEditor.trades') +} + +export default ({ + t, rowData = {}, formatTime, quoteCcy, +}) => [ + { + label: t('strategyEditor.executedAt'), + value: renderDate(rowData.timestamp, formatTime), + }, + { + label: t('strategyEditor.tradingPair'), + value: rowData.symbol, + }, + { + label: t('strategyEditor.startDate'), + value: renderDate(rowData.start, formatTime), + }, + { + label: t('strategyEditor.endDate'), + value: renderDate(rowData.end, formatTime), + }, + { + label: t('strategyEditor.source'), + value: getBacktestSourceField(rowData, t), + }, + { + label: t('strategySettingsModal.capitalAllocationLabel'), + value: `${rowData.capitalAllocation} ${quoteCcy}`, + }, + { + label: t('strategySettingsModal.stopLoss'), + value: `${rowData.stopLossPerc}%`, + }, + { + label: t('strategySettingsModal.maxDrawdown'), + value: `${rowData.maxDrawdownPerc}%`, + }, +] diff --git a/src/components/BacktestOptionsPanel/tabs/BacktestDetails.js b/src/components/BacktestOptionsPanel/tabs/BacktestDetails.js index 5e3dd26b8..bf5b7e078 100644 --- a/src/components/BacktestOptionsPanel/tabs/BacktestDetails.js +++ b/src/components/BacktestOptionsPanel/tabs/BacktestDetails.js @@ -1,12 +1,58 @@ -import React from 'react' +import React, { useMemo } from 'react' import PropTypes from 'prop-types' +import _map from 'lodash/map' +import { useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { reduxSelectors } from '@ufx-ui/bfx-containers' +import { getSymbols } from '@ufx-ui/utils' +import { getFormatTimeFn } from '../../../redux/selectors/ui' +import getBacktestDetailsFields from './BacktestDetails.fields' + +const { getCurrencySymbolMemo } = reduxSelectors const BacktestDetails = ({ backtest }) => { + const formatTime = useSelector(getFormatTimeFn) + const getCurrencySymbol = useSelector(getCurrencySymbolMemo) + const [, quote] = getSymbols(backtest.symbol) + const quoteCcy = getCurrencySymbol(quote) + + const { t } = useTranslation() + + const backtestDetails = useMemo( + () => getBacktestDetailsFields({ + t, + rowData: backtest, + formatTime, + quoteCcy, + }), + [backtest, t, formatTime, quoteCcy], + ) return ( <> -
+ {_map(backtestDetails, ({ label, value }) => { + return ( +
+
{label}
+
{value}
+
+ ) + })} ) } +BacktestDetails.propTypes = { + backtest: PropTypes.shape({ + symbol: PropTypes.string, + timestamp: PropTypes.number, + start: PropTypes.number, + end: PropTypes.number, + timeframe: PropTypes.string, + candleSeed: PropTypes.number, + capitalAllocation: PropTypes.number, + stopLossPerc: PropTypes.number, + maxDrawdownPerc: PropTypes.number, + }).isRequired, +} + export default BacktestDetails diff --git a/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.columns.js b/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.columns.js index 19e9c5ec3..e02d92dc0 100644 --- a/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.columns.js +++ b/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.columns.js @@ -4,7 +4,9 @@ import { Icon } from 'react-fa' import { renderDate } from '../../../util/ui' import FavoriteIcon from '../../../ui/Icons/FavoriteIcon' -export default ({ toggleFavorite, formatTime, removeBacktest }) => [ +export default ({ + toggleFavorite, formatTime, removeBacktest, t, +}) => [ { dataKey: 'executionId', width: 25, @@ -31,13 +33,13 @@ export default ({ toggleFavorite, formatTime, removeBacktest }) => [ }, { dataKey: 'timestamp', + label: t('strategyEditor.executedAt'), width: 100, flexGrow: 1, style: { justifyContent: 'center' }, cellRenderer: ({ rowData = {} }) => { return renderDate(rowData.timestamp, formatTime) }, - disableSort: true, }, { dataKey: 'id', diff --git a/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js b/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js index 4f52c76ba..63aeb72ba 100644 --- a/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js +++ b/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js @@ -53,8 +53,9 @@ const BacktestHistoryList = ({ onBacktestRowClick }) => { formatTime, toggleFavorite, removeBacktest, + t, }), - [toggleFavorite, formatTime, removeBacktest], + [toggleFavorite, formatTime, removeBacktest, t], ) const data = useMemo(() => { @@ -91,8 +92,8 @@ const BacktestHistoryList = ({ onBacktestRowClick }) => { defaultSortDirection='DESC' defaultSortBy='dataKey' onRowClick={onBacktestRowClick} - headerHeight={0} rowHeight={30} + disableColumnsResizing /> )}
From 36e0519fd42e46c7d29cc03e7c17e14134477a37 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Wed, 16 Aug 2023 19:35:46 +0300 Subject: [PATCH 11/47] fix prop validation --- .../tabs/StrategySettings.Execution.js | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/modals/Strategy/StrategySettingsModal/tabs/StrategySettings.Execution.js b/src/modals/Strategy/StrategySettingsModal/tabs/StrategySettings.Execution.js index d4f658b00..2d71bc062 100644 --- a/src/modals/Strategy/StrategySettingsModal/tabs/StrategySettings.Execution.js +++ b/src/modals/Strategy/StrategySettingsModal/tabs/StrategySettings.Execution.js @@ -67,9 +67,7 @@ const ExecutionTab = ({ {t('strategySettingsModal.capitalAllocationLabel')}

-

- {t('strategySettingsModal.capitalAllocationHelp')} -

+

{t('strategySettingsModal.capitalAllocationHelp')}

-

- {t('strategySettingsModal.stopLossHelp')} -

+

{t('strategySettingsModal.stopLossHelp')}

-

- {t('strategySettingsModal.maximumDrawdownHelp')} -

+

{t('strategySettingsModal.maximumDrawdownHelp')}

Date: Wed, 16 Aug 2023 19:39:31 +0300 Subject: [PATCH 12/47] BT history: fix styles --- src/components/BacktestOptionsPanel/BacktestOptionsPanel.js | 3 ++- src/components/BacktestOptionsPanel/style.scss | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js index f76e1ba30..5cd890a7f 100644 --- a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js +++ b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js @@ -43,13 +43,14 @@ const BacktestOptionsPanel = (props) => { onClick={onBackButtonClick} text={_toUpper(t('ui.goBack'))} isActive={false} - icon={} + icon={} /> ) : ( } /> )} diff --git a/src/components/BacktestOptionsPanel/style.scss b/src/components/BacktestOptionsPanel/style.scss index 2f583e72b..05315f4a6 100644 --- a/src/components/BacktestOptionsPanel/style.scss +++ b/src/components/BacktestOptionsPanel/style.scss @@ -7,6 +7,7 @@ border-radius: 5px; height: 100%; padding: 16px; + min-width: 275px; .item { margin-bottom: 10px; @@ -27,6 +28,7 @@ .label { font-weight: 400; margin-bottom: 3px; + color: var(--shade-5); } .value { @@ -51,6 +53,10 @@ @extend .item; display: flex; justify-content: space-between; + + .icon { + font-size: 14px; + } } &__icn-selector-container { From e4804a1d91389e7beb8176c61e6f9d5a09179073 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Thu, 17 Aug 2023 16:19:47 +0300 Subject: [PATCH 13/47] BT history: results --- public/locales/en-US/translations.json | 3 +- .../BacktestOptionsPanel.js | 46 ++++++------ .../BacktestResultsOptionsPanel.js | 75 ++++++++++++++++--- .../BacktestOptionsPanel/style.scss | 1 + .../tabs/BacktestDetails.js | 45 +++++++---- .../StrategyEditor/tabs/BacktestTab.js | 46 ++++++++++-- .../StrategyOptionsPanel/style.scss | 9 ++- src/redux/reducers/ws/backtestHistory.js | 20 ++--- src/redux/selectors/ws/get_backtest_by_id.js | 10 +++ .../ws/get_current_strategy_backtests_list.js | 4 +- src/redux/selectors/ws/index.js | 2 + src/util/ui.js | 4 +- 12 files changed, 196 insertions(+), 69 deletions(-) create mode 100644 src/redux/selectors/ws/get_backtest_by_id.js diff --git a/public/locales/en-US/translations.json b/public/locales/en-US/translations.json index 66b36f5f5..f95b28d76 100644 --- a/public/locales/en-US/translations.json +++ b/public/locales/en-US/translations.json @@ -877,7 +877,8 @@ "period": "Period", "tradingPair": "Trading Pair", "source": "Source", - "candlesSource": "Candles ({{timeframe}}),\n\n Seed count = {{candleSeed}}" + "candlesSource": "Candles ({{timeframe}}),\n\n Seed count = {{candleSeed}}", + "backtestHistoryResults": "Backtest results for" }, "ui": { "ok": "Okay", diff --git a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js index 5cd890a7f..eb0dfa629 100644 --- a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js +++ b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js @@ -1,5 +1,6 @@ -import React, { useState } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' +import PropTypes from 'prop-types' import _toUpper from 'lodash/toUpper' import { Icon } from 'react-fa' import BacktestOptionsNewTest from './tabs/NewTest' @@ -7,38 +8,33 @@ import HistoryButton from '../../ui/HistoryButton/HistoryButton' import BacktestHistoryList from './tabs/BacktestHistoryList' import PanelButton from '../../ui/Panel/Panel.Button' import BacktestDetails from './tabs/BacktestDetails' +import { BACKTEST_TAB_SECTIONS } from '../StrategyEditor/tabs/BacktestTab' import './style.css' -const BACKTEST_OPTIONS_TABS = { - NEW_TEST: 'NEW TEST', - HISTORY_LIST: 'HISTORY_LIST', - HISTORY_DETAILS: 'HISTORY_DETAILS', -} - const BacktestOptionsPanel = (props) => { - const [activeTab, setActiveTab] = useState(BACKTEST_OPTIONS_TABS.NEW_TEST) - const [backtestDetails, setBacktestDetails] = useState(null) + const { setActiveSection, activeSection, setBtHistoryId } = props + const { t } = useTranslation() - const onNewTestTabClick = () => setActiveTab(BACKTEST_OPTIONS_TABS.NEW_TEST) + const onNewTestTabClick = () => setActiveSection(BACKTEST_TAB_SECTIONS.NEW_BT) const onHistoryTabClick = () => { - if (activeTab !== BACKTEST_OPTIONS_TABS.NEW_TEST) { + if (activeSection !== BACKTEST_TAB_SECTIONS.NEW_BT) { return } - setActiveTab(BACKTEST_OPTIONS_TABS.HISTORY_LIST) + setActiveSection(BACKTEST_TAB_SECTIONS.HISTORY_BT_LIST) } - const onBackButtonClick = () => setActiveTab(BACKTEST_OPTIONS_TABS.HISTORY_LIST) + const onBackButtonClick = () => setActiveSection(BACKTEST_TAB_SECTIONS.HISTORY_BT_LIST) const onBacktestRowClick = ({ rowData }) => { - setBacktestDetails(rowData) - setActiveTab(BACKTEST_OPTIONS_TABS.HISTORY_DETAILS) + setBtHistoryId(rowData.executionId) + setActiveSection(BACKTEST_TAB_SECTIONS.HISTORY_BT_DETAILS) } return (
- {activeTab === BACKTEST_OPTIONS_TABS.HISTORY_DETAILS ? ( + {activeSection === BACKTEST_TAB_SECTIONS.HISTORY_BT_DETAILS ? ( { } /> )}
- {activeTab === BACKTEST_OPTIONS_TABS.NEW_TEST && ( + {activeSection === BACKTEST_TAB_SECTIONS.NEW_BT && ( )} - {activeTab === BACKTEST_OPTIONS_TABS.HISTORY_LIST && ( + {activeSection === BACKTEST_TAB_SECTIONS.HISTORY_BT_LIST && ( )} - {activeTab === BACKTEST_OPTIONS_TABS.HISTORY_DETAILS && ( - + {activeSection === BACKTEST_TAB_SECTIONS.HISTORY_BT_DETAILS && ( + )}
) } +BacktestOptionsPanel.propTypes = { + activeSection: PropTypes.string.isRequired, + setActiveSection: PropTypes.func.isRequired, + setBtHistoryId: PropTypes.func.isRequired, +} + export default BacktestOptionsPanel diff --git a/src/components/BacktestOptionsPanel/BacktestResultsOptionsPanel.js b/src/components/BacktestOptionsPanel/BacktestResultsOptionsPanel.js index 0315efb9c..b3052b084 100644 --- a/src/components/BacktestOptionsPanel/BacktestResultsOptionsPanel.js +++ b/src/components/BacktestOptionsPanel/BacktestResultsOptionsPanel.js @@ -1,22 +1,70 @@ import React from 'react' +import _toUpper from 'lodash/toUpper' import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import { Icon } from 'react-fa' import Button from '../../ui/Button' +import { getFormatTimeFn } from '../../redux/selectors/ui' +import { renderDate } from '../../util/ui' +import PanelButton from '../../ui/Panel/Panel.Button' +import { BACKTEST_TAB_SECTIONS } from '../StrategyEditor/tabs/BacktestTab' -const BacktestResultsOptionsPanel = ({ showFullscreenChart, openNewTest }) => { +const BacktestResultsOptionsPanel = ({ + showFullscreenChart, + openNewTest, + backtestTimestamp, + activeSection, + setActiveSection, +}) => { const { t } = useTranslation() + const formatTime = useSelector(getFormatTimeFn) + + const onBackButonClick = () => setActiveSection(BACKTEST_TAB_SECTIONS.HISTORY_BT_DETAILS) + const onNewTestButtonClick = () => { + if (activeSection === BACKTEST_TAB_SECTIONS.HISTORY_BT_RESULTS) { + setActiveSection(BACKTEST_TAB_SECTIONS.NEW_BT) + return + } + openNewTest() + } + + const showBtHistoryResults = activeSection === BACKTEST_TAB_SECTIONS.HISTORY_BT_RESULTS return (
-
-

{t('strategyEditor.backtestSuccessful')}

-
+ {showBtHistoryResults ? ( +
+ } + /> +

+ {t('strategyEditor.backtestHistoryResults')} +   + {renderDate(backtestTimestamp, formatTime, false)} +

+
+ ) : ( +
+

{t('strategyEditor.backtestSuccessful')}

+
+ )} + ) } BacktestDetails.propTypes = { - backtest: PropTypes.shape({ - symbol: PropTypes.string, - timestamp: PropTypes.number, - start: PropTypes.number, - end: PropTypes.number, - timeframe: PropTypes.string, - candleSeed: PropTypes.number, - capitalAllocation: PropTypes.number, - stopLossPerc: PropTypes.number, - maxDrawdownPerc: PropTypes.number, - }).isRequired, + btHistoryId: PropTypes.string.isRequired, + setActiveSection: PropTypes.func.isRequired, } export default BacktestDetails diff --git a/src/components/StrategyEditor/tabs/BacktestTab.js b/src/components/StrategyEditor/tabs/BacktestTab.js index 90e6fd7d4..08e8a59ba 100644 --- a/src/components/StrategyEditor/tabs/BacktestTab.js +++ b/src/components/StrategyEditor/tabs/BacktestTab.js @@ -24,6 +24,14 @@ import { import BacktestResultsOptionsPanel from '../../BacktestOptionsPanel/BacktestResultsOptionsPanel' import BacktestProgressBar from '../../BacktestProgressBar' +export const BACKTEST_TAB_SECTIONS = { + NEW_BT: 'NEW_BT', + NEW_BT_RESULTS: 'NEW_BT _RESULTS', + HISTORY_BT_LIST: 'HISTORY_BT_LIST', + HISTORY_BT_DETAILS: 'HISTORY_BT_DETAILS', + HISTORY_BT_RESULTS: 'HISTORY_BT_RESULTS', +} + const BacktestTab = (props) => { const { results, @@ -36,33 +44,50 @@ const BacktestTab = (props) => { openNewTest, } = props const { t } = useTranslation() + + const [activeSection, setActiveSection] = useState( + BACKTEST_TAB_SECTIONS.NEW_BT, + ) + const [btHistoryId, setBtHistoryId] = useState(null) const [layoutConfig, setLayoutConfig] = useState() + const [fullscreenChart, , showFullscreenChart, hideFullscreenChart] = useToggle(false) const { - finished = false, loading, progressPerc, gid, + finished = false, loading, progressPerc, gid, timestamp, } = results // const loading = true const positions = results?.strategy?.closedPositions const trades = useMemo(() => prepareChartTrades(positions), [positions]) + const showBacktestResults = activeSection === BACKTEST_TAB_SECTIONS.NEW_BT_RESULTS + || activeSection === BACKTEST_TAB_SECTIONS.HISTORY_BT_RESULTS useEffect(() => { - if (!finished) { + if (!showBacktestResults) { setLayoutConfig(BACKTEST_LAYOUT_CONFIG_NO_DATA) return } setLayoutConfig(LAYOUT_CONFIG) + }, [showBacktestResults]) + + useEffect(() => { + if (finished) { + setActiveSection(BACKTEST_TAB_SECTIONS.NEW_BT_RESULTS) + } }, [finished]) const renderGridComponents = useCallback( (i) => { switch (i) { case COMPONENTS_KEYS.OPTIONS: - return finished ? ( + return showBacktestResults ? ( ) : ( { saveStrategyOptions={saveStrategyOptions} isLoading={loading} onCancelProcess={onCancelProcess} + activeSection={activeSection} + setActiveSection={setActiveSection} + setBtHistoryId={setBtHistoryId} + btHistoryId={btHistoryId} /> ) @@ -112,14 +141,17 @@ const BacktestTab = (props) => { } }, [ - finished, + showBacktestResults, showFullscreenChart, openNewTest, + timestamp, + activeSection, strategy, onBacktestStart, saveStrategyOptions, loading, onCancelProcess, + btHistoryId, indicators, markets, fullscreenChart, @@ -137,7 +169,7 @@ const BacktestTab = (props) => { layoutConfig={layoutConfig} renderGridComponents={renderGridComponents} /> - {!finished && ( + {!showBacktestResults && (
{loading ? ( @@ -158,11 +190,13 @@ BacktestTab.propTypes = { loading: PropTypes.bool, strategy: PropTypes.shape({ closedPositions: PropTypes.oneOfType([ - PropTypes.arrayOf(PropTypes.string), PropTypes.object, + PropTypes.arrayOf(PropTypes.string), + PropTypes.object, ]), }), progressPerc: PropTypes.number, gid: PropTypes.number, + timestamp: PropTypes.number, }).isRequired, onCancelProcess: PropTypes.func.isRequired, strategy: PropTypes.shape(STRATEGY_SHAPE).isRequired, diff --git a/src/components/StrategyOptionsPanel/style.scss b/src/components/StrategyOptionsPanel/style.scss index 56a07bad5..ef37163d6 100644 --- a/src/components/StrategyOptionsPanel/style.scss +++ b/src/components/StrategyOptionsPanel/style.scss @@ -21,9 +21,14 @@ display: flex; justify-content: space-between; align-items: center; - width: 250px; + min-width: 250px; border-radius: 5px; padding: 10px; + gap: 15px; + + .icon { + font-size: 14px; + } } &__left-container { @@ -95,7 +100,7 @@ margin-bottom: 0; } - &__description{ + &__description { padding-left: 11px; } diff --git a/src/redux/reducers/ws/backtestHistory.js b/src/redux/reducers/ws/backtestHistory.js index 2e72ff021..96cf5ecee 100644 --- a/src/redux/reducers/ws/backtestHistory.js +++ b/src/redux/reducers/ws/backtestHistory.js @@ -6,7 +6,7 @@ import types from '../../constants/ws' const getInitialState = () => ({ mappedKeysByStrategyIds: {}, - backtestResults: {}, + backtests: {}, }) const reducer = (state = getInitialState(), action = {}) => { @@ -17,8 +17,8 @@ const reducer = (state = getInitialState(), action = {}) => { const { strategyId, backtestsList } = payload const mappedBacktestKeys = [] - const backtestResults = { - ...state.backtestResults, + const backtests = { + ...state.backtests, } _forEach(backtestsList, (_backtest) => { @@ -29,7 +29,7 @@ const reducer = (state = getInitialState(), action = {}) => { // normalize types after SQLite backtest.isFavorite = !!isFavorite - backtestResults[executionId] = backtest + backtests[executionId] = backtest }) return { @@ -38,19 +38,19 @@ const reducer = (state = getInitialState(), action = {}) => { ...state.mappedKeysByStrategyIds, [strategyId]: mappedBacktestKeys, }, - backtestResults, + backtests, } } case types.BACKTEST_SET_FAVORITE: { const { backtestId, isFavorite } = payload - const backtest = _get(state, `backtestResults.${backtestId}`, {}) + const backtest = _get(state, `backtests.${backtestId}`, {}) return { ...state, - backtestResults: { - ...state.backtestResults, + backtests: { + ...state.backtests, [backtestId]: { ...backtest, isFavorite: !!isFavorite, @@ -62,7 +62,7 @@ const reducer = (state = getInitialState(), action = {}) => { case types.BACKTEST_REMOVE: { const { backtestId } = payload - const backtest = _get(state, `backtestResults.${backtestId}`, {}) + const backtest = _get(state, `backtests.${backtestId}`, {}) const { strategyId } = backtest const backtestKeys = state.mappedKeysByStrategyIds[strategyId] const filteredKeys = _filter(backtestKeys, (id) => backtestId !== id) @@ -73,7 +73,7 @@ const reducer = (state = getInitialState(), action = {}) => { ...state.mappedKeysByStrategyIds, [strategyId]: filteredKeys, }, - backtestResults: _omit(state.backtestResults, backtestId), + backtests: _omit(state.backtests, backtestId), } } diff --git a/src/redux/selectors/ws/get_backtest_by_id.js b/src/redux/selectors/ws/get_backtest_by_id.js new file mode 100644 index 000000000..e0a1def60 --- /dev/null +++ b/src/redux/selectors/ws/get_backtest_by_id.js @@ -0,0 +1,10 @@ +import { createSelector } from 'reselect' +import _get from 'lodash/get' +import { getBacktestHistory } from '.' + +const getBacktestById = (executionId) => createSelector([getBacktestHistory], (backtestHistory) => { + const { backtests } = backtestHistory + return _get(backtests, executionId, {}) +}) + +export default getBacktestById diff --git a/src/redux/selectors/ws/get_current_strategy_backtests_list.js b/src/redux/selectors/ws/get_current_strategy_backtests_list.js index f6c11fed8..8a2851805 100644 --- a/src/redux/selectors/ws/get_current_strategy_backtests_list.js +++ b/src/redux/selectors/ws/get_current_strategy_backtests_list.js @@ -8,14 +8,14 @@ import { getBacktestHistory } from '.' const getCurrentStrategyBacktestsList = createSelector( [getCurrentStrategy, getBacktestHistory], ({ id }, backtestHistory) => { - const { mappedKeysByStrategyIds, backtestResults } = backtestHistory + const { mappedKeysByStrategyIds, backtests } = backtestHistory const mappedKeys = _get(mappedKeysByStrategyIds, id, null) if (!_isArray(mappedKeys)) { return mappedKeys } return _map(mappedKeys, (executionId) => { - return _get(backtestResults, executionId, {}) + return _get(backtests, executionId, {}) }) }, ) diff --git a/src/redux/selectors/ws/index.js b/src/redux/selectors/ws/index.js index 32a69c5f1..a1b34f1d9 100644 --- a/src/redux/selectors/ws/index.js +++ b/src/redux/selectors/ws/index.js @@ -32,6 +32,7 @@ import getBacktestState from './get_backtest_state' import getBacktestData from './get_backtest_data' import getBacktestResults from './get_backtest_results' import getBacktestHistory from './get_backtest_history' +import getBacktestById from './get_backtest_by_id' import getCurrentStrategyBacktestsList from './get_current_strategy_backtests_list' import { @@ -81,6 +82,7 @@ export { getBacktestResults, getBacktestHistory, getCurrentStrategyBacktestsList, + getBacktestById, apiClientConnected, apiClientConnecting, diff --git a/src/util/ui.js b/src/util/ui.js index 91f2afaee..a644c1770 100644 --- a/src/util/ui.js +++ b/src/util/ui.js @@ -88,13 +88,13 @@ export const readJSONFile = () => new Promise((resolve, reject) => { input.remove() }) -export const renderDate = (rawDate, formatTime) => { +export const renderDate = (rawDate, formatTime, shouldTruncate = true) => { if (!rawDate) { return '--' } const date = formatTime(rawDate) - return defaultCellRenderer(date) + return shouldTruncate ? defaultCellRenderer(date) : date } export const processDateForCSV = (rawDate, formatTime) => { From be620fd809cda36438cb54db3bc0f1aef05b9da8 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Thu, 17 Aug 2023 16:29:12 +0300 Subject: [PATCH 14/47] Revert "Merge pull request #394 from dmytroshch/fix/backtest-clear" This reverts commit 755f7dd4a7234957207f72907e02184a0ba4f884, reversing changes made to 7e41af40fa421e5f1421ae0b652169edd41d9e83. --- public/locales/en-US/translations.json | 2 - .../StrategyEditor.container.js | 3 + .../StrategyPerfomanceMetrics.js | 3 +- .../ClearBacktestResultsModal.js | 60 ------------------- .../ClearBacktestResultsModal/index.js | 3 - src/pages/Strategies/Strategies.container.js | 3 +- src/pages/Strategies/Strategies.js | 28 +-------- 7 files changed, 7 insertions(+), 95 deletions(-) delete mode 100644 src/modals/Strategy/ClearBacktestResultsModal/ClearBacktestResultsModal.js delete mode 100644 src/modals/Strategy/ClearBacktestResultsModal/index.js diff --git a/public/locales/en-US/translations.json b/public/locales/en-US/translations.json index f95b28d76..13823c31e 100644 --- a/public/locales/en-US/translations.json +++ b/public/locales/en-US/translations.json @@ -732,8 +732,6 @@ "updateCreds": "Update credentials" }, "strategyEditor": { - "clearBacktestResultsModalTitle": "Confirmation Required", - "clearBacktestResultsModalText": "If you choose to continue, the current backtest results will be cleared. Do you wish to proceed?", "unsavedChanges": "Unsaved changes", "unsavedChangesModalTitle": "Do you need to save changes in the strategy before closing?", "cancelThisProcess": "Cancel this process", diff --git a/src/components/StrategyEditor/StrategyEditor.container.js b/src/components/StrategyEditor/StrategyEditor.container.js index 7c6781363..55b309d53 100644 --- a/src/components/StrategyEditor/StrategyEditor.container.js +++ b/src/components/StrategyEditor/StrategyEditor.container.js @@ -56,6 +56,9 @@ const mapDispatchToProps = (dispatch) => ({ gaCreateStrategy: () => { dispatch(GAActions.createStrategy()) }, + clearBacktestOptions: () => { + dispatch(WSActions.resetBacktestData()) + }, dsExecuteLiveStrategy: ({ authToken, label, diff --git a/src/components/StrategyPerfomanceMetrics/StrategyPerfomanceMetrics.js b/src/components/StrategyPerfomanceMetrics/StrategyPerfomanceMetrics.js index f9ba331cf..4a696619d 100644 --- a/src/components/StrategyPerfomanceMetrics/StrategyPerfomanceMetrics.js +++ b/src/components/StrategyPerfomanceMetrics/StrategyPerfomanceMetrics.js @@ -98,7 +98,7 @@ StrategyPerfomanceMetrics.propTypes = { returnPerc: PropTypes.string, drawdown: PropTypes.string, }), - isExecuting: PropTypes.bool, + isExecuting: PropTypes.bool.isRequired, isBacktest: PropTypes.bool, startedOn: PropTypes.number, } @@ -125,7 +125,6 @@ StrategyPerfomanceMetrics.defaultProps = { }, isBacktest: false, startedOn: 0, - isExecuting: false, } export default memo(StrategyPerfomanceMetrics) diff --git a/src/modals/Strategy/ClearBacktestResultsModal/ClearBacktestResultsModal.js b/src/modals/Strategy/ClearBacktestResultsModal/ClearBacktestResultsModal.js deleted file mode 100644 index decc9541f..000000000 --- a/src/modals/Strategy/ClearBacktestResultsModal/ClearBacktestResultsModal.js +++ /dev/null @@ -1,60 +0,0 @@ -import React, { memo } from 'react' -import PropTypes from 'prop-types' -import { useTranslation } from 'react-i18next' -import { useDispatch } from 'react-redux' -import Modal from '../../../ui/Modal' -import WSActions from '../../../redux/actions/ws' - -const CleanBacktestResultsModal = ({ - onClose, - isOpen, - nextStrategy, - onLoadStrategy, -}) => { - const { t } = useTranslation() - - const dispatch = useDispatch() - - const clearBacktestOptions = () => { - dispatch(WSActions.resetBacktestData()) - } - const onSubmitReset = () => { - clearBacktestOptions() - onLoadStrategy(nextStrategy, true) - onClose() - } - - return ( - -

{t('strategyEditor.clearBacktestResultsModalText')}

- - - {t('ui.cancel')} - - - {t('ui.proceed')} - - -
- ) -} - -CleanBacktestResultsModal.propTypes = { - onClose: PropTypes.func.isRequired, - isOpen: PropTypes.bool.isRequired, - nextStrategy: PropTypes.shape({ - label: PropTypes.string, - }), - onLoadStrategy: PropTypes.func.isRequired, -} - -CleanBacktestResultsModal.defaultProps = { - nextStrategy: {}, -} - -export default memo(CleanBacktestResultsModal) diff --git a/src/modals/Strategy/ClearBacktestResultsModal/index.js b/src/modals/Strategy/ClearBacktestResultsModal/index.js deleted file mode 100644 index d51c4d3a4..000000000 --- a/src/modals/Strategy/ClearBacktestResultsModal/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import ClearBacktestResultsModal from './ClearBacktestResultsModal' - -export default ClearBacktestResultsModal diff --git a/src/pages/Strategies/Strategies.container.js b/src/pages/Strategies/Strategies.container.js index 227e8ff1f..4eace62b9 100644 --- a/src/pages/Strategies/Strategies.container.js +++ b/src/pages/Strategies/Strategies.container.js @@ -6,14 +6,13 @@ import { } from '../../redux/selectors/ui' import UIActions from '../../redux/actions/ui' import WSActions from '../../redux/actions/ws' -import { getAuthToken, getBacktestResults } from '../../redux/selectors/ws' +import { getAuthToken } from '../../redux/selectors/ws' import StrategiesPage from './Strategies' const mapStateToProps = (state) => ({ authToken: getAuthToken(state), strategy: getCurrentStrategy(state), - backtestResults: getBacktestResults(state), strategyDirty: getIsStrategyDirty(state), isPaperTrading: getIsPaperTrading(state), }) diff --git a/src/pages/Strategies/Strategies.js b/src/pages/Strategies/Strategies.js index f0a91ca64..9e2d1d42b 100644 --- a/src/pages/Strategies/Strategies.js +++ b/src/pages/Strategies/Strategies.js @@ -19,7 +19,6 @@ import SaveUnsavedChangesModal from '../../modals/Strategy/SaveUnsavedChangesMod import RemoveExistingStrategyModal from '../../modals/Strategy/RemoveExistingStrategyModal' import SaveStrategyAsModal from '../../modals/Strategy/SaveStrategyAsModal/SaveStrategyAsModal' import { getDefaultStrategyOptions } from '../../components/StrategyEditor/StrategyEditor.helpers' -import ClearBacktestResultsModal from '../../modals/Strategy/ClearBacktestResultsModal' import useToggle from '../../hooks/useToggle' import { STRATEGY_SHAPE } from '../../constants/prop-types-shapes' @@ -28,13 +27,13 @@ import './style.css' const debug = Debug('hfui-ui:p:strategy-editor') const StrategyEditor = lazy(() => import('../../components/StrategyEditor')) -const StrategiesListTable = lazy(() => import('../../components/StrategiesListTable')) +const StrategiesListTable = lazy(() => import('../../components/StrategiesListTable'), +) const StrategiesPage = ({ authToken, onSave, onRemove, - backtestResults: { finished }, strategy, setStrategy, strategyDirty, @@ -60,11 +59,6 @@ const StrategiesPage = ({ openRenameStrategyModal, closeRenameStrategyModal, ] = useToggle(false) - const [ - isClearBacktestResultsModalOpen,, - openClearBacktestResultsModal, - closeClearBacktestResultsModal, - ] = useToggle(false) const [actionStrategy, setActionStrategy] = useState({}) const [nextStrategyToOpen, setNextStrategyToOpen] = useState(null) @@ -190,11 +184,6 @@ const StrategiesPage = ({ openUnsavedStrategyModal() return } - if (finished && !forcedLoad) { - setNextStrategyToOpen(strategyToLoad) - openClearBacktestResultsModal() - return - } if ( !_isEmpty(strategyToLoad) && _isEmpty(strategyToLoad.strategyOptions) @@ -213,9 +202,7 @@ const StrategiesPage = ({ } }, [ - finished, onDefineIndicatorsChange, - openClearBacktestResultsModal, openUnsavedStrategyModal, setStrategy, setStrategyDirty, @@ -236,10 +223,8 @@ const StrategiesPage = ({ closeRemoveModal() closeSaveStrategyAsModal() closeRenameStrategyModal() - closeClearBacktestResultsModal() closeUnsavedStrategyModal() }, [ - closeClearBacktestResultsModal, closeRemoveModal, closeRenameStrategyModal, closeSaveStrategyAsModal, @@ -348,12 +333,6 @@ const StrategiesPage = ({ onRemoveStrategy={removeStrategy} strategy={actionStrategy} /> - @@ -366,9 +345,6 @@ StrategiesPage.propTypes = { authToken: PropTypes.string.isRequired, onSave: PropTypes.func.isRequired, onRemove: PropTypes.func.isRequired, - backtestResults: PropTypes.shape({ - finished: PropTypes.bool, - }).isRequired, strategyDirty: PropTypes.bool.isRequired, isPaperTrading: PropTypes.bool.isRequired, setStrategyDirty: PropTypes.func.isRequired, From 34e3fbcc017a5a02981604d30ba78112d4025454 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Thu, 17 Aug 2023 16:40:54 +0300 Subject: [PATCH 15/47] fixed: new test button --- .../BacktestResultsOptionsPanel.js | 20 +++++++++---------- .../StrategyEditor/StrategyEditor.js | 3 --- .../StrategyEditor/tabs/BacktestTab.js | 4 ---- src/redux/reducers/ws/backtest.js | 14 ++----------- 4 files changed, 11 insertions(+), 30 deletions(-) diff --git a/src/components/BacktestOptionsPanel/BacktestResultsOptionsPanel.js b/src/components/BacktestOptionsPanel/BacktestResultsOptionsPanel.js index b3052b084..fc98906e0 100644 --- a/src/components/BacktestOptionsPanel/BacktestResultsOptionsPanel.js +++ b/src/components/BacktestOptionsPanel/BacktestResultsOptionsPanel.js @@ -2,31 +2,30 @@ import React from 'react' import _toUpper from 'lodash/toUpper' import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' +import { useDispatch, useSelector } from 'react-redux' import { Icon } from 'react-fa' import Button from '../../ui/Button' import { getFormatTimeFn } from '../../redux/selectors/ui' import { renderDate } from '../../util/ui' import PanelButton from '../../ui/Panel/Panel.Button' import { BACKTEST_TAB_SECTIONS } from '../StrategyEditor/tabs/BacktestTab' +import WSActions from '../../redux/actions/ws' const BacktestResultsOptionsPanel = ({ showFullscreenChart, - openNewTest, backtestTimestamp, activeSection, setActiveSection, }) => { - const { t } = useTranslation() const formatTime = useSelector(getFormatTimeFn) - const onBackButonClick = () => setActiveSection(BACKTEST_TAB_SECTIONS.HISTORY_BT_DETAILS) + const { t } = useTranslation() + const dispatch = useDispatch() + + const onBackButtonClick = () => setActiveSection(BACKTEST_TAB_SECTIONS.HISTORY_BT_DETAILS) const onNewTestButtonClick = () => { - if (activeSection === BACKTEST_TAB_SECTIONS.HISTORY_BT_RESULTS) { - setActiveSection(BACKTEST_TAB_SECTIONS.NEW_BT) - return - } - openNewTest() + dispatch(WSActions.purgeBacktestData()) + setActiveSection(BACKTEST_TAB_SECTIONS.NEW_BT) } const showBtHistoryResults = activeSection === BACKTEST_TAB_SECTIONS.HISTORY_BT_RESULTS @@ -36,7 +35,7 @@ const BacktestResultsOptionsPanel = ({ {showBtHistoryResults ? (
} @@ -77,7 +76,6 @@ const BacktestResultsOptionsPanel = ({ BacktestResultsOptionsPanel.propTypes = { showFullscreenChart: PropTypes.func.isRequired, - openNewTest: PropTypes.func.isRequired, setActiveSection: PropTypes.func.isRequired, activeSection: PropTypes.string.isRequired, backtestTimestamp: PropTypes.number, diff --git a/src/components/StrategyEditor/StrategyEditor.js b/src/components/StrategyEditor/StrategyEditor.js index bda329bd0..91cabb513 100644 --- a/src/components/StrategyEditor/StrategyEditor.js +++ b/src/components/StrategyEditor/StrategyEditor.js @@ -558,8 +558,6 @@ const StrategyEditor = (props) => { setStrategy(strategy, PAPER_MODE) }, [changeTradingMode, isPaperTrading, setStrategy, strategy]) - const openNewTest = () => onLoadStrategy(strategy) - const hasErrorsInIDE = useMemo( () => _some(_values(sectionErrors), (e) => !!e), [sectionErrors], @@ -701,7 +699,6 @@ const StrategyEditor = (props) => { onBacktestStart={onBacktestStart} saveStrategyOptions={saveStrategyOptions} onCancelProcess={onCancelProcess} - openNewTest={openNewTest} {...props} /> )} diff --git a/src/components/StrategyEditor/tabs/BacktestTab.js b/src/components/StrategyEditor/tabs/BacktestTab.js index 08e8a59ba..79aced610 100644 --- a/src/components/StrategyEditor/tabs/BacktestTab.js +++ b/src/components/StrategyEditor/tabs/BacktestTab.js @@ -41,7 +41,6 @@ const BacktestTab = (props) => { markets, onBacktestStart, saveStrategyOptions, - openNewTest, } = props const { t } = useTranslation() @@ -84,7 +83,6 @@ const BacktestTab = (props) => { return showBacktestResults ? ( { [ showBacktestResults, showFullscreenChart, - openNewTest, timestamp, activeSection, strategy, @@ -204,7 +201,6 @@ BacktestTab.propTypes = { indicators: INDICATORS_ARRAY_SHAPE, onBacktestStart: PropTypes.func.isRequired, saveStrategyOptions: PropTypes.func.isRequired, - openNewTest: PropTypes.func.isRequired, } BacktestTab.defaultProps = { diff --git a/src/redux/reducers/ws/backtest.js b/src/redux/reducers/ws/backtest.js index 908ddd63c..7e11f41ad 100644 --- a/src/redux/reducers/ws/backtest.js +++ b/src/redux/reducers/ws/backtest.js @@ -79,19 +79,9 @@ function reducer(state = getInitialState(), action = {}) { } case types.DISCONNECTED: - case types.RESET_DATA_BACKTEST: { - return getInitialState() - } - + case types.RESET_DATA_BACKTEST: case types.PURGE_DATA_BACKTEST: { - return { - candles: [], - trades: [], - loading: false, - executing: false, - progressPerc: 0, - ...state, - } + return getInitialState() } case types.BACKTEST_CANDLE: { From b115ac74f0d205c2eb59b338c0795614973899bf Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Thu, 17 Aug 2023 17:22:22 +0300 Subject: [PATCH 16/47] BT: add bt to history after execution --- src/redux/actions/ws.js | 4 ++++ src/redux/constants/ws.js | 1 + src/redux/middleware/ws/on_message.js | 12 +++++++++++- src/redux/reducers/ws/backtestHistory.js | 19 +++++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/redux/actions/ws.js b/src/redux/actions/ws.js index 8236ae709..d9b825742 100644 --- a/src/redux/actions/ws.js +++ b/src/redux/actions/ws.js @@ -391,6 +391,10 @@ export default { type: t.EXPORT_STRATEGIES_ON_RESET, payload: { password }, }), + setBacktestToHistory: (backtest) => ({ + type: t.SET_BACKTEST_TO_HISTORY, + payload: { backtest }, + }), changeBacktestFavoriteState: (backtestId, isFavorite) => ({ type: t.BACKTEST_SET_FAVORITE, payload: { backtestId, isFavorite }, diff --git a/src/redux/constants/ws.js b/src/redux/constants/ws.js index a111bfc96..c3d630ea6 100644 --- a/src/redux/constants/ws.js +++ b/src/redux/constants/ws.js @@ -84,6 +84,7 @@ export default { SET_STARTED_LIVE_STRATEGY: 'WS_SET_STARTED_LIVE_STRATEGY', SET_STOPPED_LIVE_STRATEGY: 'WS_SET_STOPPED_LIVE_STRATEGY', SET_BACKTEST_LOADING: 'WS_SET_BACKTEST_LOADING', + SET_BACKTEST_TO_HISTORY: 'WS_SET_BACKTEST_TO_HISTORY', UPDATE_FAVORITE_PAIRS: 'WS_UPDATE_FAVORITE_PAIRS', BUFFER_DATA_FROM_EXCHANGE: 'WS_BUFFER_DATA_FROM_EXCHANGE', diff --git a/src/redux/middleware/ws/on_message.js b/src/redux/middleware/ws/on_message.js index fb7fea650..6231685e2 100644 --- a/src/redux/middleware/ws/on_message.js +++ b/src/redux/middleware/ws/on_message.js @@ -468,6 +468,10 @@ export default (alias, store) => (e = {}) => { case 'bt.btresult': { const [, res] = payload + const { error } = res + if (error) { + break + } store.dispatch(WSActions.recvBacktestResults(res)) store.dispatch(UIActions.logInformation('Backtest finished successfully', LOG_LEVELS.INFO, 'backtest_success')) break @@ -485,6 +489,12 @@ export default (alias, store) => (e = {}) => { break } + case 'data.bt.saved': { + const [, , bt] = payload + store.dispatch(WSActions.setBacktestToHistory(bt)) + break + } + case 'data.bt.history.list': { const [, strategyId, backtestsList] = payload store.dispatch(WSActions.recvStrategyBacktestsList(strategyId, backtestsList)) @@ -611,7 +621,7 @@ export default (alias, store) => (e = {}) => { } case 'info.username': { - const [,, username] = payload + const [, , username] = payload store.dispatch(WSActions.setUsername(username)) break } diff --git a/src/redux/reducers/ws/backtestHistory.js b/src/redux/reducers/ws/backtestHistory.js index 96cf5ecee..4f0887d4a 100644 --- a/src/redux/reducers/ws/backtestHistory.js +++ b/src/redux/reducers/ws/backtestHistory.js @@ -77,6 +77,25 @@ const reducer = (state = getInitialState(), action = {}) => { } } + case types.SET_BACKTEST_TO_HISTORY: { + const { backtest } = payload + const { executionId, strategyId } = backtest + + const strategyMappedKeys = state.mappedKeysByStrategyIds[strategyId] || [] + + return { + ...state, + backtests: { + ...state.backtests, + [executionId]: backtest, + }, + mappedKeysByStrategyIds: { + ...state.mappedKeysByStrategyIds, + [strategyId]: [...strategyMappedKeys, executionId], + }, + } + } + default: { return state } From 1b00c854310312b43366756fc0c124de34dc93ab Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Thu, 17 Aug 2023 17:30:25 +0300 Subject: [PATCH 17/47] BT: log error messages during execution --- src/redux/middleware/ws/on_message.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/redux/middleware/ws/on_message.js b/src/redux/middleware/ws/on_message.js index 6231685e2..6805a8059 100644 --- a/src/redux/middleware/ws/on_message.js +++ b/src/redux/middleware/ws/on_message.js @@ -470,6 +470,7 @@ export default (alias, store) => (e = {}) => { const [, res] = payload const { error } = res if (error) { + store.dispatch(UIActions.logInformation(error, LOG_LEVELS.ERROR, 'backtest_failed')) break } store.dispatch(WSActions.recvBacktestResults(res)) From 6ce9cdffcb400324c1ac46f030590d96a110f825 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Thu, 17 Aug 2023 23:44:37 +0300 Subject: [PATCH 18/47] BT: update initial messages --- public/locales/en-US/translations.json | 4 +++- src/components/StrategyEditor/style.scss | 1 + src/components/StrategyEditor/tabs/BacktestTab.js | 15 ++++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/public/locales/en-US/translations.json b/public/locales/en-US/translations.json index 13823c31e..f14703ece 100644 --- a/public/locales/en-US/translations.json +++ b/public/locales/en-US/translations.json @@ -779,7 +779,9 @@ "openStartegyModalNoSelectedError": "No strategy selected", "backtestingCreateMessage": "Create a strategy to begin backtesting", "backtestingLoadingMessage": "Loading candles and executing strategy...", - "backtestingStartingMessage": "Press start to begin backtesting", + "backtestingStartingMessage": "Click \"Start\" to begin backtesting", + "backtestingHistoryListMessage": "Select a completed backtest and view its details", + "backtestingHistoryDetailsMessage": "Click \"More info\" to view backtest performance metrics, trades and chart table", "exportCSV": "Export as CSV", "exportStrategy": "Export strategy to JSON", "historical": "Historical", diff --git a/src/components/StrategyEditor/style.scss b/src/components/StrategyEditor/style.scss index d1656109b..b6e57f805 100644 --- a/src/components/StrategyEditor/style.scss +++ b/src/components/StrategyEditor/style.scss @@ -344,6 +344,7 @@ body.hosted { font-size: 20px; font-weight: 700; text-align: center; + line-height: 1.5em; } .hfui-strategyeditor__sidebar-title { diff --git a/src/components/StrategyEditor/tabs/BacktestTab.js b/src/components/StrategyEditor/tabs/BacktestTab.js index 79aced610..3c336eccf 100644 --- a/src/components/StrategyEditor/tabs/BacktestTab.js +++ b/src/components/StrategyEditor/tabs/BacktestTab.js @@ -32,6 +32,19 @@ export const BACKTEST_TAB_SECTIONS = { HISTORY_BT_RESULTS: 'HISTORY_BT_RESULTS', } +const getInitialMessageI18Key = (activeSection) => { + if (activeSection === BACKTEST_TAB_SECTIONS.NEW_BT) { + return 'strategyEditor.backtestingStartingMessage' + } + if (activeSection === BACKTEST_TAB_SECTIONS.HISTORY_BT_LIST) { + return 'strategyEditor.backtestingHistoryListMessage' + } + if (activeSection === BACKTEST_TAB_SECTIONS.HISTORY_BT_DETAILS) { + return 'strategyEditor.backtestingHistoryDetailsMessage' + } + return '' +} + const BacktestTab = (props) => { const { results, @@ -172,7 +185,7 @@ const BacktestTab = (props) => { ) : (

- {t('strategyEditor.backtestingStartingMessage')} + {t(getInitialMessageI18Key(activeSection))}

)}
From f65425caaf466b98c06c7c6acc340b76ccea65c2 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Thu, 17 Aug 2023 23:51:14 +0300 Subject: [PATCH 19/47] BT: fix button style --- src/components/BacktestOptionsPanel/style.scss | 8 +++++++- .../BacktestOptionsPanel/tabs/BacktestDetails.js | 16 +++++++++------- .../BacktestOptionsPanel/tabs/NewTest.js | 4 ++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/components/BacktestOptionsPanel/style.scss b/src/components/BacktestOptionsPanel/style.scss index 3a643e329..f7318c72c 100644 --- a/src/components/BacktestOptionsPanel/style.scss +++ b/src/components/BacktestOptionsPanel/style.scss @@ -60,6 +60,12 @@ } } + .button-container { + @extend .item; + display: flex; + justify-content: center; + } + &__icn-selector-container { display: flex; justify-content: flex-end; @@ -103,7 +109,7 @@ } &__start-btn { - width: 100%; + width: 150px; height: 34px; } diff --git a/src/components/BacktestOptionsPanel/tabs/BacktestDetails.js b/src/components/BacktestOptionsPanel/tabs/BacktestDetails.js index f2d3189b2..d4ed3f914 100644 --- a/src/components/BacktestOptionsPanel/tabs/BacktestDetails.js +++ b/src/components/BacktestOptionsPanel/tabs/BacktestDetails.js @@ -56,13 +56,15 @@ const BacktestDetails = ({ btHistoryId, setActiveSection }) => {
) })} - +
+ +
) } diff --git a/src/components/BacktestOptionsPanel/tabs/NewTest.js b/src/components/BacktestOptionsPanel/tabs/NewTest.js index c12d06fdf..243c90ed5 100644 --- a/src/components/BacktestOptionsPanel/tabs/NewTest.js +++ b/src/components/BacktestOptionsPanel/tabs/NewTest.js @@ -260,7 +260,7 @@ const BacktestOptionsNewTest = ({
{isLoading && ( -
+
)} -
+
{isLoading ? ( ) diff --git a/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js b/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js index 63aeb72ba..f5fd70aef 100644 --- a/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js +++ b/src/components/BacktestOptionsPanel/tabs/BacktestHistoryList.js @@ -36,7 +36,7 @@ const BacktestHistoryList = ({ onBacktestRowClick }) => { [dispatch], ) - const removeBacktest = useCallback( + const removeBacktestFromHistory = useCallback( (id) => { dispatch( WSActions.send({ @@ -52,10 +52,10 @@ const BacktestHistoryList = ({ onBacktestRowClick }) => { () => BacktestHistoryListColumns({ formatTime, toggleFavorite, - removeBacktest, + removeBacktestFromHistory, t, }), - [toggleFavorite, formatTime, removeBacktest, t], + [toggleFavorite, formatTime, removeBacktestFromHistory, t], ) const data = useMemo(() => { From 4536691be75022766f4b11c76dd121dc72e73c93 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Fri, 18 Aug 2023 00:43:57 +0300 Subject: [PATCH 24/47] BT: remove history on strategy delete --- src/redux/reducers/ws/backtestHistory.js | 27 +++++++++++++++++++----- src/redux/sagas/ui/on_remove_strategy.js | 15 ++++++++++++- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/redux/reducers/ws/backtestHistory.js b/src/redux/reducers/ws/backtestHistory.js index 4f0887d4a..a3f8adca4 100644 --- a/src/redux/reducers/ws/backtestHistory.js +++ b/src/redux/reducers/ws/backtestHistory.js @@ -2,7 +2,8 @@ import _get from 'lodash/get' import _forEach from 'lodash/forEach' import _omit from 'lodash/omit' import _filter from 'lodash/filter' -import types from '../../constants/ws' +import _isEmpty from 'lodash/isEmpty' +import WSTypes from '../../constants/ws' const getInitialState = () => ({ mappedKeysByStrategyIds: {}, @@ -13,7 +14,7 @@ const reducer = (state = getInitialState(), action = {}) => { const { type, payload = {} } = action switch (type) { - case types.ADD_STRATEGY_BACKTESTS_LIST: { + case WSTypes.ADD_STRATEGY_BACKTESTS_LIST: { const { strategyId, backtestsList } = payload const mappedBacktestKeys = [] @@ -42,7 +43,7 @@ const reducer = (state = getInitialState(), action = {}) => { } } - case types.BACKTEST_SET_FAVORITE: { + case WSTypes.BACKTEST_SET_FAVORITE: { const { backtestId, isFavorite } = payload const backtest = _get(state, `backtests.${backtestId}`, {}) @@ -59,7 +60,7 @@ const reducer = (state = getInitialState(), action = {}) => { } } - case types.BACKTEST_REMOVE: { + case WSTypes.BACKTEST_REMOVE: { const { backtestId } = payload const backtest = _get(state, `backtests.${backtestId}`, {}) @@ -77,7 +78,7 @@ const reducer = (state = getInitialState(), action = {}) => { } } - case types.SET_BACKTEST_TO_HISTORY: { + case WSTypes.SET_BACKTEST_TO_HISTORY: { const { backtest } = payload const { executionId, strategyId } = backtest @@ -96,6 +97,22 @@ const reducer = (state = getInitialState(), action = {}) => { } } + case WSTypes.DATA_REMOVE_STRATEGY: { + const { id: strategyId } = payload + const { backtests, mappedKeysByStrategyIds } = state + const backtestIds = mappedKeysByStrategyIds[strategyId] || [] + + if (_isEmpty(backtestIds)) { + return { ...state } + } + + return { + ...state, + backtests: _omit(backtests, backtestIds), + mappedKeysByStrategyIds: _omit(mappedKeysByStrategyIds, strategyId), + } + } + default: { return state } diff --git a/src/redux/sagas/ui/on_remove_strategy.js b/src/redux/sagas/ui/on_remove_strategy.js index 5fbb99d89..0fc8e2f99 100644 --- a/src/redux/sagas/ui/on_remove_strategy.js +++ b/src/redux/sagas/ui/on_remove_strategy.js @@ -2,6 +2,7 @@ import { put, select } from 'redux-saga/effects' import WSActions from '../../actions/ws' import UIActions from '../../actions/ui' +import WSTypes from '../../constants/ws' import { getCurrentStrategy } from '../../selectors/ui' import { PAPER_MODE } from '../../reducers/ui' import { LOG_LEVELS } from '../../../constants/logging' @@ -12,7 +13,19 @@ export default function* ({ payload }) { const { id: currentStrategyId } = yield select(getCurrentStrategy) yield put(WSActions.send(['strategy.remove', authToken, id])) - yield put(UIActions.logInformation(`Deleted strategy draft (${id})`, LOG_LEVELS.INFO, 'strategy_draft_removed')) + yield put( + WSActions.send({ + alias: WSTypes.ALIAS_DATA_SERVER, + data: ['delete.bt.history.all', id], + }), + ) + yield put( + UIActions.logInformation( + `Deleted strategy draft (${id})`, + LOG_LEVELS.INFO, + 'strategy_draft_removed', + ), + ) if (currentStrategyId === id) { yield put(WSActions.purgeBacktestData()) yield put(UIActions.setCurrentStrategy({}, PAPER_MODE)) From dc7efe9bda147a3712cd182d9055723a088110f7 Mon Sep 17 00:00:00 2001 From: Dmytro Shcherbonos Date: Fri, 18 Aug 2023 02:29:48 +0300 Subject: [PATCH 25/47] BT history: move some component state properties to redux store --- .../BacktestOptionsPanel.js | 22 +++-- .../BacktestResultsOptionsPanel.js | 32 +++---- .../tabs/BacktestDetails.js | 23 ++--- src/components/Navbar/Navbar.Link.js | 4 +- .../StrategyEditor.container.js | 4 +- .../StrategyEditor/StrategyEditor.js | 14 ++- .../StrategyEditor/tabs/BacktestTab.Title.js | 6 +- .../StrategyEditor/tabs/BacktestTab.js | 89 +++++++------------ .../StrategyPerfomanceMetrics.js | 3 +- src/redux/actions/ui.js | 6 ++ src/redux/actions/ws.js | 8 +- src/redux/constants/ui.js | 2 + src/redux/constants/ws.js | 1 + src/redux/middleware/ws/on_message.js | 2 +- src/redux/reducers/ui/index.js | 37 +++++++- src/redux/reducers/ws/backtest.js | 32 ++++--- src/redux/reducers/ws/backtestHistory.js | 18 ++++ .../ui/get_backtest_active_section.js | 13 +++ src/redux/selectors/ui/index.js | 2 + src/redux/selectors/ws/get_backtest_by_id.js | 10 --- .../selectors/ws/get_backtest_results.js | 2 +- src/redux/selectors/ws/get_backtest_state.js | 9 +- .../ws/get_current_history_backtest.js | 10 +++ src/redux/selectors/ws/index.js | 4 +- 24 files changed, 200 insertions(+), 153 deletions(-) create mode 100644 src/redux/selectors/ui/get_backtest_active_section.js delete mode 100644 src/redux/selectors/ws/get_backtest_by_id.js create mode 100644 src/redux/selectors/ws/get_current_history_backtest.js diff --git a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js index eb0dfa629..b1dfe5d46 100644 --- a/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js +++ b/src/components/BacktestOptionsPanel/BacktestOptionsPanel.js @@ -1,22 +1,26 @@ import React from 'react' import { useTranslation } from 'react-i18next' -import PropTypes from 'prop-types' import _toUpper from 'lodash/toUpper' import { Icon } from 'react-fa' +import { useDispatch, useSelector } from 'react-redux' import BacktestOptionsNewTest from './tabs/NewTest' import HistoryButton from '../../ui/HistoryButton/HistoryButton' import BacktestHistoryList from './tabs/BacktestHistoryList' import PanelButton from '../../ui/Panel/Panel.Button' import BacktestDetails from './tabs/BacktestDetails' -import { BACKTEST_TAB_SECTIONS } from '../StrategyEditor/tabs/BacktestTab' +import { BACKTEST_TAB_SECTIONS } from '../../redux/reducers/ui' +import UIActions from '../../redux/actions/ui' +import WSActions from '../../redux/actions/ws' +import { getBacktestActiveSection } from '../../redux/selectors/ui' import './style.css' const BacktestOptionsPanel = (props) => { - const { setActiveSection, activeSection, setBtHistoryId } = props - const { t } = useTranslation() + const dispatch = useDispatch() + const activeSection = useSelector(getBacktestActiveSection) + const setActiveSection = (section) => dispatch(UIActions.setBacktestActiveSection(section)) const onNewTestTabClick = () => setActiveSection(BACKTEST_TAB_SECTIONS.NEW_BT) const onHistoryTabClick = () => { if (activeSection !== BACKTEST_TAB_SECTIONS.NEW_BT) { @@ -27,7 +31,7 @@ const BacktestOptionsPanel = (props) => { const onBackButtonClick = () => setActiveSection(BACKTEST_TAB_SECTIONS.HISTORY_BT_LIST) const onBacktestRowClick = ({ rowData }) => { - setBtHistoryId(rowData.executionId) + dispatch(WSActions.setHistoryBacktestId(rowData.executionId)) setActiveSection(BACKTEST_TAB_SECTIONS.HISTORY_BT_DETAILS) } @@ -63,16 +67,10 @@ const BacktestOptionsPanel = (props) => { )} {activeSection === BACKTEST_TAB_SECTIONS.HISTORY_BT_DETAILS && ( - + )}
) } -BacktestOptionsPanel.propTypes = { - activeSection: PropTypes.string.isRequired, - setActiveSection: PropTypes.func.isRequired, - setBtHistoryId: PropTypes.func.isRequired, -} - export default BacktestOptionsPanel diff --git a/src/components/BacktestOptionsPanel/BacktestResultsOptionsPanel.js b/src/components/BacktestOptionsPanel/BacktestResultsOptionsPanel.js index fc98906e0..1d290f4c1 100644 --- a/src/components/BacktestOptionsPanel/BacktestResultsOptionsPanel.js +++ b/src/components/BacktestOptionsPanel/BacktestResultsOptionsPanel.js @@ -1,27 +1,32 @@ import React from 'react' import _toUpper from 'lodash/toUpper' +import _get from 'lodash/get' import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' import { Icon } from 'react-fa' import Button from '../../ui/Button' -import { getFormatTimeFn } from '../../redux/selectors/ui' +import { + getBacktestActiveSection, + getFormatTimeFn, +} from '../../redux/selectors/ui' import { renderDate } from '../../util/ui' import PanelButton from '../../ui/Panel/Panel.Button' -import { BACKTEST_TAB_SECTIONS } from '../StrategyEditor/tabs/BacktestTab' + +import UIActions from '../../redux/actions/ui' import WSActions from '../../redux/actions/ws' +import { BACKTEST_TAB_SECTIONS } from '../../redux/reducers/ui' +import { getCurrentHistoryBacktest } from '../../redux/selectors/ws' -const BacktestResultsOptionsPanel = ({ - showFullscreenChart, - backtestTimestamp, - activeSection, - setActiveSection, -}) => { +const BacktestResultsOptionsPanel = ({ showFullscreenChart }) => { const formatTime = useSelector(getFormatTimeFn) + const backtest = useSelector(getCurrentHistoryBacktest) + const activeSection = useSelector(getBacktestActiveSection) const { t } = useTranslation() const dispatch = useDispatch() + const setActiveSection = (section) => dispatch(UIActions.setBacktestActiveSection(section)) const onBackButtonClick = () => setActiveSection(BACKTEST_TAB_SECTIONS.HISTORY_BT_DETAILS) const onNewTestButtonClick = () => { dispatch(WSActions.purgeBacktestData()) @@ -43,7 +48,9 @@ const BacktestResultsOptionsPanel = ({

{t('strategyEditor.backtestHistoryResults')}   - {renderDate(backtestTimestamp, formatTime, false)} + + {renderDate(_get(backtest, 'timestamp', 0), formatTime, false)} +