Clean a bit and add prod env to front

This commit is contained in:
2025-05-11 17:23:02 +07:00
parent 549c4ae746
commit 65c4ec4957
15 changed files with 184 additions and 230 deletions

2
.gitignore vendored
View File

@@ -380,5 +380,3 @@ src/Managing.Infrastructure.Tests/PrivateKeys.cs
# Node.js Tools for Visual Studio # Node.js Tools for Visual Studio
node_modules/ node_modules/

View File

@@ -140,7 +140,9 @@ public class BotController : BaseController
Timeframe = request.Timeframe, Timeframe = request.Timeframe,
IsForWatchingOnly = request.IsForWatchOnly, IsForWatchingOnly = request.IsForWatchOnly,
BotTradingBalance = request.InitialTradingBalance, 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)); var result = await _mediator.Send(new StartBotCommand(config, request.Identifier, user));
@@ -622,4 +624,7 @@ public class StartBotRequest
/// The initial trading balance /// The initial trading balance
/// </summary> /// </summary>
public decimal InitialTradingBalance { get; set; } public decimal InitialTradingBalance { get; set; }
public int CooldownPeriod { get; set; }
public int MaxLossStreak { get; set; }
} }

View File

@@ -23,7 +23,7 @@ namespace Managing.Application.Abstractions.Services
bool isForWatchingOnly = false, bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle>? initialCandles = null, List<Candle>? initialCandles = null,
decimal cooldownPeriod = 1, int cooldownPeriod = 1,
int maxLossStreak = 0); int maxLossStreak = 0);
Task<Backtest> RunFlippingBotBacktest( Task<Backtest> RunFlippingBotBacktest(
@@ -39,7 +39,7 @@ namespace Managing.Application.Abstractions.Services
bool isForWatchingOnly = false, bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle>? initialCandles = null, List<Candle>? initialCandles = null,
decimal cooldownPeriod = 1, int cooldownPeriod = 1,
int maxLossStreak = 0); int maxLossStreak = 0);
bool DeleteBacktest(string id); bool DeleteBacktest(string id);
@@ -53,7 +53,7 @@ namespace Managing.Application.Abstractions.Services
List<Candle> candles, List<Candle> candles,
decimal balance, decimal balance,
User user = null, User user = null,
decimal cooldownPeriod = 1, int cooldownPeriod = 1,
int maxLossStreak = 0); int maxLossStreak = 0);
Task<Backtest> RunFlippingBotBacktest( Task<Backtest> RunFlippingBotBacktest(
@@ -64,7 +64,7 @@ namespace Managing.Application.Abstractions.Services
List<Candle> candles, List<Candle> candles,
decimal balance, decimal balance,
User user = null, User user = null,
decimal cooldownPeriod = 1, int cooldownPeriod = 1,
int maxLossStreak = 0); int maxLossStreak = 0);
// User-specific operations // User-specific operations

View File

@@ -66,7 +66,7 @@ namespace Managing.Application.Backtesting
bool isForWatchingOnly = false, bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle>? initialCandles = null, List<Candle>? initialCandles = null,
decimal cooldownPeriod = 1, int cooldownPeriod = 1,
int maxLossStreak = 0) int maxLossStreak = 0)
{ {
var config = new TradingBotConfig var config = new TradingBotConfig
@@ -121,7 +121,7 @@ namespace Managing.Application.Backtesting
bool isForWatchingOnly = false, bool isForWatchingOnly = false,
bool save = false, bool save = false,
List<Candle>? initialCandles = null, List<Candle>? initialCandles = null,
decimal cooldownPeriod = 1, int cooldownPeriod = 1,
int maxLossStreak = 0) int maxLossStreak = 0)
{ {
var config = new TradingBotConfig var config = new TradingBotConfig
@@ -172,7 +172,7 @@ namespace Managing.Application.Backtesting
List<Candle> candles, List<Candle> candles,
decimal balance, decimal balance,
User user = null, User user = null,
decimal cooldownPeriod = 1, int cooldownPeriod = 1,
int maxLossStreak = 0) int maxLossStreak = 0)
{ {
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker); var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);
@@ -214,7 +214,7 @@ namespace Managing.Application.Backtesting
List<Candle> candles, List<Candle> candles,
decimal balance, decimal balance,
User user = null, User user = null,
decimal cooldownPeriod = 1, int cooldownPeriod = 1,
int maxLossStreak = 0) int maxLossStreak = 0)
{ {
var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker); var ticker = MiscExtensions.ParseEnum<Ticker>(candles.FirstOrDefault().Ticker);

View File

@@ -1008,6 +1008,7 @@ public class TradingBot : Bot, ITradingBot
BotTradingBalance = data.BotTradingBalance, BotTradingBalance = data.BotTradingBalance,
BotType = data.BotType, BotType = data.BotType,
CooldownPeriod = data.CooldownPeriod, CooldownPeriod = data.CooldownPeriod,
MaxLossStreak = data.MaxLossStreak,
Name = data.Name Name = data.Name
}; };
@@ -1077,5 +1078,6 @@ public class TradingBotBackup
public MoneyManagement MoneyManagement { get; set; } public MoneyManagement MoneyManagement { get; set; }
public DateTime StartupTime { get; set; } public DateTime StartupTime { get; set; }
public decimal BotTradingBalance { get; set; } public decimal BotTradingBalance { get; set; }
public decimal CooldownPeriod { get; set; } public int CooldownPeriod { get; set; }
public int MaxLossStreak { get; set; }
} }

