Remove candle from backtest

This commit is contained in:
2025-07-08 23:36:21 +07:00
parent 3439f13156
commit 9c01dce461
13 changed files with 367 additions and 68 deletions

View File

@@ -1,6 +1,15 @@
import {CardPositionItem, TradeChart} from '..'
import {Backtest} from '../../../generated/ManagingApi'
import {
Backtest,
CandlesWithIndicatorsResponse,
DataClient,
GetCandlesWithIndicatorsRequest,
IndicatorType,
SignalType
} from '../../../generated/ManagingApi'
import {CardPosition, CardText} from '../../mollecules'
import {useQuery} from '@tanstack/react-query'
import useApiUrlStore from '../../../app/store/apiStore'
interface IBacktestRowDetailsProps {
backtest: Backtest;
@@ -9,11 +18,60 @@ interface IBacktestRowDetailsProps {
const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
backtest
}) => {
const {apiUrl} = useApiUrlStore();
const dataClient = new DataClient({}, apiUrl);
// Use TanStack Query to load candles with indicators
const {data: candlesData, isLoading: isLoadingCandles, error} = useQuery({
queryKey: ['candlesWithIndicators', backtest.id, backtest.config?.scenario?.name],
queryFn: async (): Promise<CandlesWithIndicatorsResponse> => {
// Only fetch if no candles are present
if (backtest.candles && backtest.candles.length > 0) {
return {
candles: backtest.candles,
indicatorsValues: backtest.indicatorsValues || {}
};
}
const request: GetCandlesWithIndicatorsRequest = {
ticker: backtest.config.ticker,
startDate: backtest.startDate,
endDate: backtest.endDate,
timeframe: backtest.config.timeframe,
scenario: backtest.config?.scenario ? {
name: backtest.config.scenario.name || '',
indicators: backtest.config.scenario.indicators?.map(indicator => ({
name: indicator.name || '',
type: indicator.type || IndicatorType.RsiDivergence,
signalType: indicator.signalType || SignalType.Signal,
minimumHistory: indicator.minimumHistory || 0,
period: indicator.period,
fastPeriods: indicator.fastPeriods,
slowPeriods: indicator.slowPeriods,
signalPeriods: indicator.signalPeriods,
multiplier: indicator.multiplier,
smoothPeriods: indicator.smoothPeriods,
stochPeriods: indicator.stochPeriods,
cyclePeriods: indicator.cyclePeriods
})) || [],
loopbackPeriod: backtest.config.scenario.loopbackPeriod
} : undefined
};
return await dataClient.data_GetCandlesWithIndicators(request);
},
enabled: !backtest.candles || backtest.candles.length === 0, // Only run query if no candles exist
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime)
});
// Use the data from query or fallback to backtest data
const candles = candlesData?.candles || backtest.candles || [];
const indicatorsValues = candlesData?.indicatorsValues || backtest.indicatorsValues || {};
const {
candles,
positions,
walletBalances,
indicatorsValues,
signals,
statistics,
config
@@ -243,6 +301,12 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
return (
<>
<div className="grid grid-flow-row">
{isLoadingCandles && (
<div className="flex justify-center items-center p-4">
<div className="loading loading-spinner loading-lg"></div>
<span className="ml-2">Loading candles with indicators...</span>
</div>
)}
<div className="grid grid-cols-4 p-5">
<CardPosition
positivePosition={true}
@@ -351,18 +415,20 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
content={getAverageTradesPerDay() + " trades/day"}
></CardText>
</div>
<div className="w-full">
<figure className="w-full">
<TradeChart
candles={candles}
positions={positions}
walletBalances={walletBalances}
indicatorsValues={indicatorsValues}
signals={signals}
height={1000}
></TradeChart>
</figure>
</div>
{!isLoadingCandles && (
<div className="w-full">
<figure className="w-full">
<TradeChart
candles={candles}
positions={positions}
walletBalances={walletBalances}
indicatorsValues={indicatorsValues}
signals={signals}
height={1000}
></TradeChart>
</figure>
</div>
)}
</div>
</>
)

View File

@@ -1087,29 +1087,17 @@ export class DataClient extends AuthorizedApiBase {
return Promise.resolve<SpotlightOverview>(null as any);
}
data_GetCandles(exchange: TradingExchanges | undefined, ticker: Ticker | undefined, startDate: Date | undefined, timeframe: Timeframe | undefined): Promise<Candle[]> {
let url_ = this.baseUrl + "/Data/GetCandles?";
if (exchange === null)
throw new Error("The parameter 'exchange' cannot be null.");
else if (exchange !== undefined)
url_ += "exchange=" + encodeURIComponent("" + exchange) + "&";
if (ticker === null)
throw new Error("The parameter 'ticker' cannot be null.");
else if (ticker !== undefined)
url_ += "ticker=" + encodeURIComponent("" + ticker) + "&";
if (startDate === null)
throw new Error("The parameter 'startDate' cannot be null.");
else if (startDate !== undefined)
url_ += "startDate=" + encodeURIComponent(startDate ? "" + startDate.toISOString() : "") + "&";
if (timeframe === null)
throw new Error("The parameter 'timeframe' cannot be null.");
else if (timeframe !== undefined)
url_ += "timeframe=" + encodeURIComponent("" + timeframe) + "&";
data_GetCandlesWithIndicators(request: GetCandlesWithIndicatorsRequest): Promise<CandlesWithIndicatorsResponse> {
let url_ = this.baseUrl + "/Data/GetCandlesWithIndicators";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(request);
let options_: RequestInit = {
method: "GET",
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
};
@@ -1117,17 +1105,17 @@ export class DataClient extends AuthorizedApiBase {
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processData_GetCandles(_response);
return this.processData_GetCandlesWithIndicators(_response);
});
}
protected processData_GetCandles(response: Response): Promise<Candle[]> {
protected processData_GetCandlesWithIndicators(response: Response): Promise<CandlesWithIndicatorsResponse> {
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) {
return response.text().then((_responseText) => {
let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as Candle[];
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as CandlesWithIndicatorsResponse;
return result200;
});
} else if (status !== 200 && status !== 204) {
@@ -1135,7 +1123,7 @@ export class DataClient extends AuthorizedApiBase {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<Candle[]>(null as any);
return Promise.resolve<CandlesWithIndicatorsResponse>(null as any);
}
data_GetStrategiesStatistics(): Promise<StrategiesStatisticsViewModel> {
@@ -3470,6 +3458,7 @@ export interface RunBacktestRequest {
startDate?: Date;
endDate?: Date;
save?: boolean;
withCandles?: boolean;
}
export interface TradingBotConfigRequest {
@@ -3589,6 +3578,19 @@ export interface TickerSignal {
oneDay: Signal[];
}
export interface CandlesWithIndicatorsResponse {
candles?: Candle[] | null;
indicatorsValues?: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; } | null;
}
export interface GetCandlesWithIndicatorsRequest {
ticker?: Ticker;
startDate?: Date;
endDate?: Date;
timeframe?: Timeframe;
scenario?: ScenarioRequest | null;
}
export interface StrategiesStatisticsViewModel {
totalStrategiesRunning?: number;
changeInLast24Hours?: number;

View File

@@ -598,6 +598,7 @@ export interface RunBacktestRequest {
startDate?: Date;
endDate?: Date;
save?: boolean;
withCandles?: boolean;
}
export interface TradingBotConfigRequest {
@@ -717,6 +718,19 @@ export interface TickerSignal {
oneDay: Signal[];
}
export interface CandlesWithIndicatorsResponse {
candles?: Candle[] | null;
indicatorsValues?: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; } | null;
}
export interface GetCandlesWithIndicatorsRequest {
ticker?: Ticker;
startDate?: Date;
endDate?: Date;
timeframe?: Timeframe;
scenario?: ScenarioRequest | null;
}
export interface StrategiesStatisticsViewModel {
totalStrategiesRunning?: number;
changeInLast24Hours?: number;