From b4a4cfd1e7810d8f34f78216d3dbfa66a841f0e1 Mon Sep 17 00:00:00 2001 From: Carlos Wu Fei Date: Sat, 28 Sep 2024 16:08:41 +0100 Subject: [PATCH] Refactor reducer to use redux toolkit This gets rid of multiple redux files, combining into one single redux "slice" file. Removes action creators and centralizes API calls. --- api/bots/schemas.py | 5 +- api/deals/models.py | 3 +- api/deals/schema.py | 1 + api/tools/enum_definitions.py | 2 + web/environment.d.ts | 8 + web/package.json | 2 +- web/src/pages/bots/actions.js | 538 ---------------------------------- web/src/pages/bots/api.js | 108 +++++++ web/src/pages/bots/reducer.ts | 306 ------------------- web/src/pages/bots/saga.js | 17 +- web/src/state/bots/actions.ts | 129 ++------ web/src/state/bots/reducer.ts | 246 ++++++++++++++++ web/src/state/bots/types.ts | 242 +++++++++++++++ web/src/store.ts | 19 +- web/yarn.lock | 18 +- 15 files changed, 668 insertions(+), 976 deletions(-) create mode 100644 web/environment.d.ts delete mode 100644 web/src/pages/bots/actions.js create mode 100644 web/src/pages/bots/api.js delete mode 100644 web/src/pages/bots/reducer.ts create mode 100644 web/src/state/bots/reducer.ts create mode 100644 web/src/state/bots/types.ts diff --git a/api/bots/schemas.py b/api/bots/schemas.py index f26b8ab1c..4f79a8198 100644 --- a/api/bots/schemas.py +++ b/api/bots/schemas.py @@ -40,14 +40,14 @@ class BotSchema(BaseModel): created_at: float = time() * 1000 deal: DealModel = Field(default_factory=DealModel) dynamic_trailling: bool = False - errors: list[str] = [] # Event logs + errors: list[str] = [] # Event logs locked_so_funds: float = 0 # funds locked by Safety orders mode: str = "manual" # Manual is triggered by the terminal dashboard, autotrade by research app name: str = "Default bot" orders: list[BinanceOrderModel] = [] # Internal status: Status = Status.inactive stop_loss: float = 0 - margin_short_reversal: bool = False # If stop_loss > 0, allow for reversal + margin_short_reversal: bool = False # If stop_loss > 0, allow for reversal take_profit: float = 0 trailling: bool = True trailling_deviation: float = 0 @@ -93,7 +93,6 @@ class BotSchema(BaseModel): } } - @field_validator("pair", "base_order_size", "candlestick_interval") @classmethod def check_names_not_empty(cls, v): diff --git a/api/deals/models.py b/api/deals/models.py index a86d8bb3f..57773605d 100644 --- a/api/deals/models.py +++ b/api/deals/models.py @@ -67,7 +67,7 @@ class DealModel(BaseModel): sell_price: float = 0 sell_qty: float = 0 trailling_stop_loss_price: float = 0 - trailling_profit_price: float = 0 # take_profit but for trailling, to avoid confusion, trailling_profit_price always be > trailling_stop_loss_price + trailling_profit_price: float = 0 # take_profit but for trailling, to avoid confusion, trailling_profit_price always be > trailling_stop_loss_price stop_loss_price: float = 0 trailling_profit: float = 0 so_prices: float = 0 @@ -107,6 +107,7 @@ def check_prices(cls, v): return float(v) return v + class SafetyOrderModel(BaseModel): buy_price: float so_size: float diff --git a/api/deals/schema.py b/api/deals/schema.py index 2dcf70a5c..07fb461b2 100644 --- a/api/deals/schema.py +++ b/api/deals/schema.py @@ -1,5 +1,6 @@ from deals.models import BinanceOrderModel + class MarginOrderSchema(BinanceOrderModel): margin_buy_borrow_amount: int = 0 margin_buy_borrow_asset: str = "USDC" diff --git a/api/tools/enum_definitions.py b/api/tools/enum_definitions.py index c439a60d6..16f66caf1 100644 --- a/api/tools/enum_definitions.py +++ b/api/tools/enum_definitions.py @@ -134,6 +134,7 @@ class DealType(str, Enum): def __str__(self): return str(self.str) + class BinanceKlineIntervals(str, Enum): one_minute = "1m" three_minutes = "3m" @@ -154,6 +155,7 @@ class BinanceKlineIntervals(str, Enum): def __str__(self): return str(self.str) + class AutotradeSettingsDocument(str, Enum): # Autotrade settings for test bots test_autotrade_settings = "test_autotrade_settings" diff --git a/web/environment.d.ts b/web/environment.d.ts new file mode 100644 index 000000000..b79bcfa28 --- /dev/null +++ b/web/environment.d.ts @@ -0,0 +1,8 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + NODE_ENV: 'development' | 'production'; + PORT?: string; + } + } + } \ No newline at end of file diff --git a/web/package.json b/web/package.json index de2142f21..72aaa91ca 100644 --- a/web/package.json +++ b/web/package.json @@ -44,7 +44,7 @@ "@testing-library/react": "^12.1.1", "@testing-library/user-event": "^14.1.1", "@types/jest": "^29.5.5", - "@types/node": "^20.8.6", + "@types/node": "^22.7.4", "@types/react": "^18.2.28", "@types/react-dom": "^18.2.13", "@types/react-router-dom": "^5.3.3", diff --git a/web/src/pages/bots/actions.js b/web/src/pages/bots/actions.js deleted file mode 100644 index a6789d8f0..000000000 --- a/web/src/pages/bots/actions.js +++ /dev/null @@ -1,538 +0,0 @@ -import { addNotification } from "../../validations"; - -export const GET_BOTS = "GET_BOTS"; -export const GET_BOTS_SUCCESS = "GET_BOTS_SUCCESS"; -export const GET_BOTS_ERROR = "GET_BOTS_ERROR"; - -export const GET_BOT = "GET_BOT"; -export const GET_BOT_SUCCESS = "GET_BOT_SUCCESS"; -export const GET_BOT_ERROR = "GET_BOT_ERROR"; - -export const CREATE_BOT = "CREATE_BOT"; -export const CREATE_BOT_SUCCESS = "CREATE_BOT_SUCCESS"; -export const CREATE_BOT_ERROR = "CREATE_BOT_ERROR"; -export const EDIT_BOT = "EDIT_BOT"; -export const EDIT_BOT_SUCCESS = "EDIT_BOT_SUCCESS"; -export const EDIT_BOT_ERROR = "EDIT_BOT_ERROR"; -export const DELETE_BOT = "DELETE_BOT"; -export const DELETE_BOT_SUCCESS = "DELETE_BOT_SUCCESS"; -export const DELETE_BOT_ERROR = "DELETE_BOT_ERROR"; -export const CLOSE_BOT = "CLOSE_BOT"; - -export const ACTIVATE_BOT = "ACTIVATE_BOT"; -export const ACTIVATE_BOT_SUCCESS = "ACTIVATE_BOT_SUCCESS"; -export const ACTIVATE_BOT_ERROR = "ACTIVATE_BOT_ERROR"; -export const DEACTIVATE_BOT = "DEACTIVATE_BOT"; -export const DEACTIVATE_BOT_SUCCESS = "DEACTIVATE_BOT_SUCCESS"; -export const DEACTIVATE_BOT_ERROR = "DEACTIVATE_BOT_ERROR"; - -export const GET_SYMBOLS = "GET_SYMBOLS"; -export const GET_SYMBOLS_SUCCESS = "GET_SYMBOLS_SUCCESS"; -export const GET_SYMBOLS_ERROR = "GET_SYMBOLS_ERROR"; -export const GET_SYMBOL_INFO = "GET_SYMBOL_INFO"; -export const GET_SYMBOL_INFO_SUCCESS = "GET_SYMBOL_INFO_SUCCESS"; -export const GET_SYMBOL_INFO_ERROR = "GET_SYMBOL_INFO_ERROR"; - -export const LOAD_CANDLESTICK = "LOAD_CANDLESTICK"; -export const LOAD_CANDLESTICK_SUCCESS = "LOAD_CANDLESTICK_SUCCESS"; -export const LOAD_CANDLESTICK_ERROR = "LOAD_CANDLESTICK_ERROR"; - -export const GET_QUOTE_ASSET = "GET_QUOTE_ASSET"; -export const GET_QUOTE_ASSET_SUCCESSFUL = "GET_QUOTE_ASSET_SUCCESSFUL"; -export const GET_QUOTE_ASSET_ERROR = "GET_QUOTE_ASSET_ERROR"; - -export const GET_BASE_ASSET = "GET_BASE_ASSET"; -export const GET_BASE_ASSET_SUCCESSFUL = "GET_BASE_ASSET_SUCCESSFUL"; -export const GET_BASE_ASSET_ERROR = "GET_BASE_ASSET_ERROR"; - -export const ARCHIVE_BOT = "ARCHIVE_BOT"; -export const ARCHIVE_BOT_SUCCESS = "ARCHIVE_BOT_SUCCESS"; -export const ARCHIVE_BOT_ERROR = "ARCHIVE_BOT_ERROR"; - -export const SET_BOT = "SET_BOT"; -export const RESET_BOT = "RESET_BOT"; - -// Autotrade settings -export const GET_SETTINGS = "GET_SETTINGS"; -export const GET_SETTINGS_SUCCESS = "GET_SETTINGS_SUCCESS"; -export const GET_SETTINGS_ERROR = "GET_SETTINGS_ERROR"; -export const SET_SETTINGS_STATE = "SET_SETTINGS_STATE"; - -export const EDIT_SETTINGS = "EDIT_SETTINGS"; -export const EDIT_SETTINGS_SUCCESS = "EDIT_SETTINGS_SUCCESS"; -export const EDIT_SETTINGS_ERROR = "EDIT_SETTINGS_ERROR"; - -export function setBot(payload) { - return { - type: SET_BOT, - payload, - }; -} - -/** - * Create new user - * - * @return {object} An action object with a type of BOT - */ -export function getBots(params) { - return { - type: GET_BOTS, - params: params, - }; -} - -/** - * Dispatched when the repositories are loaded by the request saga - * - * @param {array} repos The repository data - * @param {string} username The current username - * - * @return {object} An action object with a type of BOT_SUCCESS passing the repos - */ -export function getBotsSucceeded(res) { - return { - type: GET_BOTS_SUCCESS, - isError: false, - bots: res.data, - }; -} - -/** - * Dispatched when loading the repositories fails - * - * @param {object} error The error - * - * @return {object} An action object with a type of BOT_ERROR passing the error - */ -export function getBotsFailed(error) { - return { - type: GET_BOTS_ERROR, - isError: true, - data: error, - }; -} - -/** - * Create new user - * - * @return {object} An action object with a type of BOT - */ -export function getBot(id) { - return { - type: GET_BOT, - isError: false, - data: id, - }; -} - -/** - * Dispatched when the repositories are loaded by the request saga - * - * @param {array} repos The repository data - * @param {string} username The current username - * - * @return {object} An action object with a type of BOT_SUCCESS passing the repos - */ -export function getBotSucceeded(res) { - return { - type: GET_BOT_SUCCESS, - bots: res.data, - message: res.message, - }; -} - -/** - * Dispatched when loading the repositories fails - * - * @param {object} error The error - * - * @return {object} An action object with a type of BOT_ERROR passing the error - */ -export function getBotFailed(error) { - return { - type: GET_BOT_ERROR, - data: error, - }; -} - -/** - * Create new bot - * - * @return {object} An action object with a type of BOT - */ -export function createBot(body) { - return { - type: CREATE_BOT, - data: body, - }; -} - -/** - * Dispatched when the repositories are loaded by the request saga - * - * @param {array} repos The repository data - * @param {string} username The current username - * - * @return {object} An action object with a type of BOT_SUCCESS passing the repos - */ -export function createBotSucceeded(res) { - if (res.error === 1) { - addNotification("Some errors encountered", res.message, "error"); - } else { - addNotification("SUCCESS!", res.message, "success"); - } - - return { - type: CREATE_BOT_SUCCESS, - botId: res.botId, - }; -} - -/** - * Dispatched when loading the repositories fails - * - * @param {object} error The error - * - * @return {object} An action object with a type of BOT_ERROR passing the error - */ -export function createBotFailed(error) { - addNotification("FAILED!", error.message, "error"); - return { - type: CREATE_BOT_ERROR, - data: error, - message: error.message, - }; -} - -/** - * Edit bot - * - * @return {object} An action object with a type of BOT - */ -export function editBot(id, body) { - return { - type: EDIT_BOT, - data: body, - id: id, - }; -} - -/** - * Dispatched when the repositories are loaded by the request saga - * - * @param {array} repos The repository data - * @param {string} username The current username - * - * @return {object} An action object with a type of BOT_SUCCESS passing the repos - */ -export function editBotSucceeded(res) { - if (res.error === 1) { - addNotification("Some errors encountered", res.message, "error"); - } else { - addNotification("SUCCESS!", res.message, "success"); - } - return { - type: EDIT_BOT_SUCCESS, - bots: res, - }; -} - -/** - * Dispatched when loading the repositories fails - * - * @param {object} error The error - * - * @return {object} An action object with a type of BOT_ERROR passing the error - */ -export function editBotFailed(error) { - addNotification("FAILED!", error.message, "error"); - return { - type: EDIT_BOT_ERROR, - data: error, - }; -} - -/** - * Simple Delete bot - * @return {objectId} - */ -export function deleteBot(id) { - return { - type: DELETE_BOT, - data: id, - removeId: id, - }; -} -/** - * Close deal, sell coins and delete bot - * @return {objectId} - */ -export function closeBot(id) { - return { - type: CLOSE_BOT, - data: id, - removeId: id, - }; -} - -/** - * Dispatched when the repositories are loaded by the request saga - * - * @param {array} repos The repository data - * @param {string} username The current username - * - * @return {object} An action object with a type of BOT_SUCCESS passing the repos - */ -export function deleteBotSucceeded(res) { - if (res.error === 1) { - addNotification("Some errors encountered", res.message, "error"); - } else { - addNotification("SUCCESS!", res.message, "success"); - } - return { - type: DELETE_BOT_SUCCESS, - message: res.message, - }; -} - -/** - * Dispatched when loading the repositories fails - * - * @param {object} error The error - * - * @return {object} An action object with a type of BOT_ERROR passing the error - */ -export function deleteBotFailed(error) { - addNotification("ERROR!", error.message, "error"); - return { - type: DELETE_BOT_SUCCESS, - error: error.message, - }; -} - -/** - * Activate bot - * GET /bot/activate/ - * @return {object} An action object with a type of BOT - */ -export function activateBot(id) { - return { - type: ACTIVATE_BOT, - data: id, - }; -} - -export function activateBotSucceeded(res) { - if (res.error === 1) { - addNotification("Some errors encountered", res.message, "error"); - } else { - addNotification("SUCCESS!", res.message, "success"); - } - return { - type: ACTIVATE_BOT_SUCCESS, - data: res.botId, - }; -} - -export function activateBotFailed(error) { - addNotification("Failed to fetch", error.message, "error"); - return { - type: ACTIVATE_BOT_ERROR, - }; -} - -/** - * Deactivate bot - * GET /bot/deactivate/ - * @return {object} An action object with a type of BOT - */ -export function deactivateBot(id) { - return { - type: DEACTIVATE_BOT, - data: id, - }; -} - -export function deactivateBotSucceeded(res) { - if (res.error === 1) { - addNotification("Some errors encountered", res.message, "error"); - } else { - addNotification("SUCCESS!", res.message, "success"); - } - return { - type: DEACTIVATE_BOT_SUCCESS, - data: res.botId, - message: res.message, - }; -} - -export function deactivateBotFailed(error) { - return { - type: DEACTIVATE_BOT_ERROR, - error: error.message, - }; -} - -/** - * Get symbols - * @return {object} An action object with a type of BOT - */ -export function getSymbols() { - return { - type: GET_SYMBOLS, - }; -} - -export function getSymbolsSucceeded(res) { - return { - type: GET_SYMBOLS_SUCCESS, - data: res.data, - }; -} - -export function getSymbolsFailed(error) { - return { - type: GET_SYMBOLS_ERROR, - error: error.message, - }; -} - -export function getSymbolInfo(pair) { - return { - type: GET_SYMBOL_INFO, - data: pair, - }; -} - -export function getSymbolInfoSucceeded(res) { - return { - type: GET_SYMBOL_INFO_SUCCESS, - data: res.data, - }; -} - -export function getSymbolInfoFailed(error) { - return { - type: GET_SYMBOL_INFO_ERROR, - error: error.message, - }; -} - -export function loadCandlestick(pair, interval, start_time) { - return { - type: LOAD_CANDLESTICK, - trace: null, - layout: null, - pair, - interval, - start_time, - }; -} - -export function loadCandlestickSucceeded(payload) { - if (payload.error === 1) { - addNotification("Some errors encountered", payload.message, "error"); - } else { - addNotification("SUCCESS!", "Candlestick data reloaded", "success"); - } - return { - type: LOAD_CANDLESTICK_SUCCESS, - payload, - }; -} - -export function loadCandlestickFailed(payload) { - return { - type: LOAD_CANDLESTICK_ERROR, - payload, - }; -} - -export function archiveBot(id) { - return { - type: ARCHIVE_BOT, - id: id, - }; -} - -export function archiveBotSucceeded(payload) { - return { - type: ARCHIVE_BOT_SUCCESS, - id: payload.botId, - }; -} - -export function archiveBotFailed(payload) { - return { - type: ARCHIVE_BOT_ERROR, - id: payload.botId, - }; -} - -// Autotrade settings -export function setSettingsState(payload) { - return { - type: SET_SETTINGS_STATE, - payload: payload, - }; -} - -export function getSettings() { - return { - type: GET_SETTINGS, - }; -} - -export function getSettingsSucceeded(payload) { - if (payload.error === 1) { - addNotification("FAILED!", payload.message, "error"); - } else { - addNotification("SUCCESS!", payload.message, "success"); - } - return { - type: GET_SETTINGS_SUCCESS, - data: payload.data, - }; -} - -export function getSettingsFailed() { - return { - type: GET_SETTINGS_ERROR, - }; -} - -export function editSettings(payload) { - return { - type: EDIT_SETTINGS, - data: payload, - }; -} - -export function editSettingsSucceeded(payload) { - if (payload.error === 1) { - addNotification("FAILED!", payload.message, "error"); - } else { - addNotification("SUCCESS!", payload.message, "success"); - } - - return { - type: EDIT_SETTINGS_SUCCESS, - data: payload.settings, - }; -} - -export function editSettingsFailed() { - return { - type: EDIT_SETTINGS_ERROR, - }; -} - -/** - * Clear out form when visiting /bots/new - * @return {object} An action object with a type of BOT - */ -export function resetBot() { - return { - type: RESET_BOT, - }; -} \ No newline at end of file diff --git a/web/src/pages/bots/api.js b/web/src/pages/bots/api.js new file mode 100644 index 000000000..03b4b9819 --- /dev/null +++ b/web/src/pages/bots/api.js @@ -0,0 +1,108 @@ +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; +import request, { buildBackUrl } from "../../request"; + +const baseUrl = buildBackUrl(); + +export const botsApi = createApi({ + baseQuery: fetchBaseQuery({ + // Fill in your own server starting URL here + baseUrl: baseUrl, + }), + endpoints: (build) => ({ + getBots: build.query({ + query: (params) => { + let requestURL = `${process.env.REACT_APP_GET_BOTS}`; + if (params) { + const { startDate, endDate, status = null } = params; + const params = `${startDate ? "start_date=" + startDate + "&" : ""}${ + endDate ? "end_date=" + endDate : "" + }${status ? "&status=" + status : ""}`; + requestURL += `?${params}`; + } + return request(requestURL); + }, + }), + getBot: build.query({ + query: (id) => { + const requestURL = `${process.env.REACT_APP_GET_BOTS}/${id}`; + return request(requestURL); + }, + }), + createBot: build.mutation({ + query: (data) => { + const requestURL = `${process.env.REACT_APP_GET_BOTS}`; + return request(requestURL, "POST", data); + }, + }), + editBot: build.mutation({ + query: ({ data, id }) => { + const requestURL = `${process.env.REACT_APP_GET_BOTS}/${id}`; + return request(requestURL, "PUT", data); + }, + }), + deleteBot: build.mutation({ + query: (removeId) => { + const ids = removeId; + const params = new URLSearchParams(ids.map((s) => ["id", s])); + const requestURL = `${ + process.env.REACT_APP_GET_BOTS + }?${params.toString()}`; + return request(requestURL, "DELETE"); + }, + }), + closeBot: build.mutation({ + query: (id) => { + const requestURL = `${process.env.REACT_APP_DEACTIVATE_BOT}/${id}`; + return request(requestURL, "DELETE"); + }, + }), + getSymbols: build.query({ + query: () => { + const requestURL = `${process.env.REACT_APP_NO_CANNIBALISM_SYMBOLS}`; + return request(requestURL); + }, + }), + getSymbolInfo: build.query({ + query: (pair) => { + const requestURL = `${process.env.REACT_APP_SYMBOL_INFO}/${pair}`; + return request(requestURL); + }, + }), + activateBot: build.mutation({ + query: (id) => { + const requestURL = `${process.env.REACT_APP_ACTIVATE_BOT}/${id}`; + return request(requestURL); + }, + }), + deactivateBot: build.mutation({ + query: (id) => { + const requestURL = `${process.env.REACT_APP_DEACTIVATE_BOT}/${id}`; + return request(requestURL, "DELETE"); + }, + }), + getCandlestick: build.query({ + query: ({ pair, interval }) => { + const requestURL = `${process.env.REACT_APP_CANDLESTICK}?symbol=${pair}&interval=${interval}`; + return request(requestURL); + }, + }), + archiveBot: build.mutation({ + query: (id) => { + const requestURL = `${process.env.REACT_APP_ARCHIVE_BOT}/${id}`; + return request(requestURL, "PUT"); + }, + }), + getSettings: build.query({ + query: () => { + const url = new URL(process.env.REACT_APP_RESEARCH_CONTROLLER, baseUrl); + return request(url); + }, + }), + editSettings: build.mutation({ + query: (data) => { + const url = new URL(process.env.REACT_APP_RESEARCH_CONTROLLER, baseUrl); + return request(url, "PUT", data); + }, + }), + }), +}); diff --git a/web/src/pages/bots/reducer.ts b/web/src/pages/bots/reducer.ts deleted file mode 100644 index 2bf4ac897..000000000 --- a/web/src/pages/bots/reducer.ts +++ /dev/null @@ -1,306 +0,0 @@ -import { CaseReducer, createSlice, PayloadAction, Slice } from '@reduxjs/toolkit' -import { - Bot, - bot, - computeSingleBotProfit, - computeTotalProfit, -} from "../../state/bots/actions"; -import { - GET_TEST_AUTOTRADE_SETTINGS, - GET_TEST_AUTOTRADE_SETTINGS_SUCCESS, - SET_TEST_AUTOTRADE_SETTING, -} from "../paper-trading/actions"; -import { - ACTIVATE_BOT, - ACTIVATE_BOT_ERROR, - ACTIVATE_BOT_SUCCESS, - ARCHIVE_BOT, - ARCHIVE_BOT_SUCCESS, - CLOSE_BOT, - CREATE_BOT, - CREATE_BOT_ERROR, - CREATE_BOT_SUCCESS, - DEACTIVATE_BOT, - DEACTIVATE_BOT_ERROR, - DEACTIVATE_BOT_SUCCESS, - DELETE_BOT, - DELETE_BOT_ERROR, - DELETE_BOT_SUCCESS, - EDIT_BOT, - EDIT_BOT_SUCCESS, - GET_BOTS, - GET_BOTS_ERROR, - GET_BOTS_SUCCESS, - GET_BOT_SUCCESS, - GET_SETTINGS, - GET_SETTINGS_SUCCESS, - GET_SYMBOLS, - GET_SYMBOLS_ERROR, - GET_SYMBOLS_SUCCESS, - GET_SYMBOL_INFO, - GET_SYMBOL_INFO_ERROR, - GET_SYMBOL_INFO_SUCCESS, - LOAD_CANDLESTICK, - LOAD_CANDLESTICK_ERROR, - LOAD_CANDLESTICK_SUCCESS, - SET_BOT, - SET_SETTINGS_STATE, - RESET_BOT, -} from "./actions"; - -type BotState = { - bots: any[]; - bot_profit: number; - errorBots: any[]; - bot: any; - data: any; - message: string; - botId: string; - totalProfit?: number; - params: { - startDate: string | null; - endDate: string | null; - }; -}; - - -interface BotPayload { - params?: any; - bots?: Bot[]; - error?: string; - removeId?: string; - message?: string; - data?: string | Bot; - id?: string; -} - - -// The initial state of the App -export const initialState: BotState = { - bots: [], - bot_profit: 0, // separate state to avoid tick re-rendering - errorBots: [], - bot: bot, - data: null, - message: null, - botId: null, - params: { - startDate: null, - endDate: null, - }, -}; - -export const settingsReducerInitial = { - candlestick_interval: "15m", - autotrade: 0, - max_request: 950, - telegram_signals: 1, - balance_to_use: "USDC", - balance_size_to_use: 0, - trailling: "true", - take_profit: 0, - trailling_deviation: 0, - stop_loss: 0, -}; - -export const botReducer = createSlice({ - name: "bot", - initialState, - reducers: { - getBots(state: BotState, action: PayloadAction) { - if (action.payload.params) { - state.params = action.payload.params; - } else { - state.params = initialState.params; - } - return state; - }, - getBotsSuccess(state: BotState, action: PayloadAction) { - if (action.payload.bots) { - state.bots = action.payload.bots; - state.errorBots = action.payload.bots.filter((x) => x.status === "error"); - state.totalProfit = computeTotalProfit(action.payload.bots); - } else { - state.bots = action.payload.bots; - } - return state; - }, - getBotsError(state: BotState, action: PayloadAction) { - return { - error: action.error, - }; - }, - deleteBot(state: BotState, action: PayloadAction) { - state.removeId = action.removeId; - return state; - }, - deleteBotSuccess(state: BotState, action: PayloadAction) { - let bots = state.bots.filter((x) => !x.id.includes(state.removeId)); - state.bots = bots; - state.totalProfit = computeTotalProfit(bots); - return state; - }, - deleteBotError(state: BotState, action: PayloadAction) { - return { - error: action.error, - botActive: state.botActive, - }; - }, - closeBot(state: BotState, action: PayloadAction) { - return state; - }, - activateBot(state: BotState, action: PayloadAction) { - return state; - }, - activateBotSuccess(state: BotState, action: PayloadAction) {) { - state.message = action.message; - state.botId = action.data; - return state; - }, - activateBotError(state: BotState, action: PayloadAction) { { - return { - error: action.error, - botActive: false, - }; - }, - deactivateBot(state: BotState, action: PayloadAction) { - const newState = { - data: state.data, - botActive: true, - }; - - return newState; - }, - deactivateBotSuccess(state: BotState, action: PayloadAction) {) { - const findidx = state.data.findIndex((x) => x.id === action.id); - state.data[findidx].status = "inactive"; - const newState = { - data: state.data, - message: action.message, - botActive: false, - }; - return newState; - }, - deactivateBotError(state: BotState, action: PayloadAction) {) { - return { - error: action.error, - botActive: true, - }; - }, - editBot(state: BotState, action: PayloadAction) { - return state; - }, - editBotSuccess(state: BotState, action: PayloadAction) { - const findidx = state.data.findIndex((x) => x.id === action.id); - state.data[findidx] = action.data; - return state; - }, - createBot(state: BotState, action: PayloadAction) { - return state; - }, - createBotSuccess(state: BotState, action: PayloadAction) { { - state.botId = action.botId; - return state; - }, - createBotError(state: BotState, action: PayloadAction) { - return { - error: action.error, - botActive: false, - }; - }, - setBot(state: BotState, action: PayloadAction) { - state.bot = action.data; - return state; - }, - resetBot(state: BotState, action: PayloadAction) { - state.bot = initialState.bot; - return state; - }, - getBotSuccess(state: BotState, action: PayloadAction) { - state.bot = action.bots; - state.bot_profit = computeSingleBotProfit(action.bots); - return state; - }, - getBotError(state: BotState, action: PayloadAction) { - return { - error: action.error, - }; - }, - }, -}); - - -export const symbolReducer = createSlice({ - name: "symbol", - initialState, - reducers: { - getSymbols(state: BotState, action: PayloadAction) { - return state; - }, - getSymbolsSuccess(state: BotState, action: PayloadAction) {) { - state.data = action.data; - return state; - }, - getSymbolsError(state: BotState, action: PayloadAction) {{ - return { - error: action.error, - }; - }, - }, -}); - -export const symbolInfoReducer = createSlice({ - name: "symbolInfo", - initialState, - reducers: { - getSymbolInfo(state: BotState, action: PayloadAction) { - return state; - }, - getSymbolInfoSuccess(state: BotState, action: PayloadAction) {) { - state.data = action.data; - return state; - }, - getSymbolInfoError(state: BotState, action: PayloadAction) {) { - return { - error: action.error, - }; - }, - }, -}); - -export const candlestickReducer = createSlice({ - name: "candlestick", - initialState, - reducers: { - loadCandlestick(state: BotState, action: PayloadAction) {{ - return state; - }, - loadCandlestickSuccess(state: BotState, action: PayloadAction) {) { - state.data = action.data; - return state; - }, - loadCandlestickError(state: BotState, action: PayloadAction) {) { - return { - error: action.error, - }; - }, - }, -}); - -export const settingsReducer = createSlice({ - name: "settings", - initialState, - reducers: { - getSettings(state: BotState, action: PayloadAction) { - return state; - }, - getSettingsSuccess(state: BotState, action: PayloadAction) {) { - state.data = action.data; - return state; - }, - setSettingsState(state: BotState, action: PayloadAction) { { - state.data = { ...state.data, ...action.payload }; - return state; - }, - }, -}); diff --git a/web/src/pages/bots/saga.js b/web/src/pages/bots/saga.js index a43824c08..1f320235f 100644 --- a/web/src/pages/bots/saga.js +++ b/web/src/pages/bots/saga.js @@ -53,9 +53,11 @@ const baseUrl = buildBackUrl(); export function* getBotsApi(payload) { let requestURL = `${process.env.REACT_APP_GET_BOTS}`; if (payload.params) { - const { startDate, endDate, status=null } = payload.params; - const params = `${startDate ? "start_date=" + startDate + "&" : ""}${endDate ? "end_date=" + endDate : ""}${status ? "&status=" + status : ""}`; - requestURL += `?${params}` + const { startDate, endDate, status = null } = payload.params; + const params = `${startDate ? "start_date=" + startDate + "&" : ""}${ + endDate ? "end_date=" + endDate : "" + }${status ? "&status=" + status : ""}`; + requestURL += `?${params}`; } try { @@ -132,7 +134,7 @@ export function* watchEditBot() { */ export function* deleteBotApi(payload) { const ids = payload.removeId; - const params = new URLSearchParams(ids.map(s=>['id',s])) + const params = new URLSearchParams(ids.map((s) => ["id", s])); const requestURL = `${process.env.REACT_APP_GET_BOTS}?${params.toString()}`; try { const res = yield call(request, requestURL, "DELETE"); @@ -265,8 +267,8 @@ export function* watchArchiveBot() { /** * Settings (controller) */ - export function* getSettingsApi() { - const url = new URL(process.env.REACT_APP_RESEARCH_CONTROLLER, baseUrl) +export function* getSettingsApi() { + const url = new URL(process.env.REACT_APP_RESEARCH_CONTROLLER, baseUrl); try { const res = yield call(request, url); yield put(getSettingsSucceeded(res)); @@ -279,11 +281,10 @@ export function* watchGetSettingsApi() { yield takeLatest(GET_SETTINGS, getSettingsApi); } - /** * Edit Settings (controller) */ - export function* editSettingsApi({ data }) { +export function* editSettingsApi({ data }) { const url = new URL(process.env.REACT_APP_RESEARCH_CONTROLLER, baseUrl); try { const res = yield call(request, url, "PUT", data); diff --git a/web/src/state/bots/actions.ts b/web/src/state/bots/actions.ts index 61b7eb2a1..000f64376 100644 --- a/web/src/state/bots/actions.ts +++ b/web/src/state/bots/actions.ts @@ -1,87 +1,6 @@ -import { checkValue, intervalOptions } from "../../validations"; +import { checkValue } from "../../validations"; import { FILTER_BY_MONTH, FILTER_BY_WEEK } from "../constants"; -/** - * This file contains redux state - * that is shared between bots views - * at the time of writing: bots and paper trading (test bots) - */ - -export interface Bot { - id: string | null; - status: string; - balance_available: string; - balance_available_asset: string; - balanceAvailableError: boolean; - balanceUsageError: boolean; - balance_size_to_use: number; - max_so_count: string; - maxSOCountError: boolean; - name: string; - nameError: boolean; - pair: string; - price_deviation_so: string; - priceDevSoError: boolean; - so_size: string; - soSizeError: boolean; - take_profit: number; - takeProfitError: boolean; - trailling: string; - trailling_deviation: number; - dynamic_trailling: boolean; - traillingDeviationError: boolean; - formIsValid: boolean; - candlestick_interval: string; - deal: any; -} - -// The initial state of the App -export const bot: Bot = { - id: null, - status: "inactive", - balance_available: "0", - balance_available_asset: "", - balanceAvailableError: false, - balanceUsageError: false, - balance_size_to_use: 0, // Centralized - base_order_size: 50, - baseOrderSizeError: false, - balance_to_use: "USDC", - errors: [], - mode: "manual", - max_so_count: "0", - maxSOCountError: false, - name: "Default bot", - nameError: false, - pair: "", - price_deviation_so: "0.63", - priceDevSoError: false, - so_size: "0", - soSizeError: false, - take_profit: 2.3, - takeProfitError: false, - trailling: "false", - trailling_deviation: 2.8, - dynamic_trailling: false, - traillingDeviationError: false, - formIsValid: true, - candlestick_interval: intervalOptions[3], - deal: {}, - orders: [], - quoteAsset: "", - baseAsset: "", - stop_loss: 3, - margin_short_reversal: true, - stopLossError: false, - safety_orders: [], - addAllError: "", - cooldown: 0, - strategy: "long", - marginShortError: null, - short_buy_price: 0, - short_sell_price: 0, -}; - export function setFilterByWeek() { return { type: FILTER_BY_WEEK, @@ -94,7 +13,11 @@ export function setFilterByMonthState() { }; } -export function getProfit(base_price, current_price, strategy = "long") : number { +export function getProfit( + base_price, + current_price, + strategy = "long" +): number { if (!checkValue(base_price) && !checkValue(current_price)) { let percent = ((parseFloat(current_price) - parseFloat(base_price)) / @@ -113,13 +36,17 @@ export function getProfit(base_price, current_price, strategy = "long") : number * @param {bot} bot object * @returns {float} */ -function getInterestsShortMargin(bot): { interests: number, openTotal: number, closeTotal: number } { +function getInterestsShortMargin(bot): { + interests: number; + openTotal: number; + closeTotal: number; +} { let closeTimestamp = bot.deal.margin_short_buy_back_timestamp; if (closeTimestamp === 0) { - closeTimestamp = new Date().getTime() + closeTimestamp = new Date().getTime(); } const timeDelta = closeTimestamp - bot.deal.margin_short_sell_timestamp; - const durationHours = (timeDelta / 1000) / 3600 + const durationHours = timeDelta / 1000 / 3600; const interests = parseFloat(bot.deal.hourly_interest_rate) * durationHours; const closeTotal = parseFloat(bot.deal.margin_short_buy_back_price); const openTotal = parseFloat(bot.deal.margin_short_sell_price); @@ -127,7 +54,7 @@ function getInterestsShortMargin(bot): { interests: number, openTotal: number, c interests: interests, openTotal: openTotal, closeTotal: closeTotal, - } + }; } /** @@ -155,12 +82,11 @@ export function computeSingleBotProfit(bot, realTimeCurrPrice = null) { } else if (bot.deal.margin_short_sell_price > 0) { // Completed margin short if (bot.deal.margin_short_buy_back_price > 0) { - - const { interests, openTotal, closeTotal} = getInterestsShortMargin(bot); - let profitChange = - parseFloat( - (((openTotal - closeTotal) / openTotal) - interests) * 100 - ); + const { interests, openTotal, closeTotal } = + getInterestsShortMargin(bot); + let profitChange = parseFloat( + ((openTotal - closeTotal) / openTotal - interests) * 100 + ); return +profitChange.toFixed(2); } else { // Not completed margin_sho @@ -172,10 +98,9 @@ export function computeSingleBotProfit(bot, realTimeCurrPrice = null) { return 0; } const { interests, openTotal } = getInterestsShortMargin(bot); - let profitChange = - parseFloat( - (((openTotal - closePrice) / openTotal) - interests) * 100 - ); + let profitChange = parseFloat( + ((openTotal - closePrice) / openTotal - interests) * 100 + ); return +profitChange.toFixed(2); } } else { @@ -196,21 +121,20 @@ export function computeTotalProfit(bots) { !checkValue(bot.deal.take_profit_price) && parseFloat(bot.deal.take_profit_price) > 0 ) { - let enterPositionPrice = 0; let exitPositionPrice = bot.deal.current_price; if (bot.deal.buy_price > 0) { - enterPositionPrice = bot.deal.buy_price + enterPositionPrice = bot.deal.buy_price; } if (bot.deal.margin_short_sell_price > 0) { - enterPositionPrice = bot.deal.margin_short_sell_price + enterPositionPrice = bot.deal.margin_short_sell_price; } if (bot.deal.sell_price > 0) { - exitPositionPrice = bot.deal.sell_price + exitPositionPrice = bot.deal.sell_price; } if (bot.deal.margin_short_buy_back_price > 0) { - exitPositionPrice = bot.deal.margin_short_buy_back_price + exitPositionPrice = bot.deal.margin_short_buy_back_price; } if (exitPositionPrice === 0 || enterPositionPrice === 0) { @@ -222,7 +146,6 @@ export function computeTotalProfit(bots) { bot.strategy ); } - } return parseFloat(accumulator) + parseFloat(currTotalProfit); }, 0); diff --git a/web/src/state/bots/reducer.ts b/web/src/state/bots/reducer.ts new file mode 100644 index 000000000..f3ed52efc --- /dev/null +++ b/web/src/state/bots/reducer.ts @@ -0,0 +1,246 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { + computeSingleBotProfit, + computeTotalProfit, +} from "./actions"; +import { BotState, BotPayload, SettingsState } from "./types"; +import { addNotification } from "../../validations"; + +// The initial state of the App +export const initialState: BotState = { + bots: [], + bot_profit: 0, // separate state to avoid tick re-rendering + bot: null, + data: null, + message: null, + botId: null, + params: { + startDate: null, + endDate: null, + }, +}; + +const settingsReducerInitial: SettingsState = { + candlestick_interval: "15m", + autotrade: 0, + max_request: 950, + telegram_signals: 1, + balance_to_use: "USDC", + balance_size_to_use: 0, + trailling: "true", + take_profit: 0, + trailling_deviation: 0, + stop_loss: 0, +}; + +/** + * Handle notification messages + * HTTP 200 errors with error message + * this differs from the error message from the server + * which can be 422, 404, 500 etc which are handled by + * getBotsError, deleteBotError, activateBotError, deactivateBotError, createBotError... + * @param payload: BotPayload +**/ +function handleNotifications({ payload }) { + if (payload.error === 1) { + addNotification("Some errors encountered", payload.message, "error"); + } else { + addNotification("SUCCESS!", payload.message, "success"); + } +} + +const botReducer = createSlice({ + name: "bot", + initialState, + reducers: { + getBots(state: BotState, action: PayloadAction) { + if (action.payload.params) { + state.params.startDate = action.payload.params.startDate; + state.params = action.payload.params; + } else { + state.params = initialState.params; + } + return state; + }, + getBotsSuccess(state: BotState, action: PayloadAction) { + handleNotifications(action); + if (action.payload.bots) { + state.bots = action.payload.bots; + state.totalProfit = computeTotalProfit(action.payload.bots); + } else { + state.bots = action.payload.bots; + } + return state; + }, + getBotsError(state: BotState, action: PayloadAction) { + state.error = action.payload.error; + return state; + }, + deleteBot(state: BotState, action: PayloadAction) { + state.removeId = action.payload.removeId; + return state; + }, + deleteBotSuccess(state: BotState, action: PayloadAction) { + handleNotifications(action); + const bots = state.bots.filter((x) => !x.id.includes(state.removeId)); + state.bots = bots; + state.totalProfit = computeTotalProfit(bots); + return state; + }, + deleteBotError(state: BotState, action: PayloadAction) { + state.error = action.payload.error; + }, + activateBot(state: BotState) { + return state; + }, + activateBotSuccess(state: BotState, action: PayloadAction) { + handleNotifications(action); + state.message = action.payload.message; + state.botId = action.payload.data; + return state; + }, + activateBotError(state: BotState, action: PayloadAction) { + state.error = action.payload.error; + state.botActive = false; + }, + deactivateBot(state: BotState, action: PayloadAction) { + state.data = action.data; + state.botActive = true; + }, + deactivateBotSuccess(state: BotState, action: PayloadAction) { + handleNotifications(action); + const findidx = state.data.findIndex((x) => x.id === action.id); + state.data[findidx].status = "inactive"; + const newState = { + data: state.data, + message: action.payload.message, + botActive: false, + }; + return newState; + }, + deactivateBotError(state: BotState, action: PayloadAction) { + state.error = action.payload.error; + state.botActive = true; + }, + editBot(state: BotState) { + return state; + }, + editBotSuccess(state: BotState, action: PayloadAction) { + const findidx = state.data.findIndex((x) => x.id === action.payload.id); + handleNotifications(action); + state.data[findidx] = action.payload.data; + return state; + }, + createBot(state: BotState) { + return state; + }, + createBotSuccess(state: BotState, action: PayloadAction) { + handleNotifications(action); + state.botId = action.payload.botId; + return state; + }, + createBotError(state: BotState, action: PayloadAction) { + state.error = action.payload.error; + state.botActive = false; + }, + setBot(state: BotState, action: PayloadAction) { + state.bot = action.payload.data; + return state; + }, + resetBot(state: BotState) { + state.bot = initialState.bot; + return state; + }, + getBotSuccess(state: BotState, action: PayloadAction) { + handleNotifications(action); + state.bots = action.payload.bots; + state.bot_profit = computeSingleBotProfit(action.payload.bots); + return state; + }, + getBotError(state: BotState, action: PayloadAction) { + state.error = action.payload.error; + }, + }, +}); + +const symbolReducer = createSlice({ + name: "symbol", + initialState, + reducers: { + getSymbols(state: BotState) { + return state; + }, + getSymbolsSuccess(state: BotState, action: PayloadAction) { + handleNotifications(action); + state.data = action.payload.data; + return state; + }, + getSymbolsError(state: BotState, action: PayloadAction) { + state.error = action.payload.error; + }, + }, +}); + +const symbolInfoReducer = createSlice({ + name: "symbolInfo", + initialState, + reducers: { + getSymbolInfo(state: BotState) { + return state; + }, + getSymbolInfoSuccess(state: BotState, action: PayloadAction) { + handleNotifications(action); + state.data = action.payload.data; + return state; + }, + getSymbolInfoError(state: BotState, action: PayloadAction) { + state.error = action.payload.error; + }, + }, +}); + +const candlestickReducer = createSlice({ + name: "candlestick", + initialState, + reducers: { + loadCandlestick(state: BotState) { + return state; + }, + loadCandlestickSuccess(state: BotState, action: PayloadAction) { + handleNotifications(action); + state.data = action.payload.data; + return state; + }, + loadCandlestickError(state: BotState, action: PayloadAction) { + state.error = action.payload.error; + }, + }, +}); + +const settingsReducer = createSlice({ + name: "settings", + initialState: settingsReducerInitial, + reducers: { + getSettings(state: SettingsState) { + return state; + }, + getSettingsSuccess(state: SettingsState, action: PayloadAction) { + handleNotifications(action); + state.data = action.payload.data; + return state; + }, + setSettingsState(state: SettingsState, action: PayloadAction) { + handleNotifications(action); + state.data = { ...state.data, ...action }; + return state; + }, + }, +}); + +export { + botReducer, + symbolReducer, + symbolInfoReducer, + candlestickReducer, + settingsReducer, +}; diff --git a/web/src/state/bots/types.ts b/web/src/state/bots/types.ts new file mode 100644 index 000000000..a49b09134 --- /dev/null +++ b/web/src/state/bots/types.ts @@ -0,0 +1,242 @@ +/** + * Enums for bot status + * refer to api/bots/schema.py + */ +enum BotStatus { + active = "active", + inactive = "inactive", + completed = "completed", + error = "error", + archived = "archived", +} + +enum BotStrategy { + long = "long", + margin_short = "margin_short", +} + +enum BotMode { + manual = "manual", + autotrade = "autotrade", +} + +/** + * Enums for candlestick intervals + * from Binance + */ +enum IntervalOptions { + oneMinute = "1m", + threeMinutes = "3m", + fiveMinutes = "5m", + fifteenMinutes = "15m", + thirtyMinutes = "30m", + oneHour = "1h", + twoHours = "2h", + fourHours = "4h", + sixHours = "6h", + eightHours = "8h", + twelveHours = "12h", + oneDay = "1d", + threeDays = "3d", + oneWeek = "1w", + oneMonth = "1M", +} +interface Deal { + buy_price: number; + buy_total_qty: number; + buy_timestamp: number; + current_price: number; + sd: number; + avg_buy_price: number; + take_profit_price: number; + sell_timestamp: number; + sell_price: number; + sell_qty: number; + trailling_stop_loss_price: number; + trailling_profit_price: number; + stop_loss_price: number; + trailling_profit: number; + so_prices: number; + margin_short_loan_principal: number; + margin_loan_id: number; + hourly_interest_rate: number; + margin_short_sell_price: number; + margin_short_loan_interest: number; + margin_short_buy_back_price: number; + margin_short_sell_qty: number; + margin_short_buy_back_timestamp: number; + margin_short_base_order: number; + margin_short_sell_timestamp: number | string; + margin_short_loan_timestamp: number | string; +} + +/** + * This file contains redux state + * that is shared between bots views + * at the time of writing: bots and paper trading (test bots) + */ + +export interface Bot { + id: string | null; + status: BotStatus; + balance_available: string; + balance_available_asset: string; + base_order_size: number; + balance_to_use: string; + errors: string[]; + baseOrderSizeError: boolean; + balanceAvailableError: boolean; + balanceUsageError: boolean; + balance_size_to_use: number; + max_so_count: string; + maxSOCountError: boolean; + mode: BotMode; + name: string; + nameError: boolean; + pair: string; + price_deviation_so: string; + priceDevSoError: boolean; + so_size: string; + soSizeError: boolean; + take_profit: number; + takeProfitError: boolean; + trailling: string; + trailling_deviation: number; + dynamic_trailling: boolean; + traillingDeviationError: boolean; + formIsValid: boolean; + candlestick_interval: string; + deal: Deal; // Handled server side, shouldn't be changed in the client + strategy: BotStrategy; + orders: object[]; + quoteAsset: string; + baseAsset: string; + stop_loss: number; + margin_short_reversal: boolean; + stopLossError: boolean; + safety_orders: object[]; + addAllError: string; + cooldown: number; + marginShortError: string | null; + short_buy_price: number; + short_sell_price: number; +} + +// The initial state of the App +export const bot: Bot = { + id: null, + status: BotStatus.inactive, + balance_available: "0", + balance_available_asset: "", + balanceAvailableError: false, + balanceUsageError: false, + balance_size_to_use: 0, // Centralized + base_order_size: 50, + baseOrderSizeError: false, + balance_to_use: "USDC", + errors: [], + mode: BotMode.manual, + max_so_count: "0", + maxSOCountError: false, + name: "Default bot", + nameError: false, + pair: "", + price_deviation_so: "0.63", + priceDevSoError: false, + so_size: "0", + soSizeError: false, + take_profit: 2.3, + takeProfitError: false, + trailling: "false", + trailling_deviation: 2.8, + dynamic_trailling: false, + traillingDeviationError: false, + formIsValid: true, + candlestick_interval: IntervalOptions.fifteenMinutes, + deal: { + buy_price: 0, + buy_total_qty: 0, + buy_timestamp: 0, + current_price: 0, + sd: 0, + avg_buy_price: 0, + take_profit_price: 0, + sell_timestamp: 0, + sell_price: 0, + sell_qty: 0, + trailling_stop_loss_price: 0, + trailling_profit_price: 0, + stop_loss_price: 0, + trailling_profit: 0, + so_prices: 0, + margin_short_loan_principal: 0, + margin_loan_id: 0, + hourly_interest_rate: 0, + margin_short_sell_price: 0, + margin_short_loan_interest: 0, + margin_short_buy_back_price: 0, + margin_short_sell_qty: 0, + margin_short_buy_back_timestamp: 0, + margin_short_base_order: 0, + margin_short_sell_timestamp: 0, + margin_short_loan_timestamp: 0, + }, + orders: [], + quoteAsset: "", + baseAsset: "", + stop_loss: 3, + margin_short_reversal: true, + stopLossError: false, + safety_orders: [], + addAllError: "", + cooldown: 0, + strategy: BotStrategy.long, + marginShortError: null, + short_buy_price: 0, + short_sell_price: 0, +}; + + +export type FilterParams = { + startDate: string | null; + endDate: string | null; +}; + +export type BotState = { + bots?: Bot[]; + bot_profit?: number; + bot?: Bot; + data?: any; + message: string; + botId?: string; + totalProfit?: number; + params?: FilterParams; + botActive?: boolean; + removeId?: string; + error?: number; +}; + +export interface BotPayload { + params?: FilterParams; + bots?: Bot[]; + error?: number; + removeId?: string; + message?: string; + data?: any; + id?: string; + botId?: string; +} + +export interface SettingsState { + candlestick_interval: string; + autotrade: number; + max_request: number; + telegram_signals: number; + balance_to_use: string; + balance_size_to_use: number; + trailling: string; + take_profit: number; + trailling_deviation: number; + stop_loss: number; + data?: object | Array | null; +} diff --git a/web/src/store.ts b/web/src/store.ts index ba70b7619..463b33ed5 100644 --- a/web/src/store.ts +++ b/web/src/store.ts @@ -8,9 +8,9 @@ import { candlestickReducer, symbolInfoReducer, symbolReducer, -} from "./pages/bots/reducer"; + settingsReducer +} from "./state/bots/reducer"; import { testBotsReducer } from "./pages/paper-trading/reducer"; -import { settingsReducer } from "./pages/bots/reducer"; import { blacklistReducer } from "./pages/research/reducer"; import { balanceRawReducer, estimateReducer } from "./state/balances/reducer"; import { @@ -18,20 +18,25 @@ import { btcBenchmarkReducer, gainersLosersSeriesReducer, } from "./pages/dashboard/reducer"; +import { botsApi } from "./pages/bots/api"; export const store = configureStore({ reducer: { + // API slices + [botsApi.reducerPath]: botsApi.reducer, + + // Reducer slices + botReducer: botReducer.reducer, + symbolInfoReducer: symbolInfoReducer.reducer, + symbolReducer: symbolReducer.reducer, + candlestickReducer: candlestickReducer.reducer, + settingsReducer: settingsReducer.reducer, registrationReducer, loginReducer, balanceRawReducer, - botReducer, - symbolInfoReducer, - symbolReducer, - candlestickReducer, toastr: toastrReducer, loadingReducer, blacklistReducer, - settingsReducer, estimateReducer, testBotsReducer, gainersLosersReducer, diff --git a/web/yarn.lock b/web/yarn.lock index d412398c8..a965e8b91 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -2343,12 +2343,12 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.6.tgz#ae1973dd2b1eeb1825695bb11ebfb746d27e3e93" integrity sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA== -"@types/node@^20.8.6": - version "20.8.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.6.tgz#0dbd4ebcc82ad0128df05d0e6f57e05359ee47fa" - integrity sha512-eWO4K2Ji70QzKUqRy6oyJWUeB7+g2cRagT3T/nxYibYcT4y2BDL8lqolRXjTHmkZCdJfIPaY73KbJAZmcryxTQ== +"@types/node@^22.7.4": + version "22.7.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.4.tgz#e35d6f48dca3255ce44256ddc05dee1c23353fcc" + integrity sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg== dependencies: - undici-types "~5.25.1" + undici-types "~6.19.2" "@types/parse-json@^4.0.0": version "4.0.0" @@ -10786,10 +10786,10 @@ uncontrollable@^7.2.1: invariant "^2.2.4" react-lifecycles-compat "^3.0.4" -undici-types@~5.25.1: - version "5.25.3" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" - integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0"