View File

@@ -15,7 +15,7 @@ public class TradingBotConfig
public bool FlipPosition { get; set; } public bool FlipPosition { get; set; }
public BotType BotType { get; set; } public BotType BotType { get; set; }
public decimal BotTradingBalance { get; set; } public decimal BotTradingBalance { get; set; }
public decimal CooldownPeriod { get; set; } = 1; public int CooldownPeriod { get; set; } = 1;
public int MaxLossStreak { get; set; } = 0; // 0 means no limit public int MaxLossStreak { get; set; } = 0;
public string Name { get; set; } public string Name { get; set; }
} }

View File

@@ -1,7 +1,16 @@
VITE_API_URL_LOCAL=http://localhost:5000 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_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 VITE_WORKER_URL_SERVER=https://dev-managing-worker.apps.managing.live
ALCHEMY_ID=Bao7OirVe4bmYiDbPh0l8cs5gYb5D4_9 ALCHEMY_ID=Bao7OirVe4bmYiDbPh0l8cs5gYb5D4_9
WALLET_CONNECT_PROJECT_ID=363bf09c10fec2293b21ee199b2ce8d5 WALLET_CONNECT_PROJECT_ID=363bf09c10fec2293b21ee199b2ce8d5
VITE_PRIVY_APP_ID=cm7u09v0u002zrkuf2yjjr58p VITE_PRIVY_APP_ID=cm7u09v0u002zrkuf2yjjr58p
VITE_PRIVY_APP_ID_LOCAL=cm7u09v0u002zrkuf2yjjr58p
VITE_PRIVY_APP_ID_SANDBOX=cm7u09v0u002zrkuf2yjjr58p
VITE_PRIVY_APP_ID_PRODUCTION=cm6kkz5ke00n5ffmpwdbr05mp

View File

