From 65c4ec4957afd2f6213f4fcbf6aa279cda63a690 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Sun, 11 May 2025 17:23:02 +0700 Subject: [PATCH] Clean a bit and add prod env to front --- .gitignore | 2 - src/Managing.Api/Controllers/BotController.cs | 7 +- .../Services/IBacktester.cs | 8 +- .../Backtesting/Backtester.cs | 8 +- src/Managing.Application/Bots/TradingBot.cs | 4 +- .../Bots/TradingBotConfig.cs | 4 +- src/Managing.WebApp/.env | 11 +- .../src/app/store/apiStore.tsx | 96 ++++++++++++---- .../components/mollecules/NavBar/NavBar.tsx | 44 +++++--- .../organism/Backtest/backtestModal.tsx | 57 +++++----- src/Managing.WebApp/src/main.tsx | 29 +++-- .../src/pages/web3Page/web3.tsx | 104 ------------------ .../src/smartcontracts/courses/mood.sol | 16 --- .../src/smartcontracts/courses/odaNFT.sol | 14 --- .../src/smartcontracts/courses/odaToken.sol | 10 -- 15 files changed, 184 insertions(+), 230 deletions(-) delete mode 100644 src/Managing.WebApp/src/pages/web3Page/web3.tsx delete mode 100644 src/Managing.WebApp/src/smartcontracts/courses/mood.sol delete mode 100644 src/Managing.WebApp/src/smartcontracts/courses/odaNFT.sol delete mode 100644 src/Managing.WebApp/src/smartcontracts/courses/odaToken.sol diff --git a/.gitignore b/.gitignore index c8af6fe..234a5ef 100644 --- a/.gitignore +++ b/.gitignore @@ -380,5 +380,3 @@ src/Managing.Infrastructure.Tests/PrivateKeys.cs # Node.js Tools for Visual Studio node_modules/ - - diff --git a/src/Managing.Api/Controllers/BotController.cs b/src/Managing.Api/Controllers/BotController.cs index e91d082..5101eb7 100644 --- a/src/Managing.Api/Controllers/BotController.cs +++ b/src/Managing.Api/Controllers/BotController.cs @@ -140,7 +140,9 @@ public class BotController : BaseController Timeframe = request.Timeframe, IsForWatchingOnly = request.IsForWatchOnly, BotTradingBalance = request.InitialTradingBalance, - BotType = request.BotType + BotType = request.BotType, + CooldownPeriod = request.CooldownPeriod, + MaxLossStreak = request.MaxLossStreak }; var result = await _mediator.Send(new StartBotCommand(config, request.Identifier, user)); @@ -622,4 +624,7 @@ public class StartBotRequest /// The initial trading balance /// public decimal InitialTradingBalance { get; set; } + + public int CooldownPeriod { get; set; } + public int MaxLossStreak { get; set; } } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Services/IBacktester.cs b/src/Managing.Application.Abstractions/Services/IBacktester.cs index 6e0e64a..9a689a4 100644 --- a/src/Managing.Application.Abstractions/Services/IBacktester.cs +++ b/src/Managing.Application.Abstractions/Services/IBacktester.cs @@ -23,7 +23,7 @@ namespace Managing.Application.Abstractions.Services bool isForWatchingOnly = false, bool save = false, List? initialCandles = null, - decimal cooldownPeriod = 1, + int cooldownPeriod = 1, int maxLossStreak = 0); Task RunFlippingBotBacktest( @@ -39,7 +39,7 @@ namespace Managing.Application.Abstractions.Services bool isForWatchingOnly = false, bool save = false, List? initialCandles = null, - decimal cooldownPeriod = 1, + int cooldownPeriod = 1, int maxLossStreak = 0); bool DeleteBacktest(string id); @@ -53,7 +53,7 @@ namespace Managing.Application.Abstractions.Services List candles, decimal balance, User user = null, - decimal cooldownPeriod = 1, + int cooldownPeriod = 1, int maxLossStreak = 0); Task RunFlippingBotBacktest( @@ -64,7 +64,7 @@ namespace Managing.Application.Abstractions.Services List candles, decimal balance, User user = null, - decimal cooldownPeriod = 1, + int cooldownPeriod = 1, int maxLossStreak = 0); // User-specific operations diff --git a/src/Managing.Application/Backtesting/Backtester.cs b/src/Managing.Application/Backtesting/Backtester.cs index 72fb302..d66da20 100644 --- a/src/Managing.Application/Backtesting/Backtester.cs +++ b/src/Managing.Application/Backtesting/Backtester.cs @@ -66,7 +66,7 @@ namespace Managing.Application.Backtesting bool isForWatchingOnly = false, bool save = false, List? initialCandles = null, - decimal cooldownPeriod = 1, + int cooldownPeriod = 1, int maxLossStreak = 0) { var config = new TradingBotConfig @@ -121,7 +121,7 @@ namespace Managing.Application.Backtesting bool isForWatchingOnly = false, bool save = false, List? initialCandles = null, - decimal cooldownPeriod = 1, + int cooldownPeriod = 1, int maxLossStreak = 0) { var config = new TradingBotConfig @@ -172,7 +172,7 @@ namespace Managing.Application.Backtesting List candles, decimal balance, User user = null, - decimal cooldownPeriod = 1, + int cooldownPeriod = 1, int maxLossStreak = 0) { var ticker = MiscExtensions.ParseEnum(candles.FirstOrDefault().Ticker); @@ -214,7 +214,7 @@ namespace Managing.Application.Backtesting List candles, decimal balance, User user = null, - decimal cooldownPeriod = 1, + int cooldownPeriod = 1, int maxLossStreak = 0) { var ticker = MiscExtensions.ParseEnum(candles.FirstOrDefault().Ticker); diff --git a/src/Managing.Application/Bots/TradingBot.cs b/src/Managing.Application/Bots/TradingBot.cs index 8d06bc6..1b31ce4 100644 --- a/src/Managing.Application/Bots/TradingBot.cs +++ b/src/Managing.Application/Bots/TradingBot.cs @@ -1008,6 +1008,7 @@ public class TradingBot : Bot, ITradingBot BotTradingBalance = data.BotTradingBalance, BotType = data.BotType, CooldownPeriod = data.CooldownPeriod, + MaxLossStreak = data.MaxLossStreak, Name = data.Name }; @@ -1077,5 +1078,6 @@ public class TradingBotBackup public MoneyManagement MoneyManagement { get; set; } public DateTime StartupTime { get; set; } public decimal BotTradingBalance { get; set; } - public decimal CooldownPeriod { get; set; } + public int CooldownPeriod { get; set; } + public int MaxLossStreak { get; set; } } \ No newline at end of file diff --git a/src/Managing.Application/Bots/TradingBotConfig.cs b/src/Managing.Application/Bots/TradingBotConfig.cs index 151ef82..4b2a80d 100644 --- a/src/Managing.Application/Bots/TradingBotConfig.cs +++ b/src/Managing.Application/Bots/TradingBotConfig.cs @@ -15,7 +15,7 @@ public class TradingBotConfig public bool FlipPosition { get; set; } public BotType BotType { get; set; } public decimal BotTradingBalance { get; set; } - public decimal CooldownPeriod { get; set; } = 1; - public int MaxLossStreak { get; set; } = 0; // 0 means no limit + public int CooldownPeriod { get; set; } = 1; + public int MaxLossStreak { get; set; } = 0; public string Name { get; set; } } \ No newline at end of file diff --git a/src/Managing.WebApp/.env b/src/Managing.WebApp/.env index 6128ea3..6fbc885 100644 --- a/src/Managing.WebApp/.env +++ b/src/Managing.WebApp/.env @@ -1,7 +1,16 @@ VITE_API_URL_LOCAL=http://localhost:5000 -VITE_API_URL_SERVER=https://dev-managing-api.apps.managing.live +VITE_API_URL_SANDBOX=https://dev-managing-api.apps.managing.live +VITE_API_URL_SERVER=https://api.kaigen.managing.live + VITE_WORKER_URL_LOCAL=https://localhost:5002 +VITE_WORKER_URL_SANDBOX=https://dev-managing-worker.apps.managing.live VITE_WORKER_URL_SERVER=https://dev-managing-worker.apps.managing.live + + ALCHEMY_ID=Bao7OirVe4bmYiDbPh0l8cs5gYb5D4_9 WALLET_CONNECT_PROJECT_ID=363bf09c10fec2293b21ee199b2ce8d5 VITE_PRIVY_APP_ID=cm7u09v0u002zrkuf2yjjr58p + +VITE_PRIVY_APP_ID_LOCAL=cm7u09v0u002zrkuf2yjjr58p +VITE_PRIVY_APP_ID_SANDBOX=cm7u09v0u002zrkuf2yjjr58p +VITE_PRIVY_APP_ID_PRODUCTION=cm6kkz5ke00n5ffmpwdbr05mp \ No newline at end of file diff --git a/src/Managing.WebApp/src/app/store/apiStore.tsx b/src/Managing.WebApp/src/app/store/apiStore.tsx index 632fb70..f8544ce 100644 --- a/src/Managing.WebApp/src/app/store/apiStore.tsx +++ b/src/Managing.WebApp/src/app/store/apiStore.tsx @@ -1,28 +1,86 @@ import create from 'zustand' -type ApiStore = { - isProd: boolean - apiUrl: string - workerUrl: string - toggleApiUrl: () => void +type Environment = 'local' | 'sandbox' | 'production' + +const ENV_COOKIE_NAME = 'app_environment' + +// Cookie utility functions +const getCookie = (name: string): string | undefined => { + const cookies = document.cookie.split(';').reduce((acc, cookie) => { + const [key, value] = cookie.trim().split('=') + return { ...acc, [key]: value } + }, {} as Record) + return cookies[name] } +const setCookie = (name: string, value: string, daysToExpire: number) => { + const expirationDate = new Date( + new Date().getTime() + daysToExpire * 24 * 60 * 60 * 1000 + ).toUTCString() + document.cookie = `${name}=${value}; expires=${expirationDate}; path=/` +} + +const getUrlsForEnvironment = (env: Environment) => { + switch (env) { + case 'local': + return { + apiUrl: import.meta.env.VITE_API_URL_LOCAL, + workerUrl: import.meta.env.VITE_WORKER_URL_LOCAL, + privyAppId: import.meta.env.VITE_PRIVY_APP_ID_LOCAL, + } + case 'sandbox': + return { + apiUrl: import.meta.env.VITE_API_URL_SANDBOX, + workerUrl: import.meta.env.VITE_WORKER_URL_SANDBOX, + privyAppId: import.meta.env.VITE_PRIVY_APP_ID_SANDBOX, + } + case 'production': + return { + apiUrl: import.meta.env.VITE_API_URL_SERVER, + workerUrl: import.meta.env.VITE_WORKER_URL_SERVER, + privyAppId: import.meta.env.VITE_PRIVY_APP_ID_PRODUCTION, + } + } +} + +type ApiStore = { + environment: Environment + apiUrl: string + workerUrl: string + privyAppId: string + setEnvironment: (env: Environment) => void + prepareEnvironmentChange: (env: Environment) => void +} + +const getInitialEnvironment = (): Environment => { + const savedEnv = getCookie(ENV_COOKIE_NAME) as Environment + return savedEnv || 'production' +} + +const initialEnv = getInitialEnvironment() +const initialUrls = getUrlsForEnvironment(initialEnv) + const useApiUrlStore = create((set) => ({ - // Mettez la valeur initiale de isProd ici - apiUrl: import.meta.env.VITE_API_URL_SERVER, - isProd: true, - toggleApiUrl: () => { - set((state) => ({ - apiUrl: state.isProd - ? import.meta.env.VITE_API_URL_LOCAL - : import.meta.env.VITE_API_URL_SERVER, - isProd: !state.isProd, - workerUrl: state.isProd - ? import.meta.env.VITE_WORKER_URL_LOCAL - : import.meta.env.VITE_WORKER_URL_SERVER, - })) + environment: initialEnv, + apiUrl: initialUrls.apiUrl, + workerUrl: initialUrls.workerUrl, + privyAppId: initialUrls.privyAppId, + setEnvironment: (env: Environment) => { + // Save to cookie with 1 year expiration + setCookie(ENV_COOKIE_NAME, env, 365) + + const urls = getUrlsForEnvironment(env) + set({ + environment: env, + apiUrl: urls.apiUrl, + workerUrl: urls.workerUrl, + privyAppId: urls.privyAppId, + }) + }, + prepareEnvironmentChange: (env: Environment) => { + // This function will be called from the component where we have access to the Privy hook + useApiUrlStore.getState().setEnvironment(env) }, - workerUrl: import.meta.env.VITE_WORKER_URL_SERVER, })) export default useApiUrlStore diff --git a/src/Managing.WebApp/src/components/mollecules/NavBar/NavBar.tsx b/src/Managing.WebApp/src/components/mollecules/NavBar/NavBar.tsx index b730cbc..4b53968 100644 --- a/src/Managing.WebApp/src/components/mollecules/NavBar/NavBar.tsx +++ b/src/Managing.WebApp/src/components/mollecules/NavBar/NavBar.tsx @@ -1,13 +1,13 @@ -import { useIsFetching } from '@tanstack/react-query' -import { usePrivy } from '@privy-io/react-auth' -import type { ReactNode } from 'react' -import { useState } from 'react' -import { Link } from 'react-router-dom' +import {useIsFetching} from '@tanstack/react-query' +import {usePrivy} from '@privy-io/react-auth' +import type {ReactNode} from 'react' +import {useState} from 'react' +import {Link} from 'react-router-dom' -import { NavItem } from '..' +import {NavItem} from '..' import useApiUrlStore from '../../../app/store/apiStore' import Logo from '../../../assets/img/logo.png' -import { Loader } from '../../atoms' +import {Loader} from '../../atoms' const navigation = [ { href: '/desk', name: 'Desk' }, @@ -77,21 +77,31 @@ const PrivyWalletButton = () => { } export function SecondaryNavbar() { - const { toggleApiUrl, isProd } = useApiUrlStore() + const { environment, prepareEnvironmentChange } = useApiUrlStore() + const { logout } = usePrivy() + + const handleEnvironmentChange = async (newEnv: 'local' | 'sandbox' | 'production') => { + prepareEnvironmentChange(newEnv) + + if (logout) { + await logout() + } + window.location.reload() + } return (
- +
diff --git a/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx b/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx index d76d841..2da2038 100644 --- a/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx +++ b/src/Managing.WebApp/src/components/organism/Backtest/backtestModal.tsx @@ -5,7 +5,6 @@ import {type SubmitHandler, useForm} from 'react-hook-form' import useApiUrlStore from '../../../app/store/apiStore' import { AccountClient, - Backtest, BacktestClient, BotType, DataClient, @@ -80,9 +79,16 @@ const BacktestModal: React.FC = ({ return; } + // Process tickers sequentially for (const ticker of form.tickers) { const scenarioName = form.scenarioName; - await runBacktest(form, ticker, scenarioName, customMoneyManagement, 0); + try { + await runBacktest(form, ticker, scenarioName, customMoneyManagement, 0); + } catch (error) { + console.error(`Error running backtest for ${ticker}:`, error); + // Continue with next ticker even if one fails + continue; + } } } @@ -92,15 +98,15 @@ const BacktestModal: React.FC = ({ scenarioName: string, customMoneyManagement: MoneyManagement | undefined, loopCount: number - ) { + ): Promise { const t = new Toast(ticker + ' is running') // Use the name of the money management strategy if custom is not provided const moneyManagementName = customMoneyManagement ? undefined : selectedMoneyManagement console.log(customMoneyManagement) - backtestClient - .backtest_Run( + try { + const backtest = await backtestClient.backtest_Run( form.accountName, form.botType, ticker as Ticker, @@ -115,26 +121,26 @@ const BacktestModal: React.FC = ({ form.cooldownPeriod, // Use the cooldown period from the form form.maxLossStreak, // Add the max loss streak parameter customMoneyManagement - ) - .then((backtest: Backtest) => { - t.update('success', `${backtest.ticker} Backtest Succeeded`) - setBacktests((arr) => [...arr, backtest]) + ); + + t.update('success', `${backtest.ticker} Backtest Succeeded`) + setBacktests((arr) => [...arr, backtest]) - if (showLoopSlider && selectedLoopQuantity > loopCount) { - const nextCount = loopCount + 1 - const mm: MoneyManagement = { - leverage: backtest.optimizedMoneyManagement.leverage, - name: backtest.optimizedMoneyManagement.name + nextCount, - stopLoss: backtest.optimizedMoneyManagement.stopLoss, - takeProfit: backtest.optimizedMoneyManagement.takeProfit, - timeframe: backtest.optimizedMoneyManagement.timeframe, - } - runBacktest(form, ticker, scenarioName, mm, nextCount) + if (showLoopSlider && selectedLoopQuantity > loopCount) { + const nextCount = loopCount + 1 + const mm: MoneyManagement = { + leverage: backtest.optimizedMoneyManagement.leverage, + name: backtest.optimizedMoneyManagement.name + nextCount, + stopLoss: backtest.optimizedMoneyManagement.stopLoss, + takeProfit: backtest.optimizedMoneyManagement.takeProfit, + timeframe: backtest.optimizedMoneyManagement.timeframe, } - }) - .catch((err) => { - t.update('error', 'Error: ' + err) - }) + await runBacktest(form, ticker, scenarioName, mm, nextCount) + } + } catch (err) { + t.update('error', 'Error: ' + err) + throw err; // Re-throw the error to be caught by the caller + } } function setSelectedAccountEvent(e: React.ChangeEvent) { @@ -349,8 +355,9 @@ const BacktestModal: React.FC = ({ {...register('tickers')} > {tickers?.map((item) => ( - ))} diff --git a/src/Managing.WebApp/src/main.tsx b/src/Managing.WebApp/src/main.tsx index ec8e651..9eb75c5 100644 --- a/src/Managing.WebApp/src/main.tsx +++ b/src/Managing.WebApp/src/main.tsx @@ -1,18 +1,19 @@ import './styles/globals.css' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { PrivyProvider } from '@privy-io/react-auth' -import { WagmiProvider } from '@privy-io/wagmi' -import { createRoot } from 'react-dom/client' -import { BrowserRouter } from 'react-router-dom' +import {QueryClient, QueryClientProvider} from '@tanstack/react-query' +import {PrivyProvider} from '@privy-io/react-auth' +import {WagmiProvider} from '@privy-io/wagmi' +import {createRoot} from 'react-dom/client' +import {BrowserRouter} from 'react-router-dom' import App from './app' -import { privyWagmiConfig, supportedChains } from './config/privy' +import {privyWagmiConfig} from './config/privy' +import useApiUrlStore from './app/store/apiStore' import 'react-grid-layout/css/styles.css' import 'react-resizable/css/styles.css' import 'react-toastify/dist/ReactToastify.css' -import { ToastContainer } from 'react-toastify' +import {ToastContainer} from 'react-toastify' const element = document.getElementById('root') as HTMLElement @@ -35,10 +36,12 @@ const privyConfig = { }, } -root.render( - +function AppWithProviders() { + const { privyAppId } = useApiUrlStore() + + return ( @@ -58,5 +61,11 @@ root.render( + ) +} + +root.render( + + ) diff --git a/src/Managing.WebApp/src/pages/web3Page/web3.tsx b/src/Managing.WebApp/src/pages/web3Page/web3.tsx deleted file mode 100644 index fb60113..0000000 --- a/src/Managing.WebApp/src/pages/web3Page/web3.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import type { Contract } from 'ethers' -import { ethers } from 'ethers' -import { useRef, useState } from 'react' - -const Web3 = () => { - const moodInputRef = useRef() - const [mood, setMood] = useState() - //@ts-ignore - const provider = new ethers.providers.Web3Provider(window.ethereum, 'ropsten') - const contractAddress = '0x0335e801159Af04b3067bED0aeeaCC86Ece51e19' - const moodAbi = [ - { - constant: true, - inputs: [], - name: 'getMood', - outputs: [ - { - internalType: 'string', - name: '', - type: 'string', - }, - ], - signature: '0x9d0c1397', - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'string', - name: '_mood', - type: 'string', - }, - ], - name: 'setMood', - outputs: [], - signature: '0x5f3cbff5', - stateMutability: 'nonpayable', - type: 'function', - }, - ] - - let contract: Contract - let signer - - provider.send('eth_requestAccounts', []).then(() => { - provider.listAccounts().then(function (accounts) { - signer = provider.getSigner(accounts[0]) - contract = new ethers.Contract(contractAddress, moodAbi, signer) - }) - }) - - async function getMoodAbi() { - const getMoodPromise = contract.getMood() - const currentMood = await getMoodPromise - setMood(currentMood) - } - - async function setMoodAbi() { - //@ts-ignore - const setMoodPromise = contract.setMood(moodInputRef.current.value) - await setMoodPromise - } - - return ( -
-
-

Web3 Playground

-
-
-

dApp

- - - - -
-
-

{mood}

-
- -
-
-
-
-
-

NFT

-
-
-
-
- ) -} - -export default Web3 diff --git a/src/Managing.WebApp/src/smartcontracts/courses/mood.sol b/src/Managing.WebApp/src/smartcontracts/courses/mood.sol deleted file mode 100644 index 68f90b7..0000000 --- a/src/Managing.WebApp/src/smartcontracts/courses/mood.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.1; - -contract MoodDiary{ - string mood; - - //create a function that writes a mood to the smart contract - function setMood(string memory _mood) public{ - mood = _mood; - } - - //create a function the reads the mood from the smart contract - function getMood() public view returns(string memory){ - return mood; - } - } \ No newline at end of file diff --git a/src/Managing.WebApp/src/smartcontracts/courses/odaNFT.sol b/src/Managing.WebApp/src/smartcontracts/courses/odaNFT.sol deleted file mode 100644 index 378a154..0000000 --- a/src/Managing.WebApp/src/smartcontracts/courses/odaNFT.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Import the openzepplin contracts -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -// GameItem is ERC721 signifies that the contract we are creating imports ERC721 and follows ERC721 contract from openzeppelin -contract GameItem is ERC721 { - - constructor() ERC721("GameItem", "ITM") { - // mint an NFT to yourself - _mint(msg.sender, 1); - } -} \ No newline at end of file diff --git a/src/Managing.WebApp/src/smartcontracts/courses/odaToken.sol b/src/Managing.WebApp/src/smartcontracts/courses/odaToken.sol deleted file mode 100644 index 64743a8..0000000 --- a/src/Managing.WebApp/src/smartcontracts/courses/odaToken.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol"; - -contract OdaToken is ERC20 { - constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) { - _mint(msg.sender, 10 * 10 ** 18); - } -} \ No newline at end of file