Fix front and backtest
This commit is contained in:
@@ -30,7 +30,6 @@ namespace Managing.Application.Abstractions
|
||||
int GetWinRate();
|
||||
decimal GetProfitAndLoss();
|
||||
decimal GetTotalFees();
|
||||
void LoadScenario(string scenarioName);
|
||||
void LoadScenario(Scenario scenario);
|
||||
void UpdateIndicatorsValues();
|
||||
Task LoadAccount();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Managing.Application.Abstractions;
|
||||
using Managing.Application.Abstractions.Repositories;
|
||||
using Managing.Application.Abstractions.Services;
|
||||
using Managing.Application.Bots;
|
||||
using Managing.Core.FixedSizedQueue;
|
||||
using Managing.Domain.Accounts;
|
||||
using Managing.Domain.Backtests;
|
||||
@@ -113,24 +114,15 @@ namespace Managing.Application.Backtesting
|
||||
List<Candle> candles,
|
||||
User user = null)
|
||||
{
|
||||
// Set FlipPosition based on BotType
|
||||
config.FlipPosition = config.FlipPosition;
|
||||
|
||||
var tradingBot = _botFactory.CreateBacktestTradingBot(config);
|
||||
|
||||
// Load scenario - prefer Scenario object over ScenarioName
|
||||
if (config.Scenario != null)
|
||||
// Scenario and indicators should already be loaded in constructor by BotService
|
||||
// This is just a validation check to ensure everything loaded properly
|
||||
if (tradingBot is TradingBot bot && !bot.Indicators.Any())
|
||||
{
|
||||
tradingBot.LoadScenario(config.Scenario);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(config.ScenarioName))
|
||||
{
|
||||
tradingBot.LoadScenario(config.ScenarioName);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Either Scenario object or ScenarioName must be provided in TradingBotConfig");
|
||||
throw new InvalidOperationException(
|
||||
$"No indicators were loaded for scenario '{config.ScenarioName ?? config.Scenario?.Name}'. " +
|
||||
"This indicates a problem with scenario loading.");
|
||||
}
|
||||
|
||||
tradingBot.User = user;
|
||||
@@ -218,7 +210,6 @@ namespace Managing.Application.Backtesting
|
||||
_logger.LogInformation("Backtest processing completed. Calculating final results...");
|
||||
|
||||
bot.Candles = new HashSet<Candle>(candles);
|
||||
// bot.UpdateIndicatorsValues();
|
||||
|
||||
var indicatorsValues = GetIndicatorsValues(bot.Config.Scenario.Indicators, candles);
|
||||
|
||||
@@ -256,7 +247,8 @@ namespace Managing.Application.Backtesting
|
||||
Statistics = stats,
|
||||
OptimizedMoneyManagement = optimizedMoneyManagement,
|
||||
IndicatorsValues = AggregateValues(indicatorsValues, bot.IndicatorsValues),
|
||||
Score = score
|
||||
Score = score,
|
||||
Id = Guid.NewGuid().ToString()
|
||||
};
|
||||
|
||||
return result;
|
||||
|
||||
@@ -38,27 +38,14 @@ namespace Managing.Application.Bots.Base
|
||||
|
||||
ITradingBot IBotFactory.CreateTradingBot(TradingBotConfig config)
|
||||
{
|
||||
return new TradingBot(
|
||||
_exchangeService,
|
||||
_tradingBotLogger,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
_botService,
|
||||
config);
|
||||
// Delegate to BotService which handles scenario loading properly
|
||||
return _botService.CreateTradingBot(config);
|
||||
}
|
||||
|
||||
ITradingBot IBotFactory.CreateBacktestTradingBot(TradingBotConfig config)
|
||||
{
|
||||
config.IsForBacktest = true;
|
||||
return new TradingBot(
|
||||
_exchangeService,
|
||||
_tradingBotLogger,
|
||||
_tradingService,
|
||||
_accountService,
|
||||
_messengerService,
|
||||
_botService,
|
||||
config);
|
||||
// Delegate to BotService which handles scenario loading properly
|
||||
return _botService.CreateBacktestTradingBot(config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,16 @@ public class TradingBot : Bot, ITradingBot
|
||||
WalletBalances = new Dictionary<DateTime, decimal>();
|
||||
IndicatorsValues = new Dictionary<IndicatorType, IndicatorsResultBase>();
|
||||
|
||||
// Load indicators if scenario is provided in config
|
||||
if (Config.Scenario != null)
|
||||
{
|
||||
LoadIndicators(Config.Scenario);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided in TradingBotConfig. ScenarioName alone is not sufficient.");
|
||||
}
|
||||
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
Interval = CandleExtensions.GetIntervalFromTimeframe(Config.Timeframe);
|
||||
@@ -91,7 +101,13 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
if (!Config.IsForBacktest)
|
||||
{
|
||||
LoadScenario(Config.ScenarioName);
|
||||
// Scenario and indicators should already be loaded in constructor
|
||||
// This is just a safety check
|
||||
if (Config.Scenario == null || !Indicators.Any())
|
||||
{
|
||||
throw new InvalidOperationException("Scenario or indicators not loaded properly in constructor. This indicates a configuration error.");
|
||||
}
|
||||
|
||||
PreloadCandles().GetAwaiter().GetResult();
|
||||
CancelAllOrders().GetAwaiter().GetResult();
|
||||
|
||||
@@ -127,33 +143,32 @@ public class TradingBot : Bot, ITradingBot
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadScenario(string scenarioName)
|
||||
{
|
||||
if (Config.Scenario != null)
|
||||
return;
|
||||
|
||||
var scenario = TradingService.GetScenarioByName(scenarioName);
|
||||
if (scenario == null)
|
||||
{
|
||||
Logger.LogWarning("No scenario found for this scenario name");
|
||||
Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadIndicators(ScenarioHelpers.GetIndicatorsFromScenario(scenario));
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadScenario(Scenario scenario)
|
||||
{
|
||||
if (scenario == null)
|
||||
{
|
||||
Logger.LogWarning("Null scenario provided");
|
||||
Stop();
|
||||
var errorMessage = "Null scenario provided";
|
||||
Logger.LogWarning(errorMessage);
|
||||
|
||||
// If called during construction, throw exception instead of Stop()
|
||||
if (Status == BotStatus.Down)
|
||||
{
|
||||
throw new ArgumentException(errorMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Store the scenario in config and load indicators
|
||||
Config.Scenario = scenario;
|
||||
LoadIndicators(ScenarioHelpers.GetIndicatorsFromScenario(scenario));
|
||||
|
||||
Logger.LogInformation($"Loaded scenario '{scenario.Name}' with {Indicators.Count} indicators");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,10 +179,15 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
public void LoadIndicators(IEnumerable<IIndicator> indicators)
|
||||
{
|
||||
foreach (var strategy in indicators)
|
||||
// Clear existing indicators to prevent duplicates
|
||||
Indicators.Clear();
|
||||
|
||||
foreach (var indicator in indicators)
|
||||
{
|
||||
Indicators.Add(strategy);
|
||||
Indicators.Add(indicator);
|
||||
}
|
||||
|
||||
Logger.LogInformation($"Loaded {Indicators.Count} indicators for bot '{Name}'");
|
||||
}
|
||||
|
||||
public async Task Run()
|
||||
@@ -1444,9 +1464,9 @@ public class TradingBot : Bot, ITradingBot
|
||||
throw new ArgumentException("Account name cannot be null or empty");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(newConfig.ScenarioName))
|
||||
if (newConfig.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario name cannot be null or empty");
|
||||
throw new ArgumentException("Scenario object must be provided in configuration");
|
||||
}
|
||||
|
||||
// Protect critical properties that shouldn't change for running bots
|
||||
@@ -1487,9 +1507,17 @@ public class TradingBot : Bot, ITradingBot
|
||||
|
||||
// If scenario changed, reload it
|
||||
var currentScenario = Config.Scenario?.Name;
|
||||
if (Config.ScenarioName != currentScenario)
|
||||
var newScenario = newConfig.Scenario?.Name;
|
||||
if (newScenario != currentScenario)
|
||||
{
|
||||
LoadScenario(Config.ScenarioName);
|
||||
if (newConfig.Scenario != null)
|
||||
{
|
||||
LoadScenario(newConfig.Scenario);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("New scenario object must be provided when updating configuration.");
|
||||
}
|
||||
}
|
||||
|
||||
await LogInformation("✅ **Configuration Applied**\n" +
|
||||
|
||||
@@ -134,6 +134,25 @@ namespace Managing.Application.ManageBot
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (scalpingConfig.Scenario == null && !string.IsNullOrEmpty(scalpingConfig.ScenarioName))
|
||||
{
|
||||
var scenario = _tradingService.GetScenarioByName(scalpingConfig.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
scalpingConfig.Scenario = scenario;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Scenario '{scalpingConfig.ScenarioName}' not found in database when loading backup");
|
||||
}
|
||||
}
|
||||
|
||||
if (scalpingConfig.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid when loading backup");
|
||||
}
|
||||
|
||||
// Ensure critical properties are set correctly for restored bots
|
||||
scalpingConfig.IsForBacktest = false;
|
||||
|
||||
@@ -235,6 +254,25 @@ namespace Managing.Application.ManageBot
|
||||
if (_botTasks.TryGetValue(identifier, out var botTaskWrapper) &&
|
||||
botTaskWrapper.BotInstance is TradingBot tradingBot)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (newConfig.Scenario == null && !string.IsNullOrEmpty(newConfig.ScenarioName))
|
||||
{
|
||||
var scenario = _tradingService.GetScenarioByName(newConfig.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
newConfig.Scenario = scenario;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Scenario '{newConfig.ScenarioName}' not found in database when updating configuration");
|
||||
}
|
||||
}
|
||||
|
||||
if (newConfig.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid when updating configuration");
|
||||
}
|
||||
|
||||
// Check if the bot name is changing
|
||||
if (newConfig.Name != identifier && !string.IsNullOrEmpty(newConfig.Name))
|
||||
{
|
||||
@@ -279,6 +317,25 @@ namespace Managing.Application.ManageBot
|
||||
|
||||
public ITradingBot CreateTradingBot(TradingBotConfig config)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||
{
|
||||
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
config.Scenario = scenario;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||
}
|
||||
|
||||
return new TradingBot(
|
||||
_exchangeService,
|
||||
_tradingBotLogger,
|
||||
@@ -291,6 +348,25 @@ namespace Managing.Application.ManageBot
|
||||
|
||||
public ITradingBot CreateBacktestTradingBot(TradingBotConfig config)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||
{
|
||||
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
config.Scenario = scenario;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||
}
|
||||
|
||||
config.IsForBacktest = true;
|
||||
return new TradingBot(
|
||||
_exchangeService,
|
||||
@@ -304,6 +380,25 @@ namespace Managing.Application.ManageBot
|
||||
|
||||
public ITradingBot CreateScalpingBot(TradingBotConfig config)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||
{
|
||||
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
config.Scenario = scenario;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||
}
|
||||
|
||||
config.FlipPosition = false;
|
||||
return new TradingBot(
|
||||
_exchangeService,
|
||||
@@ -317,6 +412,25 @@ namespace Managing.Application.ManageBot
|
||||
|
||||
public ITradingBot CreateBacktestScalpingBot(TradingBotConfig config)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||
{
|
||||
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
config.Scenario = scenario;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||
}
|
||||
|
||||
config.IsForBacktest = true;
|
||||
config.FlipPosition = false;
|
||||
return new TradingBot(
|
||||
@@ -331,6 +445,25 @@ namespace Managing.Application.ManageBot
|
||||
|
||||
public ITradingBot CreateFlippingBot(TradingBotConfig config)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||
{
|
||||
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
config.Scenario = scenario;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||
}
|
||||
|
||||
config.FlipPosition = true;
|
||||
return new TradingBot(
|
||||
_exchangeService,
|
||||
@@ -344,6 +477,25 @@ namespace Managing.Application.ManageBot
|
||||
|
||||
public ITradingBot CreateBacktestFlippingBot(TradingBotConfig config)
|
||||
{
|
||||
// Ensure the scenario is properly loaded from database if needed
|
||||
if (config.Scenario == null && !string.IsNullOrEmpty(config.ScenarioName))
|
||||
{
|
||||
var scenario = _tradingService.GetScenarioByName(config.ScenarioName);
|
||||
if (scenario != null)
|
||||
{
|
||||
config.Scenario = scenario;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Scenario '{config.ScenarioName}' not found in database");
|
||||
}
|
||||
}
|
||||
|
||||
if (config.Scenario == null)
|
||||
{
|
||||
throw new ArgumentException("Scenario object must be provided or ScenarioName must be valid");
|
||||
}
|
||||
|
||||
config.IsForBacktest = true;
|
||||
config.FlipPosition = true;
|
||||
return new TradingBot(
|
||||
|
||||
40
src/Managing.WebApp/src/app/store/backtestStore.ts
Normal file
40
src/Managing.WebApp/src/app/store/backtestStore.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import {create} from 'zustand'
|
||||
import type {Backtest} from '../../generated/ManagingApi'
|
||||
|
||||
interface BacktestStore {
|
||||
backtests: Backtest[]
|
||||
isLoading: boolean
|
||||
setBacktests: (backtests: Backtest[]) => void
|
||||
addBacktest: (backtest: Backtest) => void
|
||||
removeBacktest: (id: string) => void
|
||||
setLoading: (loading: boolean) => void
|
||||
clearBacktests: () => void
|
||||
}
|
||||
|
||||
const useBacktestStore = create<BacktestStore>((set, get) => ({
|
||||
backtests: [],
|
||||
isLoading: false,
|
||||
|
||||
setBacktests: (backtests: Backtest[]) =>
|
||||
set({ backtests }),
|
||||
|
||||
addBacktest: (backtest: Backtest) =>
|
||||
set((state) => ({
|
||||
backtests: [...state.backtests, backtest]
|
||||
})),
|
||||
|
||||
removeBacktest: (id: string) =>
|
||||
set((state) => ({
|
||||
backtests: state.backtests.filter(backtest =>
|
||||
String(backtest.id) !== String(id)
|
||||
)
|
||||
})),
|
||||
|
||||
setLoading: (loading: boolean) =>
|
||||
set({ isLoading: loading }),
|
||||
|
||||
clearBacktests: () =>
|
||||
set({ backtests: [] })
|
||||
}))
|
||||
|
||||
export default useBacktestStore
|
||||
@@ -0,0 +1,117 @@
|
||||
import React, {useState} from 'react'
|
||||
import {Modal} from '../index'
|
||||
import type {Backtest} from '../../../generated/ManagingApi'
|
||||
|
||||
interface ConfigDisplayModalProps {
|
||||
showModal: boolean
|
||||
onClose: () => void
|
||||
backtest: Backtest | null
|
||||
}
|
||||
|
||||
const ConfigDisplayModal: React.FC<ConfigDisplayModalProps> = ({
|
||||
showModal,
|
||||
onClose,
|
||||
backtest,
|
||||
}) => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const formatConfigForDisplay = (config: any) => {
|
||||
// Create a clean config object without undefined values
|
||||
const cleanConfig: Record<string, any> = {
|
||||
accountName: config.accountName,
|
||||
moneyManagement: config.moneyManagement,
|
||||
ticker: config.ticker,
|
||||
timeframe: config.timeframe,
|
||||
isForWatchingOnly: config.isForWatchingOnly,
|
||||
botTradingBalance: config.botTradingBalance,
|
||||
isForBacktest: config.isForBacktest,
|
||||
cooldownPeriod: config.cooldownPeriod,
|
||||
maxLossStreak: config.maxLossStreak,
|
||||
flipPosition: config.flipPosition,
|
||||
name: config.name,
|
||||
riskManagement: config.riskManagement,
|
||||
scenario: config.scenario,
|
||||
scenarioName: config.scenarioName,
|
||||
maxPositionTimeHours: config.maxPositionTimeHours,
|
||||
closeEarlyWhenProfitable: config.closeEarlyWhenProfitable,
|
||||
flipOnlyWhenInProfit: config.flipOnlyWhenInProfit,
|
||||
useSynthApi: config.useSynthApi,
|
||||
useForPositionSizing: config.useForPositionSizing,
|
||||
useForSignalFiltering: config.useForSignalFiltering,
|
||||
useForDynamicStopLoss: config.useForDynamicStopLoss,
|
||||
}
|
||||
|
||||
// Remove undefined values
|
||||
Object.keys(cleanConfig).forEach(key => {
|
||||
if (cleanConfig[key] === undefined || cleanConfig[key] === null) {
|
||||
delete cleanConfig[key]
|
||||
}
|
||||
})
|
||||
|
||||
return JSON.stringify(cleanConfig, null, 2)
|
||||
}
|
||||
|
||||
const handleCopyConfig = async () => {
|
||||
if (!backtest?.config) return
|
||||
|
||||
try {
|
||||
const configJson = formatConfigForDisplay(backtest.config)
|
||||
await navigator.clipboard.writeText(configJson)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
} catch (error) {
|
||||
console.error('Failed to copy config:', error)
|
||||
}
|
||||
}
|
||||
|
||||
if (!backtest) return null
|
||||
|
||||
const configJson = formatConfigForDisplay(backtest.config)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
showModal={showModal}
|
||||
onClose={onClose}
|
||||
titleHeader="Backtest Configuration"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h3 className="text-lg font-semibold">Configuration JSON</h3>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm ${copied ? 'btn-success' : 'btn-primary'}`}
|
||||
onClick={handleCopyConfig}
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
Copied!
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
|
||||
</svg>
|
||||
Copy Config
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="bg-base-200 rounded-lg p-4 max-h-96 overflow-auto">
|
||||
<pre className="text-sm text-base-content whitespace-pre-wrap break-words">
|
||||
{configJson}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-base-content/70">
|
||||
<p>This configuration can be copied and imported into the backtest modal to replicate this exact setup.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigDisplayModal
|
||||
@@ -11,8 +11,8 @@ import {Loader} from '../../atoms'
|
||||
|
||||
const navigation = [
|
||||
{ href: '/bots', name: 'Bots' },
|
||||
{ href: '/scenarios', name: 'Scenarios' },
|
||||
{ href: '/backtest', name: 'Backtest' },
|
||||
{ href: '/scenarios', name: 'Scenarios' },
|
||||
{ href: '/tools', name: 'Tools' },
|
||||
{ href: '/settings', name: 'Settings' },
|
||||
]
|
||||
|
||||
@@ -12,3 +12,4 @@ export { default as LogIn } from './LogIn/LogIn'
|
||||
export { default as GridTile } from './GridTile/GridTile'
|
||||
export { default as SelectColumnFilter } from './Table/SelectColumnFilter'
|
||||
export { default as Card } from './Card/Card'
|
||||
export { default as ConfigDisplayModal } from './ConfigDisplayModal/ConfigDisplayModal'
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, {useEffect, useState} from 'react'
|
||||
import {useQuery} from '@tanstack/react-query'
|
||||
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import useBacktestStore from '../../../app/store/backtestStore'
|
||||
import type {
|
||||
Backtest,
|
||||
MoneyManagement,
|
||||
@@ -13,7 +14,6 @@ import type {
|
||||
TradingBotConfigRequest
|
||||
} from '../../../generated/ManagingApi'
|
||||
import {BacktestClient, BotClient, MoneyManagementClient} from '../../../generated/ManagingApi'
|
||||
import type {IBacktestCards} from '../../../global/type.tsx'
|
||||
import MoneyManagementModal from '../../../pages/settingsPage/moneymanagement/moneyManagementModal'
|
||||
import {CardPosition, CardText, Toast} from '../../mollecules'
|
||||
import CardPositionItem from '../Trading/CardPositionItem'
|
||||
@@ -52,9 +52,14 @@ function daysBetween(date: Date) {
|
||||
return diffDays
|
||||
}
|
||||
|
||||
const BacktestCards: React.FC<IBacktestCards> = ({list, setBacktests}) => {
|
||||
interface BacktestCardsProps {
|
||||
list: Backtest[] | undefined
|
||||
}
|
||||
|
||||
const BacktestCards: React.FC<BacktestCardsProps> = ({list}) => {
|
||||
console.log(list)
|
||||
const {apiUrl} = useApiUrlStore()
|
||||
const {addBacktest, removeBacktest} = useBacktestStore()
|
||||
const [showMoneyManagementModal, setShowMoneyManagementModal] =
|
||||
React.useState(false)
|
||||
const [selectedMoneyManagement, setSelectedMoneyManagement] =
|
||||
@@ -173,13 +178,29 @@ const BacktestCards: React.FC<IBacktestCards> = ({list, setBacktests}) => {
|
||||
.backtest_Run(request)
|
||||
.then((backtest: Backtest) => {
|
||||
t.update('success', `${backtest.config.ticker} Backtest Succeeded`)
|
||||
setBacktests((arr: Backtest[]) => [...arr, backtest])
|
||||
addBacktest(backtest)
|
||||
})
|
||||
.catch((err) => {
|
||||
t.update('error', 'Error :' + err)
|
||||
})
|
||||
}
|
||||
|
||||
async function deleteBacktest(id: string) {
|
||||
const t = new Toast('Deleting backtest')
|
||||
const client = new BacktestClient({}, apiUrl)
|
||||
|
||||
await client
|
||||
.backtest_DeleteBacktest(id)
|
||||
.then(() => {
|
||||
t.update('success', 'Backtest deleted')
|
||||
// Remove the deleted backtest from the store
|
||||
removeBacktest(id)
|
||||
})
|
||||
.catch((err) => {
|
||||
t.update('error', err)
|
||||
})
|
||||
}
|
||||
|
||||
function saveMoneyManagement(moneyManagement: MoneyManagement) {
|
||||
setSelectedMoneyManagement(moneyManagement)
|
||||
setShowMoneyManagementModal(true)
|
||||
@@ -194,9 +215,14 @@ const BacktestCards: React.FC<IBacktestCards> = ({list, setBacktests}) => {
|
||||
>
|
||||
<div className="indicator">
|
||||
<div className="indicator-item indicator-top">
|
||||
<button className="btn btn-primary h-5 min-h-0 px-2 mr-5 rounded-full">
|
||||
<TrashIcon width={15}></TrashIcon>
|
||||
</button>
|
||||
<div className="tooltip" data-tip="Delete backtest">
|
||||
<button
|
||||
className="btn btn-primary h-5 min-h-0 px-2 mr-5 rounded-full"
|
||||
onClick={() => deleteBacktest(backtest.id)}
|
||||
>
|
||||
<TrashIcon width={15}></TrashIcon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-base-300 shadow-xl">
|
||||
|
||||
@@ -28,7 +28,6 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
showModal,
|
||||
closeModal,
|
||||
setBacktests,
|
||||
showLoopSlider = false,
|
||||
}) => {
|
||||
// Get date 15 days ago for start date
|
||||
const defaultStartDate = new Date();
|
||||
@@ -77,9 +76,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
|
||||
const [selectedAccount, setSelectedAccount] = useState<string>('')
|
||||
const [selectedTimeframe, setSelectedTimeframe] = useState<Timeframe>(Timeframe.OneHour)
|
||||
const [selectedLoopQuantity, setLoopQuantity] = React.useState<number>(
|
||||
showLoopSlider ? 3 : 1
|
||||
)
|
||||
|
||||
const [balance, setBalance] = useState<number>(10000)
|
||||
|
||||
const [customMoneyManagement, setCustomMoneyManagement] =
|
||||
@@ -204,17 +201,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
|
||||
t.update('success', `${backtest.config.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,
|
||||
}
|
||||
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
|
||||
|
||||
@@ -2,18 +2,24 @@ import {ChevronDownIcon, ChevronRightIcon, CogIcon, PlayIcon, TrashIcon} from '@
|
||||
import React, {useEffect, useState} from 'react'
|
||||
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import useBacktestStore from '../../../app/store/backtestStore'
|
||||
import type {Backtest} from '../../../generated/ManagingApi'
|
||||
import {BacktestClient} from '../../../generated/ManagingApi'
|
||||
import type {IBacktestCards} from '../../../global/type.tsx'
|
||||
import {CardText, SelectColumnFilter, Table} from '../../mollecules'
|
||||
import {CardText, ConfigDisplayModal, SelectColumnFilter, Table} from '../../mollecules'
|
||||
import {UnifiedTradingModal} from '../index'
|
||||
import Toast from '../../mollecules/Toast/Toast'
|
||||
|
||||
import BacktestRowDetails from './backtestRowDetails'
|
||||
|
||||
const BacktestTable: React.FC<IBacktestCards> = ({list, isFetching, setBacktests}) => {
|
||||
interface BacktestTableProps {
|
||||
list: Backtest[] | undefined
|
||||
isFetching?: boolean
|
||||
}
|
||||
|
||||
const BacktestTable: React.FC<BacktestTableProps> = ({list, isFetching}) => {
|
||||
const [rows, setRows] = useState<Backtest[]>([])
|
||||
const {apiUrl} = useApiUrlStore()
|
||||
const {removeBacktest} = useBacktestStore()
|
||||
const [optimizedMoneyManagement, setOptimizedMoneyManagement] = useState({
|
||||
stopLoss: 0,
|
||||
takeProfit: 0,
|
||||
@@ -36,6 +42,10 @@ const BacktestTable: React.FC<IBacktestCards> = ({list, isFetching, setBacktests
|
||||
const [showBacktestConfigModal, setShowBacktestConfigModal] = useState(false)
|
||||
const [selectedBacktestForRerun, setSelectedBacktestForRerun] = useState<Backtest | null>(null)
|
||||
|
||||
// Config display modal state
|
||||
const [showConfigDisplayModal, setShowConfigDisplayModal] = useState(false)
|
||||
const [selectedBacktestForConfigView, setSelectedBacktestForConfigView] = useState<Backtest | null>(null)
|
||||
|
||||
const handleOpenBotConfigModal = (backtest: Backtest) => {
|
||||
setSelectedBacktest(backtest)
|
||||
setShowBotConfigModal(true)
|
||||
@@ -56,6 +66,16 @@ const BacktestTable: React.FC<IBacktestCards> = ({list, isFetching, setBacktests
|
||||
setSelectedBacktestForRerun(null)
|
||||
}
|
||||
|
||||
const handleOpenConfigDisplayModal = (backtest: Backtest) => {
|
||||
setSelectedBacktestForConfigView(backtest)
|
||||
setShowConfigDisplayModal(true)
|
||||
}
|
||||
|
||||
const handleCloseConfigDisplayModal = () => {
|
||||
setShowConfigDisplayModal(false)
|
||||
setSelectedBacktestForConfigView(null)
|
||||
}
|
||||
|
||||
async function deleteBacktest(id: string) {
|
||||
const t = new Toast('Deleting backtest')
|
||||
const client = new BacktestClient({}, apiUrl)
|
||||
@@ -64,11 +84,8 @@ const BacktestTable: React.FC<IBacktestCards> = ({list, isFetching, setBacktests
|
||||
.backtest_DeleteBacktest(id)
|
||||
.then(() => {
|
||||
t.update('success', 'Backtest deleted')
|
||||
// Remove the deleted backtest from the list
|
||||
if (list) {
|
||||
const updatedList = list.filter(backtest => backtest.id !== id);
|
||||
setBacktests(updatedList);
|
||||
}
|
||||
// Remove the deleted backtest from the store
|
||||
removeBacktest(id)
|
||||
})
|
||||
.catch((err) => {
|
||||
t.update('error', err)
|
||||
@@ -218,7 +235,7 @@ const BacktestTable: React.FC<IBacktestCards> = ({list, isFetching, setBacktests
|
||||
data-value={cell.row.values.name}
|
||||
onClick={() => deleteBacktest(cell.row.values.id)}
|
||||
>
|
||||
<TrashIcon className="text-accent w-4"></TrashIcon>
|
||||
<TrashIcon className="text-error w-4"></TrashIcon>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
@@ -227,15 +244,35 @@ const BacktestTable: React.FC<IBacktestCards> = ({list, isFetching, setBacktests
|
||||
accessor: 'id',
|
||||
disableFilters: true,
|
||||
},
|
||||
|
||||
{
|
||||
Cell: ({cell}: any) => (
|
||||
<>
|
||||
<div className="tooltip" data-tip="Re-run backtest with same config">
|
||||
<div className="tooltip" data-tip="Create bot from backtest">
|
||||
<button
|
||||
data-value={cell.row.values.name}
|
||||
onClick={() => handleOpenBotConfigModal(cell.row.original as Backtest)}
|
||||
>
|
||||
<PlayIcon className="text-success w-4"></PlayIcon>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
Header: '',
|
||||
accessor: 'runner',
|
||||
disableFilters: true,
|
||||
},
|
||||
{
|
||||
Cell: ({cell}: any) => (
|
||||
<>
|
||||
<div className="tooltip" data-tip="Rerun backtest with same config">
|
||||
<button
|
||||
data-value={cell.row.values.name}
|
||||
onClick={() => handleOpenBacktestConfigModal(cell.row.original as Backtest)}
|
||||
>
|
||||
<CogIcon className="text-info w-4"></CogIcon>
|
||||
<svg className="w-4 h-4 text-warning" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
@@ -247,18 +284,18 @@ const BacktestTable: React.FC<IBacktestCards> = ({list, isFetching, setBacktests
|
||||
{
|
||||
Cell: ({cell}: any) => (
|
||||
<>
|
||||
<div className="tooltip" data-tip="Create bot from backtest">
|
||||
<div className="tooltip" data-tip="View/Copy configuration">
|
||||
<button
|
||||
data-value={cell.row.values.name}
|
||||
onClick={() => handleOpenBotConfigModal(cell.row.original as Backtest)}
|
||||
onClick={() => handleOpenConfigDisplayModal(cell.row.original as Backtest)}
|
||||
>
|
||||
<PlayIcon className="text-primary w-4"></PlayIcon>
|
||||
<CogIcon className="text-info w-4"></CogIcon>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
Header: '',
|
||||
accessor: 'runner',
|
||||
accessor: 'config',
|
||||
disableFilters: true,
|
||||
}
|
||||
],
|
||||
@@ -481,10 +518,15 @@ const BacktestTable: React.FC<IBacktestCards> = ({list, isFetching, setBacktests
|
||||
mode="backtest"
|
||||
backtest={selectedBacktestForRerun}
|
||||
closeModal={handleCloseBacktestConfigModal}
|
||||
setBacktests={setBacktests}
|
||||
showLoopSlider={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Config Display Modal */}
|
||||
<ConfigDisplayModal
|
||||
showModal={showConfigDisplayModal}
|
||||
onClose={handleCloseConfigDisplayModal}
|
||||
backtest={selectedBacktestForConfigView}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -3,30 +3,31 @@ import React, {useEffect, useState} from 'react'
|
||||
import {type SubmitHandler, useForm} from 'react-hook-form'
|
||||
|
||||
import useApiUrlStore from '../../../app/store/apiStore'
|
||||
import useBacktestStore from '../../../app/store/backtestStore'
|
||||
import {useCustomMoneyManagement} from '../../../app/store/customMoneyManagement'
|
||||
import {useCustomScenario} from '../../../app/store/customScenario'
|
||||
import {
|
||||
AccountClient,
|
||||
BacktestClient,
|
||||
BotClient,
|
||||
DataClient,
|
||||
MoneyManagement,
|
||||
MoneyManagementClient,
|
||||
RiskManagement,
|
||||
RiskToleranceLevel,
|
||||
RunBacktestRequest,
|
||||
Scenario,
|
||||
ScenarioClient,
|
||||
ScenarioRequest,
|
||||
SignalType,
|
||||
StartBotRequest,
|
||||
Ticker,
|
||||
Timeframe,
|
||||
TradingBotConfigRequest,
|
||||
UpdateBotConfigRequest,
|
||||
AccountClient,
|
||||
BacktestClient,
|
||||
BotClient,
|
||||
DataClient,
|
||||
MoneyManagement,
|
||||
MoneyManagementClient,
|
||||
RiskManagement,
|
||||
RiskToleranceLevel,
|
||||
RunBacktestRequest,
|
||||
Scenario,
|
||||
ScenarioClient,
|
||||
ScenarioRequest,
|
||||
SignalType,
|
||||
StartBotRequest,
|
||||
Ticker,
|
||||
Timeframe,
|
||||
TradingBotConfigRequest,
|
||||
UpdateBotConfigRequest,
|
||||
} from '../../../generated/ManagingApi'
|
||||
import type {IUnifiedTradingConfigInput, UnifiedTradingModalProps} from '../../../global/type'
|
||||
import {Loader, Slider} from '../../atoms'
|
||||
import {Loader} from '../../atoms'
|
||||
import {Modal, Toast} from '../../mollecules'
|
||||
import FormInput from '../../mollecules/FormInput/FormInput'
|
||||
import CustomMoneyManagement from '../CustomMoneyManagement/CustomMoneyManagement'
|
||||
@@ -36,7 +37,6 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
showModal,
|
||||
closeModal,
|
||||
mode,
|
||||
showLoopSlider = false,
|
||||
setBacktests,
|
||||
backtest,
|
||||
existingBot,
|
||||
@@ -118,11 +118,9 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
// State for collapsible sections
|
||||
const [showAdvancedParams, setShowAdvancedParams] = useState(false);
|
||||
const [showRiskManagement, setShowRiskManagement] = useState(false);
|
||||
const [showJsonImport, setShowJsonImport] = useState(false);
|
||||
|
||||
|
||||
// State for loop slider (backtests only)
|
||||
const [selectedLoopQuantity, setLoopQuantity] = React.useState<number>(
|
||||
showLoopSlider ? 3 : 1
|
||||
);
|
||||
|
||||
// Custom components state
|
||||
const [customMoneyManagement, setCustomMoneyManagement] = useState<MoneyManagement | undefined>(undefined);
|
||||
@@ -136,7 +134,12 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
// Selected ticker for bots (separate from form tickers for backtests)
|
||||
const [selectedTicker, setSelectedTicker] = useState<Ticker | undefined>(undefined);
|
||||
|
||||
// JSON import state
|
||||
const [jsonConfig, setJsonConfig] = useState('');
|
||||
const [importError, setImportError] = useState('');
|
||||
|
||||
const { apiUrl } = useApiUrlStore();
|
||||
const { addBacktest } = useBacktestStore();
|
||||
const { setCustomMoneyManagement: setGlobalCustomMoneyManagement } = useCustomMoneyManagement();
|
||||
const { setCustomScenario: setGlobalCustomScenario } = useCustomScenario();
|
||||
|
||||
@@ -436,6 +439,84 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleImportJsonConfig = () => {
|
||||
try {
|
||||
setImportError('');
|
||||
const config = JSON.parse(jsonConfig);
|
||||
|
||||
// Load the configuration into the form
|
||||
if (config.accountName) setValue('accountName', config.accountName);
|
||||
if (config.timeframe) setValue('timeframe', config.timeframe);
|
||||
if (config.cooldownPeriod !== undefined) setValue('cooldownPeriod', config.cooldownPeriod);
|
||||
if (config.maxLossStreak !== undefined) setValue('maxLossStreak', config.maxLossStreak);
|
||||
if (config.maxPositionTimeHours !== undefined) setValue('maxPositionTimeHours', config.maxPositionTimeHours);
|
||||
if (config.flipOnlyWhenInProfit !== undefined) setValue('flipOnlyWhenInProfit', config.flipOnlyWhenInProfit);
|
||||
if (config.closeEarlyWhenProfitable !== undefined) setValue('closeEarlyWhenProfitable', config.closeEarlyWhenProfitable);
|
||||
if (config.botTradingBalance !== undefined) setValue('balance', config.botTradingBalance);
|
||||
if (config.useSynthApi !== undefined) setValue('useSynthApi', config.useSynthApi);
|
||||
if (config.useForPositionSizing !== undefined) setValue('useForPositionSizing', config.useForPositionSizing);
|
||||
if (config.useForSignalFiltering !== undefined) setValue('useForSignalFiltering', config.useForSignalFiltering);
|
||||
if (config.useForDynamicStopLoss !== undefined) setValue('useForDynamicStopLoss', config.useForDynamicStopLoss);
|
||||
|
||||
// For bot modes, set the ticker
|
||||
if (mode !== 'backtest' && config.ticker) {
|
||||
setSelectedTicker(config.ticker);
|
||||
}
|
||||
|
||||
// For backtest mode, set tickers array
|
||||
if (mode === 'backtest' && config.ticker) {
|
||||
setValue('tickers', [config.ticker]);
|
||||
}
|
||||
|
||||
// Handle scenario
|
||||
if (config.scenario) {
|
||||
setShowCustomScenario(true);
|
||||
setCustomScenario(config.scenario);
|
||||
setGlobalCustomScenario(config.scenario);
|
||||
setSelectedScenario('custom');
|
||||
} else if (config.scenarioName) {
|
||||
setSelectedScenario(config.scenarioName);
|
||||
setValue('scenarioName', config.scenarioName);
|
||||
setShowCustomScenario(false);
|
||||
}
|
||||
|
||||
// Handle money management
|
||||
if (config.moneyManagement) {
|
||||
setShowCustomMoneyManagement(true);
|
||||
setCustomMoneyManagement(config.moneyManagement);
|
||||
|
||||
// Convert decimal values to percentages for UI display
|
||||
const formattedMoneyManagement = {
|
||||
...config.moneyManagement,
|
||||
stopLoss: config.moneyManagement.stopLoss * 100,
|
||||
takeProfit: config.moneyManagement.takeProfit * 100,
|
||||
};
|
||||
setGlobalCustomMoneyManagement(formattedMoneyManagement);
|
||||
setSelectedMoneyManagement('custom');
|
||||
}
|
||||
|
||||
// Handle risk management
|
||||
if (config.riskManagement) {
|
||||
setValue('useCustomRiskManagement', true);
|
||||
setValue('riskManagement', config.riskManagement);
|
||||
}
|
||||
|
||||
// Bot-specific fields
|
||||
if (mode !== 'backtest') {
|
||||
if (config.name) setValue('name', config.name);
|
||||
if (config.isForWatchingOnly !== undefined) setValue('isForWatchingOnly', config.isForWatchingOnly);
|
||||
if (config.flipPosition !== undefined) setValue('flipPosition', config.flipPosition);
|
||||
}
|
||||
|
||||
new Toast('Configuration imported successfully!', false);
|
||||
setShowJsonImport(false);
|
||||
setJsonConfig('');
|
||||
} catch (error) {
|
||||
setImportError('Invalid JSON format. Please check your configuration.');
|
||||
console.error('JSON import error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const onMoneyManagementChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
if (e.target.value === 'custom') {
|
||||
setShowCustomMoneyManagement(true);
|
||||
@@ -632,21 +713,9 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
const backtest = await backtestClient.backtest_Run(request);
|
||||
|
||||
t.update('success', `${ticker} Backtest Succeeded`);
|
||||
if (setBacktests) {
|
||||
setBacktests((arr) => [...arr, backtest]);
|
||||
}
|
||||
addBacktest(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,
|
||||
};
|
||||
await runBacktest(form, ticker, nextCount);
|
||||
}
|
||||
} catch (err: any) {
|
||||
t.update('error', 'Error: ' + err);
|
||||
throw err;
|
||||
@@ -682,6 +751,81 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
</FormInput>
|
||||
)}
|
||||
|
||||
{/* JSON Import Section */}
|
||||
<div className="divider">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline btn-sm normal-case"
|
||||
onClick={() => setShowJsonImport(!showJsonImport)}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4 mr-2"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10" />
|
||||
</svg>
|
||||
Import Configuration from JSON
|
||||
<svg
|
||||
className={`w-4 h-4 ml-2 transition-transform duration-200 ${showJsonImport ? 'rotate-180' : ''}`}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showJsonImport && (
|
||||
<div className="space-y-4 border border-accent rounded-lg p-4 bg-base-100">
|
||||
<div className="space-y-2">
|
||||
<label className="label">
|
||||
<span className="label-text">Paste Configuration JSON</span>
|
||||
</label>
|
||||
<textarea
|
||||
className="textarea textarea-bordered w-full h-32"
|
||||
placeholder='Paste your configuration JSON here...'
|
||||
value={jsonConfig}
|
||||
onChange={(e) => setJsonConfig(e.target.value)}
|
||||
/>
|
||||
{importError && (
|
||||
<div className="alert alert-error">
|
||||
<svg className="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span>{importError}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={handleImportJsonConfig}
|
||||
disabled={!jsonConfig.trim()}
|
||||
>
|
||||
Import Configuration
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-ghost btn-sm"
|
||||
onClick={() => {
|
||||
setJsonConfig('');
|
||||
setImportError('');
|
||||
setShowJsonImport(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-sm text-base-content/70">
|
||||
<p>Copy a configuration from the backtest table and paste it here to quickly load all settings.</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* First Row: Account & Timeframe */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormInput label="Account" htmlFor="accountName">
|
||||
@@ -938,28 +1082,7 @@ const UnifiedTradingModal: React.FC<UnifiedTradingModalProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Loop Slider (if enabled for backtests) */}
|
||||
{showLoopSlider && mode === 'backtest' && (
|
||||
<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: React.ChangeEvent<HTMLInputElement>) => setLoopQuantity(Number(e.target.value))}
|
||||
/>
|
||||
</FormInput>
|
||||
)}
|
||||
|
||||
|
||||
{/* Max Loss Streak & Max Position Time */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
|
||||
@@ -36,7 +36,6 @@ export interface UnifiedTradingModalProps {
|
||||
showModal: boolean
|
||||
closeModal: () => void
|
||||
mode: 'backtest' | 'createBot' | 'updateBot'
|
||||
showLoopSlider?: boolean
|
||||
|
||||
// For backtests
|
||||
setBacktests?: React.Dispatch<React.SetStateAction<Backtest[]>>
|
||||
|
||||
@@ -86,7 +86,15 @@ export type BacktestModalProps = {
|
||||
showModal: boolean
|
||||
closeModal: () => void
|
||||
setBacktests: React.Dispatch<React.SetStateAction<Backtest[]>>
|
||||
showLoopSlider?: boolean
|
||||
}
|
||||
|
||||
export type UnifiedTradingModalProps = {
|
||||
showModal: boolean
|
||||
closeModal: () => void
|
||||
mode: 'backtest' | 'createBot' | 'updateBot'
|
||||
setBacktests?: React.Dispatch<React.SetStateAction<Backtest[]>>
|
||||
backtest?: Backtest
|
||||
existingBot?: any // TradingBotResponse or whatever the bot type is
|
||||
}
|
||||
|
||||
export type ISpotlightBadge = {
|
||||
@@ -104,7 +112,6 @@ export type IBacktestsFormInput = {
|
||||
save: boolean
|
||||
balance: number
|
||||
moneyManagement: MoneyManagement
|
||||
loop: number
|
||||
startDate: string
|
||||
endDate: string
|
||||
cooldownPeriod: number
|
||||
@@ -122,7 +129,7 @@ export type IBacktestsFormInput = {
|
||||
export type IBacktestCards = {
|
||||
list: Backtest[] | undefined
|
||||
isFetching?: boolean
|
||||
setBacktests: React.Dispatch<React.SetStateAction<Backtest[]>>
|
||||
setBacktests?: React.Dispatch<React.SetStateAction<Backtest[]>>
|
||||
}
|
||||
|
||||
export type IFormInput = {
|
||||
|
||||
@@ -2,12 +2,10 @@ import React, {useState} from 'react'
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
import {Tabs} from '../../components/mollecules'
|
||||
import type {TabsType} from '../../global/type'
|
||||
|
||||
import BacktestLoop from './backtestLoop'
|
||||
import BacktestPlayground from './backtestPlayground'
|
||||
import BacktestScanner from './backtestScanner'
|
||||
import BacktestUpload from './backtestUpload'
|
||||
import type {TabsType} from '../../global/type.tsx'
|
||||
|
||||
// Tabs Array
|
||||
const tabs: TabsType = [
|
||||
@@ -21,14 +19,9 @@ const tabs: TabsType = [
|
||||
index: 2,
|
||||
label: 'Scanner',
|
||||
},
|
||||
{
|
||||
Component: BacktestLoop,
|
||||
index: 3,
|
||||
label: 'Loop',
|
||||
},
|
||||
{
|
||||
Component: BacktestUpload,
|
||||
index: 4,
|
||||
index: 3,
|
||||
label: 'Upload',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import React, {useState} from 'react'
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
|
||||
import useBacktestStore from '../../app/store/backtestStore'
|
||||
import {BacktestCards, UnifiedTradingModal} from '../../components/organism'
|
||||
import type {Backtest} from '../../generated/ManagingApi'
|
||||
|
||||
const BacktestPlayground: React.FC = () => {
|
||||
const [backtestingResult, setBacktest] = useState<Backtest[]>([])
|
||||
|
||||
const { backtests: backtestingResult } = useBacktestStore()
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
|
||||
function openModal() {
|
||||
@@ -22,12 +21,11 @@ const BacktestPlayground: React.FC = () => {
|
||||
<button className="btn" onClick={openModal}>
|
||||
Run New Backtest
|
||||
</button>
|
||||
<BacktestCards list={backtestingResult} setBacktests={setBacktest} />
|
||||
<BacktestCards list={backtestingResult} />
|
||||
<UnifiedTradingModal
|
||||
mode="backtest"
|
||||
showModal={showModal}
|
||||
closeModal={closeModal}
|
||||
setBacktests={setBacktest}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -4,17 +4,17 @@ import React, {useEffect, useState} from 'react'
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import useBacktestStore from '../../app/store/backtestStore'
|
||||
import {Loader} from '../../components/atoms'
|
||||
import {Modal, Toast} from '../../components/mollecules'
|
||||
import {BacktestTable, UnifiedTradingModal} from '../../components/organism'
|
||||
import type {Backtest} from '../../generated/ManagingApi'
|
||||
import {BacktestClient} from '../../generated/ManagingApi'
|
||||
|
||||
const BacktestScanner: React.FC = () => {
|
||||
const [backtestingResult, setBacktest] = useState<Backtest[]>([])
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [showModalRemoveBacktest, setShowModalRemoveBacktest] = useState(false)
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const { backtests: backtestingResult, setBacktests, setLoading } = useBacktestStore()
|
||||
const client = new BacktestClient({}, apiUrl)
|
||||
|
||||
const { isLoading, refetch, data: backtests } = useQuery({
|
||||
@@ -24,9 +24,13 @@ const BacktestScanner: React.FC = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (backtests) {
|
||||
setBacktest(backtests)
|
||||
setBacktests(backtests)
|
||||
}
|
||||
}, [backtests])
|
||||
}, [backtests, setBacktests])
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(isLoading)
|
||||
}, [isLoading, setLoading])
|
||||
|
||||
const openModalRemoveBacktests = () => {
|
||||
setShowModalRemoveBacktest(true)
|
||||
@@ -99,13 +103,12 @@ const BacktestScanner: React.FC = () => {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<BacktestTable list={backtestingResult} isFetching={isLoading} setBacktests={setBacktest} />
|
||||
<BacktestTable list={backtestingResult} isFetching={isLoading} />
|
||||
|
||||
<UnifiedTradingModal
|
||||
mode="backtest"
|
||||
showModal={showModal}
|
||||
closeModal={closeModal}
|
||||
setBacktests={setBacktest}
|
||||
/>
|
||||
|
||||
{/****************************/}
|
||||
|
||||
Reference in New Issue
Block a user