@@ -1,28 +1,86 @@
import create from 'zustand' import create from 'zustand'
type ApiStore = { type Environment = 'local' | 'sandbox' | 'production'
isProd: boolean
apiUrl: string const ENV_COOKIE_NAME = 'app_environment'
workerUrl: string
toggleApiUrl: () => void // 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<string, string>)
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<ApiStore>((set) => ({ const useApiUrlStore = create<ApiStore>((set) => ({
// Mettez la valeur initiale de isProd ici environment: initialEnv,
apiUrl: import.meta.env.VITE_API_URL_SERVER, apiUrl: initialUrls.apiUrl,
isProd: true, workerUrl: initialUrls.workerUrl,
toggleApiUrl: () => { privyAppId: initialUrls.privyAppId,
set((state) => ({ setEnvironment: (env: Environment) => {
apiUrl: state.isProd // Save to cookie with 1 year expiration
? import.meta.env.VITE_API_URL_LOCAL setCookie(ENV_COOKIE_NAME, env, 365)
: import.meta.env.VITE_API_URL_SERVER,
isProd: !state.isProd, const urls = getUrlsForEnvironment(env)
workerUrl: state.isProd set({
? import.meta.env.VITE_WORKER_URL_LOCAL environment: env,
: import.meta.env.VITE_WORKER_URL_SERVER, 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 export default useApiUrlStore

View File

@@ -1,13 +1,13 @@
import { useIsFetching } from '@tanstack/react-query' import {useIsFetching} from '@tanstack/react-query'
import { usePrivy } from '@privy-io/react-auth' import {usePrivy} from '@privy-io/react-auth'
import type { ReactNode } from 'react' import type {ReactNode} from 'react'
import { useState } from 'react' import {useState} from 'react'
import { Link } from 'react-router-dom' import {Link} from 'react-router-dom'
import { NavItem } from '..' import {NavItem} from '..'
import useApiUrlStore from '../../../app/store/apiStore' import useApiUrlStore from '../../../app/store/apiStore'
import Logo from '../../../assets/img/logo.png' import Logo from '../../../assets/img/logo.png'
import { Loader } from '../../atoms' import {Loader} from '../../atoms'
const navigation = [ const navigation = [
{ href: '/desk', name: 'Desk' }, { href: '/desk', name: 'Desk' },
@@ -77,21 +77,31 @@ const PrivyWalletButton = () => {
} }
export function SecondaryNavbar() { 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 ( return (
<div className="md:flex items-center hidden space-x-3"> <div className="md:flex items-center hidden space-x-3">
<GlobalLoader /> <GlobalLoader />
<div className="form-control"> <div className="form-control">
<label className="label cursor-pointer"> <select
<span className="label-text px-2">{isProd ? 'Server' : 'Debug'}</span> className="select select-bordered select-sm"
<input value={environment}
type="checkbox" onChange={(e) => handleEnvironmentChange(e.target.value as 'local' | 'sandbox' | 'production')}
className="toggle" >
checked={isProd} <option value="local">Local</option>
onChange={toggleApiUrl} <option value="sandbox">Sandbox</option>
/> <option value="production">Production</option>
</label> </select>
</div> </div>
<PrivyWalletButton /> <PrivyWalletButton />
</div> </div>

View File

@@ -5,7 +5,6 @@ import {type SubmitHandler, useForm} from 'react-hook-form'
import useApiUrlStore from '../../../app/store/apiStore' import useApiUrlStore from '../../../app/store/apiStore'
import { import {
AccountClient, AccountClient,
Backtest,
BacktestClient, BacktestClient,
BotType, BotType,
DataClient, DataClient,
@@ -80,9 +79,16 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
return; return;
} }
// Process tickers sequentially
for (const ticker of form.tickers) { for (const ticker of form.tickers) {
const scenarioName = form.scenarioName; 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<BacktestModalProps> = ({
scenarioName: string, scenarioName: string,
customMoneyManagement: MoneyManagement | undefined, customMoneyManagement: MoneyManagement | undefined,
loopCount: number loopCount: number
) { ): Promise<void> {
const t = new Toast(ticker + ' is running') const t = new Toast(ticker + ' is running')
// Use the name of the money management strategy if custom is not provided // Use the name of the money management strategy if custom is not provided
const moneyManagementName = customMoneyManagement ? undefined : selectedMoneyManagement const moneyManagementName = customMoneyManagement ? undefined : selectedMoneyManagement
console.log(customMoneyManagement) console.log(customMoneyManagement)
backtestClient try {
.backtest_Run( const backtest = await backtestClient.backtest_Run(
form.accountName, form.accountName,
form.botType, form.botType,
ticker as Ticker, ticker as Ticker,
@@ -115,26 +121,26 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
form.cooldownPeriod, // Use the cooldown period from the form form.cooldownPeriod, // Use the cooldown period from the form
form.maxLossStreak, // Add the max loss streak parameter form.maxLossStreak, // Add the max loss streak parameter
customMoneyManagement customMoneyManagement
) );
.then((backtest: Backtest) => {
t.update('success', `${backtest.ticker} Backtest Succeeded`)
setBacktests((arr) => [...arr, backtest])
if (showLoopSlider && selectedLoopQuantity > loopCount) { t.update('success', `${backtest.ticker} Backtest Succeeded`)
const nextCount = loopCount + 1 setBacktests((arr) => [...arr, backtest])
const mm: MoneyManagement = {
leverage: backtest.optimizedMoneyManagement.leverage, if (showLoopSlider && selectedLoopQuantity > loopCount) {
name: backtest.optimizedMoneyManagement.name + nextCount, const nextCount = loopCount + 1
stopLoss: backtest.optimizedMoneyManagement.stopLoss, const mm: MoneyManagement = {
takeProfit: backtest.optimizedMoneyManagement.takeProfit, leverage: backtest.optimizedMoneyManagement.leverage,
timeframe: backtest.optimizedMoneyManagement.timeframe, name: backtest.optimizedMoneyManagement.name + nextCount,
} stopLoss: backtest.optimizedMoneyManagement.stopLoss,
runBacktest(form, ticker, scenarioName, mm, nextCount) takeProfit: backtest.optimizedMoneyManagement.takeProfit,
timeframe: backtest.optimizedMoneyManagement.timeframe,
} }
}) await runBacktest(form, ticker, scenarioName, mm, nextCount)
.catch((err) => { }
t.update('error', 'Error: ' + err) } catch (err) {
}) t.update('error', 'Error: ' + err)
throw err; // Re-throw the error to be caught by the caller
}
} }
function setSelectedAccountEvent(e: React.ChangeEvent<HTMLInputElement>) { function setSelectedAccountEvent(e: React.ChangeEvent<HTMLInputElement>) {
@@ -349,8 +355,9 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
{...register('tickers')} {...register('tickers')}
> >
{tickers?.map((item) => ( {tickers?.map((item) => (
<option key={item} value={item}> <option key={item.ticker} value={item.ticker}>
{item} <img src={item.imageUrl || ''} alt={item.ticker} className="w-4 h-4 mr-2" />
{item.ticker}
</option> </option>
))} ))}
</select> </select>

View File

@@ -1,18 +1,19 @@
import './styles/globals.css' import './styles/globals.css'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import {QueryClient, QueryClientProvider} from '@tanstack/react-query'
import { PrivyProvider } from '@privy-io/react-auth' import {PrivyProvider} from '@privy-io/react-auth'
import { WagmiProvider } from '@privy-io/wagmi' import {WagmiProvider} from '@privy-io/wagmi'
import { createRoot } from 'react-dom/client' import {createRoot} from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom' import {BrowserRouter} from 'react-router-dom'
import App from './app' 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-grid-layout/css/styles.css'
import 'react-resizable/css/styles.css' import 'react-resizable/css/styles.css'
import 'react-toastify/dist/ReactToastify.css' import 'react-toastify/dist/ReactToastify.css'
import { ToastContainer } from 'react-toastify' import {ToastContainer} from 'react-toastify'
const element = document.getElementById('root') as HTMLElement const element = document.getElementById('root') as HTMLElement
@@ -35,10 +36,12 @@ const privyConfig = {
}, },
} }
root.render( function AppWithProviders() {
<QueryClientProvider client={queryClient}> const { privyAppId } = useApiUrlStore()
return (
<PrivyProvider <PrivyProvider
appId={import.meta.env.VITE_PRIVY_APP_ID} appId={privyAppId}
config={privyConfig} config={privyConfig}
> >
<WagmiProvider config={privyWagmiConfig}> <WagmiProvider config={privyWagmiConfig}>
@@ -58,5 +61,11 @@ root.render(
</BrowserRouter> </BrowserRouter>
</WagmiProvider> </WagmiProvider>
</PrivyProvider> </PrivyProvider>
)
}
root.render(
<QueryClientProvider client={queryClient}>
<AppWithProviders />
</QueryClientProvider> </QueryClientProvider>
) )

View File

@@ -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 (
<div>
<div className="container mx-auto">
<p className="mb-6 text-2xl">Web3 Playground</p>
<div className="grid-rows-1 mt-3">
<div>
<p className="text-xl">dApp</p>
<input
id="mood"
//@ts-ignore
ref={moodInputRef}
type="text"
className="input border-secondary w-full max-w-xs border-2"
/>
<button className="btn bg-secondary" onClick={setMoodAbi}>
Set Mood
</button>
<div className="card bg-primary text-primary-content w-40">
<div className="card-body">
<h2 className="card-title justify-center">{mood}</h2>
<div className="card-actions justify-center">
<button className="btn" onClick={getMoodAbi}>
Get Mood
</button>
</div>
</div>
</div>
</div>
<div>
<p className="text-xl">NFT</p>
</div>
</div>
</div>
</div>
)
}
export default Web3

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}