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_modules/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<string, string>)
return cookies[name]
}
const useApiUrlStore = create<ApiStore>((set) => ({
// Mettez la valeur initiale de isProd ici
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,
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,
}))
},
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) => ({
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)
},
}))
export default useApiUrlStore

View File

@@ -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 (
<div className="md:flex items-center hidden space-x-3">
<GlobalLoader />
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text px-2">{isProd ? 'Server' : 'Debug'}</span>
<input
type="checkbox"
className="toggle"
checked={isProd}
onChange={toggleApiUrl}
/>
</label>
<select
className="select select-bordered select-sm"
value={environment}
onChange={(e) => handleEnvironmentChange(e.target.value as 'local' | 'sandbox' | 'production')}
>
<option value="local">Local</option>
<option value="sandbox">Sandbox</option>
<option value="production">Production</option>
</select>
</div>
<PrivyWalletButton />
</div>

View File

@@ -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<BacktestModalProps> = ({
return;
}
// Process tickers sequentially
for (const ticker of form.tickers) {
const scenarioName = form.scenarioName;
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,
customMoneyManagement: MoneyManagement | undefined,
loopCount: number
) {
): Promise<void> {
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,8 +121,8 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
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])
@@ -129,12 +135,12 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
takeProfit: backtest.optimizedMoneyManagement.takeProfit,
timeframe: backtest.optimizedMoneyManagement.timeframe,
}
runBacktest(form, ticker, scenarioName, mm, nextCount)
await runBacktest(form, ticker, scenarioName, mm, nextCount)
}
})
.catch((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>) {
@@ -349,8 +355,9 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
{...register('tickers')}
>
{tickers?.map((item) => (
<option key={item} value={item}>
{item}
<option key={item.ticker} value={item.ticker}>
<img src={item.imageUrl || ''} alt={item.ticker} className="w-4 h-4 mr-2" />
{item.ticker}
</option>
))}
</select>

View File

@@ -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(
<QueryClientProvider client={queryClient}>
function AppWithProviders() {
const { privyAppId } = useApiUrlStore()
return (
<PrivyProvider
appId={import.meta.env.VITE_PRIVY_APP_ID}
appId={privyAppId}
config={privyConfig}
>
<WagmiProvider config={privyWagmiConfig}>
@@ -58,5 +61,11 @@ root.render(
</BrowserRouter>
</WagmiProvider>
</PrivyProvider>
)
}
root.render(
<QueryClientProvider client={queryClient}>
<AppWithProviders />
</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);
}
}