Postgres (#30)
* Add postgres * Migrate users * Migrate geneticRequest * Try to fix Concurrent call * Fix asyncawait * Fix async and concurrent * Migrate backtests * Add cache for user by address * Fix backtest migration * Fix not open connection * Fix backtest command error * Fix concurrent * Fix all concurrency * Migrate TradingRepo * Fix scenarios * Migrate statistic repo * Save botbackup * Add settings et moneymanagement * Add bot postgres * fix a bit more backups * Fix bot model * Fix loading backup * Remove cache market for read positions * Add workers to postgre * Fix workers api * Reduce get Accounts for workers * Migrate synth to postgre * Fix backtest saved * Remove mongodb * botservice decorrelation * Fix tradingbot scope call * fix tradingbot * fix concurrent * Fix scope for genetics * Fix account over requesting * Fix bundle backtest worker * fix a lot of things * fix tab backtest * Remove optimized moneymanagement * Add light signal to not use User and too much property * Make money management lighter * insert indicators to awaitable * Migrate add strategies to await * Refactor scenario and indicator retrieval to use asynchronous methods throughout the application * add more async await * Add services * Fix and clean * Fix bot a bit * Fix bot and add message for cooldown * Remove fees * Add script to deploy db * Update dfeeploy script * fix script * Add idempotent script and backup * finish script migration * Fix did user and agent name on start bot
This commit is contained in:
@@ -1,22 +1,32 @@
|
||||
import { StatusOfflineIcon } from '@heroicons/react/solid'
|
||||
import type { SubmitHandler } from 'react-hook-form'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { usePrivy, useSignMessage } from '@privy-io/react-auth'
|
||||
import {StatusOfflineIcon} from '@heroicons/react/solid'
|
||||
import type {SubmitHandler} from 'react-hook-form'
|
||||
import {useForm} from 'react-hook-form'
|
||||
import {usePrivy, useSignMessage} from '@privy-io/react-auth'
|
||||
import {useEffect} from 'react'
|
||||
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import { UserClient } from '../../../generated/ManagingApi'
|
||||
import type { ILoginFormInput } from '../../../global/type'
|
||||
import {UserClient} from '../../../generated/ManagingApi'
|
||||
import type {ILoginFormInput} from '../../../global/type'
|
||||
import useCookie from '../../../hooks/useCookie'
|
||||
import { SecondaryNavbar } from '../NavBar/NavBar'
|
||||
import {SecondaryNavbar} from '../NavBar/NavBar'
|
||||
import Toast from '../Toast/Toast'
|
||||
|
||||
const LogIn = () => {
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const { register, handleSubmit } = useForm<ILoginFormInput>()
|
||||
const { register, handleSubmit, setValue } = useForm<ILoginFormInput>()
|
||||
const { user, logout, ready, authenticated } = usePrivy()
|
||||
const { signMessage } = useSignMessage()
|
||||
const { setCookie } = useCookie()
|
||||
|
||||
// Prefill the name field with the Privy DID when user is available
|
||||
useEffect(() => {
|
||||
if (user?.id) {
|
||||
console.log(user)
|
||||
|
||||
setValue('name', user.id)
|
||||
}
|
||||
}, [user?.id, setValue])
|
||||
|
||||
const onSubmit: SubmitHandler<ILoginFormInput> = async (form) => {
|
||||
if (!authenticated || !user || !user.wallet?.address) {
|
||||
const t = new Toast('Error: Not authenticated')
|
||||
|
||||
@@ -92,7 +92,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
|
||||
) : strategyData ? (
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<p className="font-bold">Strategy: {strategyData.strategyName}</p>
|
||||
<p className="font-bold">Strategy: {strategyData.name}</p>
|
||||
<p>Win Rate: {strategyData.winRate?.toFixed(2)}%</p>
|
||||
<p>PnL: {strategyData.pnL?.toFixed(2)} $</p>
|
||||
<p>Wins: {strategyData.wins} / Losses: {strategyData.losses}</p>
|
||||
@@ -122,10 +122,10 @@ const TradesModal: React.FC<TradesModalProps> = ({
|
||||
{position.originDirection}
|
||||
</td>
|
||||
<td>{position.status}</td>
|
||||
<td>{position.open.price.toFixed(2)}</td>
|
||||
<td>{position.open.quantity.toFixed(4)}</td>
|
||||
<td className={position.profitAndLoss?.realized && position.profitAndLoss.realized > 0 ? 'text-success' : 'text-error'}>
|
||||
{position.profitAndLoss?.realized?.toFixed(2) || '0.00'} $
|
||||
<td>{position.Open.price.toFixed(2)}</td>
|
||||
<td>{position.Open.quantity.toFixed(4)}</td>
|
||||
<td className={position.ProfitAndLoss?.realized && position.ProfitAndLoss.realized > 0 ? 'text-success' : 'text-error'}>
|
||||
{position.ProfitAndLoss?.realized?.toFixed(2) || '0.00'} $
|
||||
</td>
|
||||
<td>
|
||||
{position.status !== 'Finished' && (
|
||||
|
||||
@@ -17,8 +17,8 @@ import {
|
||||
Timeframe,
|
||||
TradingBotConfigRequest,
|
||||
} from '../../../generated/ManagingApi'
|
||||
import type {BacktestModalProps, IBacktestsFormInput,} from '../../../global/type'
|
||||
import {Loader, Slider} from '../../atoms'
|
||||
import type {BacktestModalProps, IBacktestsFormInput,} from '../../../global/type.tsx'
|
||||
import {Loader} from '../../atoms'
|
||||
import {Modal, Toast} from '../../mollecules'
|
||||
import FormInput from '../../mollecules/FormInput/FormInput'
|
||||
import CustomMoneyManagement from '../CustomMoneyManagement/CustomMoneyManagement'
|
||||
@@ -169,7 +169,6 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
loopbackPeriod: customScenario.loopbackPeriod
|
||||
} : undefined,
|
||||
timeframe: form.timeframe,
|
||||
botType: form.botType,
|
||||
isForWatchingOnly: false, // Always false for backtests
|
||||
cooldownPeriod: form.cooldownPeriod || 1,
|
||||
maxLossStreak: form.maxLossStreak || 0,
|
||||
@@ -181,7 +180,8 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
useSynthApi: form.useSynthApi ?? false,
|
||||
useForPositionSizing: form.useForPositionSizing ?? true,
|
||||
useForSignalFiltering: form.useForSignalFiltering ?? true,
|
||||
useForDynamicStopLoss: form.useForDynamicStopLoss ?? true
|
||||
useForDynamicStopLoss: form.useForDynamicStopLoss ?? true,
|
||||
flipPosition: false
|
||||
};
|
||||
|
||||
// Create the RunBacktestRequest
|
||||
@@ -189,11 +189,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
config: tradingBotConfigRequest, // Use the request object
|
||||
startDate: new Date(form.startDate),
|
||||
endDate: new Date(form.endDate),
|
||||
balance: form.balance,
|
||||
watchOnly: false,
|
||||
save: form.save || false,
|
||||
moneyManagementName: customMoneyManagement ? undefined : selectedMoneyManagement,
|
||||
moneyManagement: customMoneyManagement
|
||||
save: form.save || false
|
||||
};
|
||||
|
||||
const backtest = await backtestClient.backtest_Run(request);
|
||||
@@ -578,29 +574,6 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
</FormInput>
|
||||
</div>
|
||||
|
||||
{/* Loop Slider (if enabled) */}
|
||||
{showLoopSlider && (
|
||||
<FormInput
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
Loop
|
||||
<div className="tooltip tooltip-top" data-tip="Number of optimization loops to run for money management. Each loop uses the optimized parameters from the previous iteration">
|
||||
<span className="badge badge-info badge-xs">i</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
htmlFor="loop"
|
||||
>
|
||||
<Slider
|
||||
id="loopSlider"
|
||||
min="1"
|
||||
max="10"
|
||||
value={selectedLoopQuantity.toString()}
|
||||
onChange={(e) => setLoopQuantity(Number(e.target.value))}
|
||||
></Slider>
|
||||
</FormInput>
|
||||
)}
|
||||
|
||||
{/* Max Loss Streak & Max Position Time */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<FormInput
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
DataClient,
|
||||
GetCandlesWithIndicatorsRequest,
|
||||
IndicatorType,
|
||||
Position,
|
||||
SignalType
|
||||
} from '../../../generated/ManagingApi'
|
||||
import {CardPosition, CardText} from '../../mollecules'
|
||||
@@ -92,18 +93,18 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
const config = currentBacktest.config;
|
||||
|
||||
// Helper function to calculate position open time in hours
|
||||
const calculateOpenTimeInHours = (position: any) => {
|
||||
const openDate = new Date(position.open.date);
|
||||
const calculateOpenTimeInHours = (position: Position) => {
|
||||
const openDate = new Date(position.Open.date);
|
||||
let closeDate: Date | null = null;
|
||||
|
||||
// Determine close date based on realized P&L (matching backend logic)
|
||||
if (position.profitAndLoss?.realized != null) {
|
||||
if (position.profitAndLoss.realized > 0) {
|
||||
if (position.ProfitAndLoss?.realized != null) {
|
||||
if (position.ProfitAndLoss.realized > 0) {
|
||||
// Profitable close = Take Profit
|
||||
closeDate = new Date(position.takeProfit1.date);
|
||||
closeDate = new Date(position.TakeProfit1.date);
|
||||
} else {
|
||||
// Loss or breakeven close = Stop Loss
|
||||
closeDate = new Date(position.stopLoss.date);
|
||||
closeDate = new Date(position.StopLoss.date);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +118,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
// Calculate average open time for winning positions
|
||||
const getAverageOpenTimeWinning = () => {
|
||||
const winningPositions = positions.filter((p) => {
|
||||
const realized = p.profitAndLoss?.realized ?? 0;
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0;
|
||||
return realized > 0;
|
||||
});
|
||||
|
||||
@@ -134,7 +135,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
// Calculate average open time for losing positions
|
||||
const getAverageOpenTimeLosing = () => {
|
||||
const losingPositions = positions.filter((p) => {
|
||||
const realized = p.profitAndLoss?.realized ?? 0;
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0;
|
||||
return realized <= 0;
|
||||
});
|
||||
|
||||
@@ -151,7 +152,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
// Calculate maximum open time for winning positions
|
||||
const getMaxOpenTimeWinning = () => {
|
||||
const winningPositions = positions.filter((p) => {
|
||||
const realized = p.profitAndLoss?.realized ?? 0;
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0;
|
||||
return realized > 0;
|
||||
});
|
||||
|
||||
@@ -183,19 +184,19 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
|
||||
positions.forEach((position) => {
|
||||
// Calculate volume for open trade
|
||||
const openLeverage = position.open.leverage || 1;
|
||||
const openVolume = position.open.quantity * position.open.price * openLeverage;
|
||||
const openLeverage = position.Open.leverage || 1;
|
||||
const openVolume = position.Open.quantity * position.Open.price * openLeverage;
|
||||
totalVolume += openVolume;
|
||||
|
||||
// Calculate volume for close trade (stopLoss or takeProfit based on realized P&L)
|
||||
if (position.profitAndLoss?.realized != null) {
|
||||
if (position.ProfitAndLoss?.realized != null) {
|
||||
let closeTrade;
|
||||
if (position.profitAndLoss.realized > 0) {
|
||||
if (position.ProfitAndLoss.realized > 0) {
|
||||
// Profitable close = Take Profit
|
||||
closeTrade = position.takeProfit1;
|
||||
closeTrade = position.TakeProfit1;
|
||||
} else {
|
||||
// Loss or breakeven close = Stop Loss
|
||||
closeTrade = position.stopLoss;
|
||||
closeTrade = position.StopLoss;
|
||||
}
|
||||
|
||||
if (closeTrade) {
|
||||
@@ -226,8 +227,8 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
const candleTimeframeMs = new Date(candles[1].date).getTime() - new Date(candles[0].date).getTime();
|
||||
|
||||
const sortedPositions = [...positions].sort((a, b) => {
|
||||
const dateA = new Date(a.open.date).getTime();
|
||||
const dateB = new Date(b.open.date).getTime();
|
||||
const dateA = new Date(a.Open.date).getTime();
|
||||
const dateB = new Date(b.Open.date).getTime();
|
||||
return dateA - dateB;
|
||||
});
|
||||
|
||||
@@ -237,23 +238,23 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
const currentPosition = sortedPositions[i];
|
||||
const nextPosition = sortedPositions[i + 1];
|
||||
|
||||
const currentRealized = currentPosition.profitAndLoss?.realized ?? 0;
|
||||
const nextRealized = nextPosition.profitAndLoss?.realized ?? 0;
|
||||
const currentRealized = currentPosition.ProfitAndLoss?.realized ?? 0;
|
||||
const nextRealized = nextPosition.ProfitAndLoss?.realized ?? 0;
|
||||
|
||||
// Check if current position is winning and next position is losing
|
||||
if (currentRealized > 0 && nextRealized <= 0) {
|
||||
// Calculate the close time of the current (winning) position
|
||||
let currentCloseDate: Date | null = null;
|
||||
if (currentPosition.profitAndLoss?.realized != null) {
|
||||
if (currentPosition.profitAndLoss.realized > 0) {
|
||||
currentCloseDate = new Date(currentPosition.takeProfit1.date);
|
||||
if (currentPosition.ProfitAndLoss?.realized != null) {
|
||||
if (currentPosition.ProfitAndLoss.realized > 0) {
|
||||
currentCloseDate = new Date(currentPosition.TakeProfit1.date);
|
||||
} else {
|
||||
currentCloseDate = new Date(currentPosition.stopLoss.date);
|
||||
currentCloseDate = new Date(currentPosition.StopLoss.date);
|
||||
}
|
||||
}
|
||||
|
||||
if (currentCloseDate) {
|
||||
const nextOpenDate = new Date(nextPosition.open.date);
|
||||
const nextOpenDate = new Date(nextPosition.Open.date);
|
||||
const gapInMs = nextOpenDate.getTime() - currentCloseDate.getTime();
|
||||
|
||||
if (gapInMs >= 0) { // Only consider positive gaps
|
||||
@@ -298,7 +299,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
if (positions.length === 0) return "0.00";
|
||||
|
||||
// Get all trade dates and sort them
|
||||
const tradeDates = positions.map(position => new Date(position.open.date)).sort((a, b) => a.getTime() - b.getTime());
|
||||
const tradeDates = positions.map(position => new Date(position.Open.date)).sort((a, b) => a.getTime() - b.getTime());
|
||||
|
||||
if (tradeDates.length < 2) return positions.length.toString();
|
||||
|
||||
@@ -327,14 +328,14 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
<CardPosition
|
||||
positivePosition={true}
|
||||
positions={positions.filter((p) => {
|
||||
const realized = p.profitAndLoss?.realized ?? 0
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0
|
||||
return realized > 0 ? p : null
|
||||
})}
|
||||
></CardPosition>
|
||||
<CardPosition
|
||||
positivePosition={false}
|
||||
positions={positions.filter((p) => {
|
||||
const realized = p.profitAndLoss?.realized ?? 0
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0
|
||||
return realized <= 0 ? p : null
|
||||
})}
|
||||
></CardPosition>
|
||||
@@ -371,12 +372,6 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
|
||||
(config.moneyManagement?.takeProfit * 100).toFixed(2) + "%" + " Lev.: x" + config.moneyManagement?.leverage
|
||||
}
|
||||
></CardText>
|
||||
<CardText
|
||||
title="Optimized Money Management"
|
||||
content={
|
||||
"SL: " + currentBacktest.optimizedMoneyManagement?.stopLoss.toFixed(2) + "% TP: " + currentBacktest.optimizedMoneyManagement?.takeProfit.toFixed(2) + "%"
|
||||
}
|
||||
></CardText>
|
||||
<CardText
|
||||
title="Avg Open Time (Winning)"
|
||||
content={getAverageOpenTimeWinning() + " hours"}
|
||||
|
||||
@@ -19,8 +19,8 @@ import type {
|
||||
IndicatorsResultBase,
|
||||
IndicatorType,
|
||||
KeyValuePairOfDateTimeAndDecimal,
|
||||
LightSignal,
|
||||
Position,
|
||||
Signal,
|
||||
} from '../../../../generated/ManagingApi'
|
||||
import {PositionStatus, TradeDirection,} from '../../../../generated/ManagingApi'
|
||||
import useTheme from '../../../../hooks/useTheme'
|
||||
@@ -43,7 +43,7 @@ import useTheme from '../../../../hooks/useTheme'
|
||||
type ITradeChartProps = {
|
||||
candles: Candle[]
|
||||
positions: Position[]
|
||||
signals: Signal[]
|
||||
signals: LightSignal[]
|
||||
walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null
|
||||
indicatorsValues?: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; } | null;
|
||||
stream?: Candle | null
|
||||
@@ -66,7 +66,6 @@ const TradeChart = ({
|
||||
const chart = useRef<IChartApi>()
|
||||
const {themeProperty} = useTheme()
|
||||
const theme = themeProperty()
|
||||
console.log(theme)
|
||||
const series1 = useRef<ISeriesApi<'Candlestick'>>()
|
||||
const [timeDiff, setTimeDiff] = useState<number>(0)
|
||||
const [candleCount, setCandleCount] = useState<number>(candles.length)
|
||||
@@ -220,7 +219,7 @@ const TradeChart = ({
|
||||
const negativeColor = theme.error
|
||||
const positiveColor = theme.success
|
||||
const status = position.status
|
||||
const realized = position.profitAndLoss?.realized ?? 0
|
||||
const realized = position.ProfitAndLoss?.realized ?? 0
|
||||
|
||||
if (status != undefined) {
|
||||
if (
|
||||
@@ -376,20 +375,19 @@ const TradeChart = ({
|
||||
getPositionColor(p),
|
||||
p.originDirection,
|
||||
p.date,
|
||||
p.open.price.toString()
|
||||
p.Open?.price?.toString()
|
||||
)
|
||||
)
|
||||
|
||||
markers.push(...positionMarkers)
|
||||
|
||||
const lastPositionOpen = positions[positions.length - 1]
|
||||
|
||||
if (lastPositionOpen) {
|
||||
series1.current.createPriceLine(
|
||||
buildLine(theme.error, lastPositionOpen.stopLoss.price, 'SL')
|
||||
buildLine(theme.error, lastPositionOpen.StopLoss?.price, 'SL')
|
||||
)
|
||||
series1.current.createPriceLine(
|
||||
buildLine(theme.success, lastPositionOpen.takeProfit1.price, 'TP')
|
||||
buildLine(theme.success, lastPositionOpen.TakeProfit1?.price, 'TP')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2659,14 +2659,14 @@ export class SettingsClient extends AuthorizedApiBase {
|
||||
this.baseUrl = baseUrl ?? "http://localhost:5000";
|
||||
}
|
||||
|
||||
settings_SetupSettings(): Promise<FileResponse> {
|
||||
settings_SetupSettings(): Promise<boolean> {
|
||||
let url_ = this.baseUrl + "/Settings";
|
||||
url_ = url_.replace(/[?&]$/, "");
|
||||
|
||||
let options_: RequestInit = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/octet-stream"
|
||||
"Accept": "application/json"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2677,26 +2677,21 @@ export class SettingsClient extends AuthorizedApiBase {
|
||||
});
|
||||
}
|
||||
|
||||
protected processSettings_SetupSettings(response: Response): Promise<FileResponse> {
|
||||
protected processSettings_SetupSettings(response: Response): Promise<boolean> {
|
||||
const status = response.status;
|
||||
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
|
||||
if (status === 200 || status === 206) {
|
||||
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
|
||||
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
|
||||
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
|
||||
if (fileName) {
|
||||
fileName = decodeURIComponent(fileName);
|
||||
} else {
|
||||
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
|
||||
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
|
||||
}
|
||||
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
|
||||
if (status === 200) {
|
||||
return response.text().then((_responseText) => {
|
||||
let result200: any = null;
|
||||
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as boolean;
|
||||
return result200;
|
||||
});
|
||||
} else if (status !== 200 && status !== 204) {
|
||||
return response.text().then((_responseText) => {
|
||||
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
|
||||
});
|
||||
}
|
||||
return Promise.resolve<FileResponse>(null as any);
|
||||
return Promise.resolve<boolean>(null as any);
|
||||
}
|
||||
|
||||
settings_ResetSettings(): Promise<boolean> {
|
||||
@@ -3658,14 +3653,13 @@ export interface Backtest {
|
||||
hodlPercentage: number;
|
||||
config: TradingBotConfig;
|
||||
positions: Position[];
|
||||
signals: Signal[];
|
||||
signals: LightSignal[];
|
||||
candles: Candle[];
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
statistics: PerformanceMetrics;
|
||||
fees: number;
|
||||
walletBalances: KeyValuePairOfDateTimeAndDecimal[];
|
||||
optimizedMoneyManagement: MoneyManagement;
|
||||
user: User;
|
||||
indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
|
||||
score: number;
|
||||
@@ -3793,11 +3787,11 @@ export interface Position {
|
||||
originDirection: TradeDirection;
|
||||
ticker: Ticker;
|
||||
moneyManagement: MoneyManagement;
|
||||
open: Trade;
|
||||
stopLoss: Trade;
|
||||
takeProfit1: Trade;
|
||||
takeProfit2?: Trade | null;
|
||||
profitAndLoss?: ProfitAndLoss | null;
|
||||
Open: Trade;
|
||||
StopLoss: Trade;
|
||||
TakeProfit1: Trade;
|
||||
TakeProfit2?: Trade | null;
|
||||
ProfitAndLoss?: ProfitAndLoss | null;
|
||||
status: PositionStatus;
|
||||
signalIdentifier?: string | null;
|
||||
identifier: string;
|
||||
@@ -3812,7 +3806,7 @@ export enum TradeDirection {
|
||||
}
|
||||
|
||||
export interface Trade {
|
||||
fee?: number;
|
||||
fee: number;
|
||||
date: Date;
|
||||
direction: TradeDirection;
|
||||
status: TradeStatus;
|
||||
@@ -3820,9 +3814,9 @@ export interface Trade {
|
||||
ticker: Ticker;
|
||||
quantity: number;
|
||||
price: number;
|
||||
leverage?: number;
|
||||
leverage: number;
|
||||
exchangeOrderId: string;
|
||||
message?: string | null;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export enum TradeStatus {
|
||||
@@ -3876,7 +3870,7 @@ export enum PositionInitiator {
|
||||
export interface ValueObject {
|
||||
}
|
||||
|
||||
export interface Signal extends ValueObject {
|
||||
export interface LightSignal extends ValueObject {
|
||||
status: SignalStatus;
|
||||
direction: TradeDirection;
|
||||
confidence: Confidence;
|
||||
@@ -3888,7 +3882,6 @@ export interface Signal extends ValueObject {
|
||||
exchange: TradingExchanges;
|
||||
indicatorType: IndicatorType;
|
||||
signalType: SignalType;
|
||||
user?: User | null;
|
||||
indicatorName: string;
|
||||
}
|
||||
|
||||
@@ -3912,15 +3905,10 @@ export interface Candle {
|
||||
date: Date;
|
||||
open: number;
|
||||
close: number;
|
||||
volume?: number;
|
||||
high: number;
|
||||
low: number;
|
||||
baseVolume?: number;
|
||||
quoteVolume?: number;
|
||||
tradeCount?: number;
|
||||
takerBuyBaseVolume?: number;
|
||||
takerBuyQuoteVolume?: number;
|
||||
timeframe: Timeframe;
|
||||
volume?: number;
|
||||
}
|
||||
|
||||
export interface PerformanceMetrics {
|
||||
@@ -4127,7 +4115,7 @@ export interface BundleBacktestRequest {
|
||||
status: BundleBacktestRequestStatus;
|
||||
name: string;
|
||||
backtestRequestsJson: string;
|
||||
results?: Backtest[] | null;
|
||||
results?: string[] | null;
|
||||
totalBacktests: number;
|
||||
completedBacktests: number;
|
||||
failedBacktests: number;
|
||||
@@ -4251,7 +4239,7 @@ export enum BotType {
|
||||
|
||||
export interface TradingBotResponse {
|
||||
status: string;
|
||||
signals: Signal[];
|
||||
signals: LightSignal[];
|
||||
positions: Position[];
|
||||
candles: Candle[];
|
||||
winRate: number;
|
||||
@@ -4299,11 +4287,11 @@ export interface Spotlight {
|
||||
|
||||
export interface TickerSignal {
|
||||
ticker: Ticker;
|
||||
fiveMinutes: Signal[];
|
||||
fifteenMinutes: Signal[];
|
||||
oneHour: Signal[];
|
||||
fourHour: Signal[];
|
||||
oneDay: Signal[];
|
||||
fiveMinutes: LightSignal[];
|
||||
fifteenMinutes: LightSignal[];
|
||||
oneHour: LightSignal[];
|
||||
fourHour: LightSignal[];
|
||||
oneDay: LightSignal[];
|
||||
}
|
||||
|
||||
export interface CandlesWithIndicatorsResponse {
|
||||
|
||||
@@ -225,14 +225,13 @@ export interface Backtest {
|
||||
hodlPercentage: number;
|
||||
config: TradingBotConfig;
|
||||
positions: Position[];
|
||||
signals: Signal[];
|
||||
signals: LightSignal[];
|
||||
candles: Candle[];
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
statistics: PerformanceMetrics;
|
||||
fees: number;
|
||||
walletBalances: KeyValuePairOfDateTimeAndDecimal[];
|
||||
optimizedMoneyManagement: MoneyManagement;
|
||||
user: User;
|
||||
indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
|
||||
score: number;
|
||||
@@ -360,11 +359,11 @@ export interface Position {
|
||||
originDirection: TradeDirection;
|
||||
ticker: Ticker;
|
||||
moneyManagement: MoneyManagement;
|
||||
open: Trade;
|
||||
stopLoss: Trade;
|
||||
takeProfit1: Trade;
|
||||
takeProfit2?: Trade | null;
|
||||
profitAndLoss?: ProfitAndLoss | null;
|
||||
Open: Trade;
|
||||
StopLoss: Trade;
|
||||
TakeProfit1: Trade;
|
||||
TakeProfit2?: Trade | null;
|
||||
ProfitAndLoss?: ProfitAndLoss | null;
|
||||
status: PositionStatus;
|
||||
signalIdentifier?: string | null;
|
||||
identifier: string;
|
||||
@@ -379,7 +378,7 @@ export enum TradeDirection {
|
||||
}
|
||||
|
||||
export interface Trade {
|
||||
fee?: number;
|
||||
fee: number;
|
||||
date: Date;
|
||||
direction: TradeDirection;
|
||||
status: TradeStatus;
|
||||
@@ -387,9 +386,9 @@ export interface Trade {
|
||||
ticker: Ticker;
|
||||
quantity: number;
|
||||
price: number;
|
||||
leverage?: number;
|
||||
leverage: number;
|
||||
exchangeOrderId: string;
|
||||
message?: string | null;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export enum TradeStatus {
|
||||
@@ -443,7 +442,7 @@ export enum PositionInitiator {
|
||||
export interface ValueObject {
|
||||
}
|
||||
|
||||
export interface Signal extends ValueObject {
|
||||
export interface LightSignal extends ValueObject {
|
||||
status: SignalStatus;
|
||||
direction: TradeDirection;
|
||||
confidence: Confidence;
|
||||
@@ -455,7 +454,6 @@ export interface Signal extends ValueObject {
|
||||
exchange: TradingExchanges;
|
||||
indicatorType: IndicatorType;
|
||||
signalType: SignalType;
|
||||
user?: User | null;
|
||||
indicatorName: string;
|
||||
}
|
||||
|
||||
@@ -479,15 +477,10 @@ export interface Candle {
|
||||
date: Date;
|
||||
open: number;
|
||||
close: number;
|
||||
volume?: number;
|
||||
high: number;
|
||||
low: number;
|
||||
baseVolume?: number;
|
||||
quoteVolume?: number;
|
||||
tradeCount?: number;
|
||||
takerBuyBaseVolume?: number;
|
||||
takerBuyQuoteVolume?: number;
|
||||
timeframe: Timeframe;
|
||||
volume?: number;
|
||||
}
|
||||
|
||||
export interface PerformanceMetrics {
|
||||
@@ -694,7 +687,7 @@ export interface BundleBacktestRequest {
|
||||
status: BundleBacktestRequestStatus;
|
||||
name: string;
|
||||
backtestRequestsJson: string;
|
||||
results?: Backtest[] | null;
|
||||
results?: string[] | null;
|
||||
totalBacktests: number;
|
||||
completedBacktests: number;
|
||||
failedBacktests: number;
|
||||
@@ -818,7 +811,7 @@ export enum BotType {
|
||||
|
||||
export interface TradingBotResponse {
|
||||
status: string;
|
||||
signals: Signal[];
|
||||
signals: LightSignal[];
|
||||
positions: Position[];
|
||||
candles: Candle[];
|
||||
winRate: number;
|
||||
@@ -866,11 +859,11 @@ export interface Spotlight {
|
||||
|
||||
export interface TickerSignal {
|
||||
ticker: Ticker;
|
||||
fiveMinutes: Signal[];
|
||||
fifteenMinutes: Signal[];
|
||||
oneHour: Signal[];
|
||||
fourHour: Signal[];
|
||||
oneDay: Signal[];
|
||||
fiveMinutes: LightSignal[];
|
||||
fifteenMinutes: LightSignal[];
|
||||
oneHour: LightSignal[];
|
||||
fourHour: LightSignal[];
|
||||
oneDay: LightSignal[];
|
||||
}
|
||||
|
||||
export interface CandlesWithIndicatorsResponse {
|
||||
|
||||
@@ -3,11 +3,9 @@ import React, {useState} from 'react'
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
import {Tabs} from '../../components/mollecules'
|
||||
import BacktestScanner from './backtestScanner'
|
||||
import BacktestUpload from './backtestUpload'
|
||||
import BacktestGenetic from './backtestGenetic'
|
||||
import BacktestGeneticBundle from './backtestGeneticBundle'
|
||||
import BacktestBundleForm from './backtestBundleForm';
|
||||
import type {TabsType} from '../../global/type.tsx'
|
||||
import BacktestBundleForm from './backtestBundleForm'
|
||||
|
||||
// Tabs Array
|
||||
const tabs: TabsType = [
|
||||
@@ -21,19 +19,9 @@ const tabs: TabsType = [
|
||||
index: 1,
|
||||
label: 'Scanner',
|
||||
},
|
||||
{
|
||||
Component: BacktestUpload,
|
||||
index: 2,
|
||||
label: 'Upload',
|
||||
},
|
||||
{
|
||||
Component: BacktestGenetic,
|
||||
index: 3,
|
||||
label: 'Genetic',
|
||||
},
|
||||
{
|
||||
Component: BacktestGeneticBundle,
|
||||
index: 4,
|
||||
index: 2,
|
||||
label: 'GeneticBundle',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {useState} from 'react';
|
||||
import {BacktestClient} from '../../generated/ManagingApi';
|
||||
import {AccountClient, BacktestClient} from '../../generated/ManagingApi';
|
||||
import type {
|
||||
MoneyManagementRequest,
|
||||
RunBacktestRequest,
|
||||
@@ -12,6 +12,7 @@ import {useCustomScenario} from '../../app/store/customScenario';
|
||||
import useApiUrlStore from '../../app/store/apiStore';
|
||||
import Toast from '../../components/mollecules/Toast/Toast';
|
||||
import BundleRequestsTable from './bundleRequestsTable';
|
||||
import {useQuery} from '@tanstack/react-query';
|
||||
|
||||
// Placeholder types (replace with your actual types)
|
||||
type Indicator = { name: string; params?: Record<string, any> };
|
||||
@@ -52,6 +53,22 @@ const timeframeMap: Record<string, Timeframe> = {
|
||||
|
||||
const BacktestBundleForm: React.FC = () => {
|
||||
const {apiUrl} = useApiUrlStore()
|
||||
|
||||
// API clients
|
||||
const accountClient = new AccountClient({}, apiUrl);
|
||||
|
||||
// Data fetching
|
||||
const { data: accounts, isSuccess } = useQuery({
|
||||
queryFn: async () => {
|
||||
const fetchedAccounts = await accountClient.account_GetAccounts();
|
||||
if (fetchedAccounts.length > 0 && accountName === 'default') {
|
||||
setAccountName(fetchedAccounts[0].name);
|
||||
}
|
||||
return fetchedAccounts;
|
||||
},
|
||||
queryKey: ['accounts'],
|
||||
});
|
||||
|
||||
// Form state
|
||||
const [strategyName, setStrategyName] = useState('');
|
||||
const [loopback, setLoopback] = useState(14);
|
||||
@@ -71,6 +88,7 @@ const BacktestBundleForm: React.FC = () => {
|
||||
const [flipOnlyInProfit, setFlipOnlyInProfit] = useState(false);
|
||||
const [closeEarly, setCloseEarly] = useState(false);
|
||||
const [startingCapital, setStartingCapital] = useState(10000);
|
||||
const [accountName, setAccountName] = useState(accounts?.[0]?.name ?? '');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState<string | null>(null);
|
||||
@@ -99,7 +117,7 @@ const BacktestBundleForm: React.FC = () => {
|
||||
timeframe: timeframeMap[timeframe],
|
||||
};
|
||||
const config: TradingBotConfigRequest = {
|
||||
accountName: 'default', // TODO: let user pick
|
||||
accountName: accountName,
|
||||
ticker: tickerMap[asset],
|
||||
timeframe: timeframeMap[timeframe],
|
||||
isForWatchingOnly: false,
|
||||
@@ -170,6 +188,22 @@ const BacktestBundleForm: React.FC = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Select account */}
|
||||
<div className="mb-4">
|
||||
<label className="label">Select account</label>
|
||||
<select
|
||||
className="select select-bordered w-full"
|
||||
value={accountName}
|
||||
onChange={e => setAccountName(e.target.value)}
|
||||
>
|
||||
{accounts?.map(account => (
|
||||
<option key={account.name} value={account.name}>
|
||||
{account.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Scenario/Indicators section */}
|
||||
<div className="mb-4">
|
||||
<CustomScenario
|
||||
@@ -431,4 +465,4 @@ const BacktestBundleForm: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default BacktestBundleForm;
|
||||
export default BacktestBundleForm;
|
||||
@@ -5,21 +5,22 @@ import {useForm} from 'react-hook-form'
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import useBacktestStore from '../../app/store/backtestStore'
|
||||
import {
|
||||
AccountClient,
|
||||
type Backtest,
|
||||
BacktestClient,
|
||||
DataClient,
|
||||
type IndicatorRequest,
|
||||
IndicatorType,
|
||||
MoneyManagementClient,
|
||||
type MoneyManagementRequest,
|
||||
type RunBacktestRequest,
|
||||
ScenarioClient,
|
||||
type ScenarioRequest,
|
||||
SignalType,
|
||||
Ticker,
|
||||
Timeframe,
|
||||
type TradingBotConfigRequest,
|
||||
AccountClient,
|
||||
type Backtest,
|
||||
BacktestClient,
|
||||
DataClient,
|
||||
type IndicatorRequest,
|
||||
IndicatorType,
|
||||
LightBacktestResponse,
|
||||
MoneyManagementClient,
|
||||
type MoneyManagementRequest,
|
||||
type RunBacktestRequest,
|
||||
ScenarioClient,
|
||||
type ScenarioRequest,
|
||||
SignalType,
|
||||
Ticker,
|
||||
Timeframe,
|
||||
type TradingBotConfigRequest,
|
||||
} from '../../generated/ManagingApi'
|
||||
import {Toast} from '../../components/mollecules'
|
||||
import BacktestTable from '../../components/organism/Backtest/backtestTable'
|
||||
@@ -2057,7 +2058,7 @@ const BacktestGenetic: React.FC = () => {
|
||||
<div className="card bg-base-100 shadow-xl">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">Best Results</h3>
|
||||
<BacktestTable list={results.map(r => r.backtest).filter(Boolean) as Backtest[]} displaySummary={false} />
|
||||
<BacktestTable list={results.map(r => r.backtest).filter(Boolean) as LightBacktestResponse[]} displaySummary={false} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,12 +7,12 @@ import ManualPositionModal from '../../components/mollecules/ManualPositionModal
|
||||
import TradesModal from '../../components/mollecules/TradesModal/TradesModal'
|
||||
import {TradeChart, UnifiedTradingModal} from '../../components/organism'
|
||||
import {
|
||||
BotClient,
|
||||
BotType,
|
||||
MoneyManagement,
|
||||
Position,
|
||||
TradingBotResponse,
|
||||
UserClient
|
||||
BotClient,
|
||||
BotType,
|
||||
MoneyManagement,
|
||||
Position,
|
||||
TradingBotResponse,
|
||||
UserClient
|
||||
} from '../../generated/ManagingApi'
|
||||
import type {IBotList} from '../../global/type.tsx'
|
||||
import MoneyManagementModal from '../settingsPage/moneymanagement/moneyManagementModal'
|
||||
@@ -300,14 +300,14 @@ const BotList: React.FC<IBotList> = ({ list }) => {
|
||||
<CardPosition
|
||||
positivePosition={true}
|
||||
positions={bot.positions.filter((p: Position) => {
|
||||
const realized = p.profitAndLoss?.realized ?? 0
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0
|
||||
return realized > 0 ? p : null
|
||||
})}
|
||||
></CardPosition>
|
||||
<CardPosition
|
||||
positivePosition={false}
|
||||
positions={bot.positions.filter((p: Position) => {
|
||||
const realized = p.profitAndLoss?.realized ?? 0
|
||||
const realized = p.ProfitAndLoss?.realized ?? 0
|
||||
return realized <= 0 ? p : null
|
||||
})}
|
||||
></CardPosition>
|
||||
|
||||
Reference in New Issue
Block a user