Add synthApi (#27)

* Add synthApi

* Put confidence for Synth proba

* Update the code

* Update readme

* Fix bootstraping

* fix github build

* Update the endpoints for scenario

* Add scenario and update backtest modal

* Update bot modal

* Update interfaces for synth

* add synth to backtest

* Add Kelly criterion and better signal

* Update signal confidence

* update doc

* save leaderboard and prediction

* Update nswag to generate ApiClient in the correct path

* Unify the trading modal

* Save miner and prediction

* Update messaging and block new signal until position not close when flipping off

* Rename strategies to indicators

* Update doc

* Update chart + add signal name

* Fix signal direction

* Update docker webui

* remove crypto npm

* Clean
This commit is contained in:
Oda
2025-07-03 00:13:42 +07:00
committed by GitHub
parent 453806356d
commit a547c4a040
103 changed files with 9916 additions and 810 deletions

View File

@@ -1,5 +1,5 @@
# Use an official Node.js image as the base
FROM node:18-alpine
FROM node:22.14.0-alpine
# Set the working directory in the container
WORKDIR /app
@@ -8,37 +8,38 @@ WORKDIR /app
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
# Install git and Python
#RUN apk update && apk add --no-cache git python3 make g++
RUN apk update && apk add --no-cache git python3 make g++
# Create a symlink for python3 as python
#RUN ln -sf /usr/bin/python3 /usr/bin/python
# Create a symlink for python3 as python
# This might not be strictly necessary for your current issue but good to keep if Python scripts are involved.
# RUN ln -sf /usr/bin/python3 /usr/bin/python
# Copy package.json and package-lock.json to the container
# COPY package*.json ./
COPY /src/Managing.WebApp/package.json ./
# Copy package.json and package-lock.json to the container
# COPY package*.json ./
COPY /src/Managing.WebApp/package.json ./
# Install dependencies with the --legacy-peer-deps flag to bypass peer dependency conflicts
RUN npm install --legacy-peer-deps
RUN npm install -g tailwindcss postcss autoprefixer @tailwindcss/typography
# Install dependencies with the --legacy-peer-deps flag to bypass peer dependency conflicts
RUN npm install --legacy-peer-deps --loglevel verbose
RUN npm install -g tailwindcss postcss autoprefixer @tailwindcss/typography
# Copy the rest of the app's source code to the container
# COPY . .
RUN ls -la
COPY src/Managing.WebApp/ .
RUN node --max-old-space-size=8192 ./node_modules/.bin/vite build
# Copy the rest of the app's source code to the container
# COPY . .
RUN ls -la
COPY src/Managing.WebApp/ .
RUN node --max-old-space-size=8192 ./node_modules/.bin/vite build
# Build the app
RUN npm run build
# Build the app
RUN npm run build
# Use NGINX as the web server
FROM nginx:alpine
# Use NGINX as the web server
FROM nginx:alpine
# Copy the built app to the NGINX web server directory
# COPY --from=0 /app/build /usr/share/nginx/html
COPY --from=0 /app/dist /usr/share/nginx/html
# Copy the built app to the NGINX web server directory
# COPY --from=0 /app/build /usr/share/nginx/html
COPY --from=0 /app/dist /usr/share/nginx/html
# Expose port 80 for the NGINX web server
EXPOSE 80
# Expose port 80 for the NGINX web server
EXPOSE 80
# Start the NGINX web server
# Start the NGINX web server
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -32,7 +32,6 @@
"canonicalize": "^2.0.0",
"classnames": "^2.3.1",
"connectkit": "^1.8.2",
"crypto": "^1.0.1",
"date-fns": "^2.30.0",
"elliptic": "^6.6.1",
"jotai": "^1.6.7",

View File

@@ -13,6 +13,7 @@ import {
Ticker,
Timeframe,
TradingBotConfig,
TradingBotConfigRequest,
UpdateBotConfigRequest
} from '../../../generated/ManagingApi'
import Toast from '../Toast/Toast'
@@ -58,6 +59,11 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
customStopLoss: number
customTakeProfit: number
customLeverage: number
// Synth API fields
useSynthApi: boolean
useForPositionSizing: boolean
useForSignalFiltering: boolean
useForDynamicStopLoss: boolean
}>({
name: '',
accountName: '',
@@ -77,9 +83,16 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
useCustomMoneyManagement: false,
customStopLoss: 0.01,
customTakeProfit: 0.02,
customLeverage: 1
customLeverage: 1,
useSynthApi: false,
useForPositionSizing: true,
useForSignalFiltering: true,
useForDynamicStopLoss: true
})
// State for advanced parameters dropdown
const [showAdvancedParams, setShowAdvancedParams] = useState(false)
// Fetch data
const { data: accounts } = useQuery({
queryFn: async () => {
@@ -110,11 +123,11 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
if (mode === 'create' && backtest) {
// Initialize from backtest
setFormData({
name: `Bot-${backtest.config.scenarioName}-${new Date().toISOString().slice(0, 19).replace(/[-:]/g, '')}`,
name: `Bot-${backtest.config.scenarioName || 'Custom'}-${new Date().toISOString().slice(0, 19).replace(/[-:]/g, '')}`,
accountName: backtest.config.accountName,
moneyManagementName: moneyManagements?.[0]?.name || '',
ticker: backtest.config.ticker,
scenarioName: backtest.config.scenarioName,
scenarioName: backtest.config.scenarioName || '',
timeframe: backtest.config.timeframe,
isForWatchingOnly: false,
botTradingBalance: 1000,
@@ -128,7 +141,11 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
useCustomMoneyManagement: true, // Default to custom for backtests
customStopLoss: backtest.config.moneyManagement?.stopLoss || 0.01,
customTakeProfit: backtest.config.moneyManagement?.takeProfit || 0.02,
customLeverage: backtest.config.moneyManagement?.leverage || 1
customLeverage: backtest.config.moneyManagement?.leverage || 1,
useSynthApi: false,
useForPositionSizing: true,
useForSignalFiltering: true,
useForDynamicStopLoss: true
})
} else if (mode === 'update' && existingBot) {
// Initialize from existing bot
@@ -137,7 +154,7 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
accountName: existingBot.config.accountName,
moneyManagementName: existingBot.config.moneyManagement?.name || '',
ticker: existingBot.config.ticker,
scenarioName: existingBot.config.scenarioName,
scenarioName: existingBot.config.scenarioName || '',
timeframe: existingBot.config.timeframe,
isForWatchingOnly: existingBot.config.isForWatchingOnly,
botTradingBalance: existingBot.config.botTradingBalance,
@@ -151,7 +168,11 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
useCustomMoneyManagement: false,
customStopLoss: existingBot.config.moneyManagement?.stopLoss || 0.01,
customTakeProfit: existingBot.config.moneyManagement?.takeProfit || 0.02,
customLeverage: existingBot.config.moneyManagement?.leverage || 1
customLeverage: existingBot.config.moneyManagement?.leverage || 1,
useSynthApi: existingBot.config.useSynthApi || false,
useForPositionSizing: existingBot.config.useForPositionSizing || true,
useForSignalFiltering: existingBot.config.useForSignalFiltering || true,
useForDynamicStopLoss: existingBot.config.useForDynamicStopLoss || true
})
} else if (mode === 'create' && !backtest) {
// Initialize for new bot creation
@@ -174,7 +195,11 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
useCustomMoneyManagement: false,
customStopLoss: 0.01,
customTakeProfit: 0.02,
customLeverage: 1
customLeverage: 1,
useSynthApi: false,
useForPositionSizing: true,
useForSignalFiltering: true,
useForDynamicStopLoss: true
})
}
}, [mode, backtest, existingBot, accounts, moneyManagements, scenarios])
@@ -216,6 +241,17 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
}))
}
const handleSynthApiToggle = (enabled: boolean) => {
setFormData(prev => ({
...prev,
useSynthApi: enabled,
// Reset sub-options when main toggle is turned off
useForPositionSizing: enabled ? prev.useForPositionSizing : false,
useForSignalFiltering: enabled ? prev.useForSignalFiltering : false,
useForDynamicStopLoss: enabled ? prev.useForDynamicStopLoss : false
}))
}
const handleSubmit = async () => {
const t = new Toast(mode === 'create' ? 'Creating bot...' : 'Updating bot...')
const client = new BotClient({}, apiUrl)
@@ -249,30 +285,31 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
return
}
// Create TradingBotConfig (reused for both create and update)
const tradingBotConfig: TradingBotConfig = {
// Create TradingBotConfigRequest (instead of TradingBotConfig)
const tradingBotConfigRequest: TradingBotConfigRequest = {
accountName: formData.accountName,
ticker: formData.ticker,
scenarioName: formData.scenarioName,
scenarioName: formData.scenarioName || undefined,
timeframe: formData.timeframe,
botType: formData.botType,
isForWatchingOnly: formData.isForWatchingOnly,
isForBacktest: false,
cooldownPeriod: formData.cooldownPeriod,
maxLossStreak: formData.maxLossStreak,
maxPositionTimeHours: formData.maxPositionTimeHours,
flipOnlyWhenInProfit: formData.flipOnlyWhenInProfit,
flipPosition: formData.flipPosition,
name: formData.name,
botTradingBalance: formData.botTradingBalance,
moneyManagement: moneyManagement,
closeEarlyWhenProfitable: formData.closeEarlyWhenProfitable
closeEarlyWhenProfitable: formData.closeEarlyWhenProfitable,
useSynthApi: formData.useSynthApi,
useForPositionSizing: formData.useForPositionSizing,
useForSignalFiltering: formData.useForSignalFiltering,
useForDynamicStopLoss: formData.useForDynamicStopLoss
}
if (mode === 'create') {
// Create new bot
const request: StartBotRequest = {
config: tradingBotConfig,
config: tradingBotConfigRequest,
moneyManagementName: formData.useCustomMoneyManagement ? undefined : formData.moneyManagementName
}
@@ -282,7 +319,7 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
// Update existing bot
const request: UpdateBotConfigRequest = {
identifier: existingBot!.identifier,
config: tradingBotConfig,
config: tradingBotConfigRequest,
moneyManagementName: formData.useCustomMoneyManagement ? undefined : formData.moneyManagementName
}
@@ -373,7 +410,12 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
<div className="form-control">
<label className="label">
<span className="label-text">Timeframe</span>
<div className="flex items-center gap-2">
<span className="label-text">Timeframe</span>
<div className="tooltip tooltip-top" data-tip="Chart timeframe for analysis. Lower timeframes (1m, 5m) for scalping, higher timeframes (1h, 4h) for swing trading">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
</label>
<select
className="select select-bordered"
@@ -390,7 +432,12 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
<div className="form-control">
<label className="label">
<span className="label-text">Bot Type</span>
<div className="flex items-center gap-2">
<span className="label-text">Bot Type</span>
<div className="tooltip tooltip-top" data-tip="ScalpingBot: Quick trades based on short-term signals. FlippingBot: Position reversal strategy that flips between long/short">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
</label>
<select
className="select select-bordered"
@@ -408,7 +455,12 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
<div className="form-control">
<label className="label">
<span className="label-text">Trading Balance</span>
<div className="flex items-center gap-2">
<span className="label-text">Trading Balance</span>
<div className="tooltip tooltip-top" data-tip="Amount of capital allocated for this bot's trading activities. This determines maximum position sizes">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
</label>
<input
type="number"
@@ -420,51 +472,14 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Cooldown Period (candles)</span>
</label>
<input
type="number"
className="input input-bordered"
value={formData.cooldownPeriod}
onChange={(e) => handleInputChange('cooldownPeriod', parseInt(e.target.value))}
min="1"
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Max Loss Streak</span>
</label>
<input
type="number"
className="input input-bordered"
value={formData.maxLossStreak}
onChange={(e) => handleInputChange('maxLossStreak', parseInt(e.target.value))}
min="0"
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Max Position Time (hours)</span>
</label>
<input
type="number"
className="input input-bordered"
value={formData.maxPositionTimeHours || ''}
onChange={(e) => handleInputChange('maxPositionTimeHours', e.target.value ? parseFloat(e.target.value) : null)}
min="0.1"
step="0.1"
placeholder="Optional"
/>
</div>
{/* Checkboxes */}
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text">Watch Only Mode</span>
<div className="flex items-center gap-2">
<span className="label-text">Watch Only Mode</span>
<div className="tooltip tooltip-top" data-tip="Bot will analyze and generate signals but won't execute actual trades. Good for testing strategies">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
<input
type="checkbox"
className="checkbox"
@@ -473,51 +488,161 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
/>
</label>
</div>
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text">Flip Only When In Profit</span>
<input
type="checkbox"
className="checkbox"
checked={formData.flipOnlyWhenInProfit}
onChange={(e) => handleInputChange('flipOnlyWhenInProfit', e.target.checked)}
/>
</label>
</div>
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text">Enable Position Flipping</span>
<input
type="checkbox"
className="checkbox"
checked={formData.flipPosition}
onChange={(e) => handleInputChange('flipPosition', e.target.checked)}
/>
</label>
</div>
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text">Close Early When Profitable</span>
<input
type="checkbox"
className="checkbox"
checked={formData.closeEarlyWhenProfitable}
onChange={(e) => handleInputChange('closeEarlyWhenProfitable', e.target.checked)}
disabled={!formData.maxPositionTimeHours}
/>
</label>
</div>
</div>
{/* Advanced Parameters Dropdown */}
<div className="divider">
<button
type="button"
className="btn btn-outline btn-sm normal-case"
onClick={() => setShowAdvancedParams(!showAdvancedParams)}
>
<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="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Advanced Parameters
<svg
className={`w-4 h-4 ml-2 transition-transform duration-200 ${showAdvancedParams ? '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>
{showAdvancedParams && (
<div className="space-y-4 border border-primary rounded-lg p-4 bg-base-100">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="form-control">
<label className="label">
<div className="flex items-center gap-2">
<span className="label-text">Cooldown Period (candles)</span>
<div className="tooltip tooltip-top" data-tip="Number of candles to wait before allowing another trade after closing a position. Prevents overtrading and allows market to settle">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
</label>
<input
type="number"
className="input input-bordered"
value={formData.cooldownPeriod}
onChange={(e) => handleInputChange('cooldownPeriod', parseInt(e.target.value))}
min="1"
/>
</div>
<div className="form-control">
<label className="label">
<div className="flex items-center gap-2">
<span className="label-text">Max Loss Streak</span>
<div className="tooltip tooltip-top" data-tip="Maximum number of consecutive losing trades before stopping the bot. Set to 0 for no limit">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
</label>
<input
type="number"
className="input input-bordered"
value={formData.maxLossStreak}
onChange={(e) => handleInputChange('maxLossStreak', parseInt(e.target.value))}
min="0"
/>
</div>
<div className="form-control">
<label className="label">
<div className="flex items-center gap-2">
<span className="label-text">Max Position Time (hours)</span>
<div className="tooltip tooltip-top" data-tip="Maximum time to hold a position before force closing. Leave empty to disable time-based position closure">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
</label>
<input
type="number"
className="input input-bordered"
value={formData.maxPositionTimeHours || ''}
onChange={(e) => handleInputChange('maxPositionTimeHours', e.target.value ? parseFloat(e.target.value) : null)}
min="0.1"
step="0.1"
placeholder="Optional"
/>
</div>
<div className="form-control">
<label className="label cursor-pointer">
<div className="flex items-center gap-2">
<span className="label-text">Flip Only When In Profit</span>
<div className="tooltip tooltip-top" data-tip="If enabled, positions will only flip when current position is profitable. Helps avoid flipping during losing streaks">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
<input
type="checkbox"
className="checkbox"
checked={formData.flipOnlyWhenInProfit}
onChange={(e) => handleInputChange('flipOnlyWhenInProfit', e.target.checked)}
/>
</label>
</div>
<div className="form-control">
<label className="label cursor-pointer">
<div className="flex items-center gap-2">
<span className="label-text">Enable Position Flipping</span>
<div className="tooltip tooltip-top" data-tip="Allow the bot to flip between long and short positions based on signals. More aggressive trading strategy">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
<input
type="checkbox"
className="checkbox"
checked={formData.flipPosition}
onChange={(e) => handleInputChange('flipPosition', e.target.checked)}
/>
</label>
</div>
<div className="form-control">
<label className="label cursor-pointer">
<div className="flex items-center gap-2">
<span className="label-text">Close Early When Profitable</span>
<div className="tooltip tooltip-top" data-tip="If enabled, positions will close early when they become profitable. Conservative approach to lock in gains">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
<input
type="checkbox"
className="checkbox"
checked={formData.closeEarlyWhenProfitable}
onChange={(e) => handleInputChange('closeEarlyWhenProfitable', e.target.checked)}
disabled={!formData.maxPositionTimeHours}
/>
</label>
</div>
</div>
</div>
)}
{/* Money Management Section */}
<div className="divider">Money Management</div>
<div className="form-control">
<label className="label cursor-pointer">
<span className="label-text">Use Custom Money Management</span>
<div className="flex items-center gap-2">
<span className="label-text">Use Custom Money Management</span>
<div className="tooltip tooltip-top" data-tip="Create custom risk management settings instead of using saved presets. Allows fine-tuning stop loss, take profit, and leverage">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
<input
type="checkbox"
className="checkbox"
@@ -592,6 +717,84 @@ const BotConfigModal: React.FC<BotConfigModalProps> = ({
</div>
)}
{/* Synth API Section */}
<div className="divider">Synth API Configuration</div>
<div className="grid grid-cols-1 gap-4">
<div className="form-control">
<label className="label cursor-pointer">
<div className="flex items-center gap-2">
<span className="label-text">Enable Synth API</span>
<div className="tooltip tooltip-top" data-tip="Enable AI-powered probabilistic price forecasts and risk assessment using advanced machine learning models for enhanced trading decisions">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
<input
type="checkbox"
className="checkbox"
checked={formData.useSynthApi}
onChange={(e) => handleSynthApiToggle(e.target.checked)}
/>
</label>
</div>
</div>
{/* Show sub-options only when Synth API is enabled */}
{formData.useSynthApi && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="form-control">
<label className="label cursor-pointer">
<div className="flex items-center gap-2">
<span className="label-text">Use for Position Sizing</span>
<div className="tooltip tooltip-top" data-tip="Use Synth predictions for position sizing adjustments and risk assessment. Optimizes trade size based on confidence levels">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
<input
type="checkbox"
className="checkbox"
checked={formData.useForPositionSizing}
onChange={(e) => handleInputChange('useForPositionSizing', e.target.checked)}
/>
</label>
</div>
<div className="form-control">
<label className="label cursor-pointer">
<div className="flex items-center gap-2">
<span className="label-text">Use for Signal Filtering</span>
<div className="tooltip tooltip-top" data-tip="Use Synth predictions to filter trading signals. Only executes trades when AI confidence is high">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
<input
type="checkbox"
className="checkbox"
checked={formData.useForSignalFiltering}
onChange={(e) => handleInputChange('useForSignalFiltering', e.target.checked)}
/>
</label>
</div>
<div className="form-control">
<label className="label cursor-pointer">
<div className="flex items-center gap-2">
<span className="label-text">Use for Dynamic Stop Loss</span>
<div className="tooltip tooltip-top" data-tip="Use Synth predictions for dynamic stop-loss/take-profit adjustments. Adapts levels based on market conditions">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
<input
type="checkbox"
className="checkbox"
checked={formData.useForDynamicStopLoss}
onChange={(e) => handleInputChange('useForDynamicStopLoss', e.target.checked)}
/>
</label>
</div>
</div>
)}
{/* Validation Messages */}
{formData.closeEarlyWhenProfitable && !formData.maxPositionTimeHours && (
<div className="alert alert-warning mt-4">

View File

@@ -4,18 +4,18 @@ import {type SubmitHandler, useForm} from 'react-hook-form'
import useApiUrlStore from '../../../app/store/apiStore'
import {
AccountClient,
BacktestClient,
BotType,
DataClient,
MoneyManagement,
MoneyManagementClient,
RunBacktestRequest,
Scenario,
ScenarioClient,
Ticker,
Timeframe,
TradingBotConfig,
AccountClient,
BacktestClient,
BotType,
DataClient,
MoneyManagement,
MoneyManagementClient,
RunBacktestRequest,
Scenario,
ScenarioClient,
Ticker,
Timeframe,
TradingBotConfigRequest,
} from '../../../generated/ManagingApi'
import type {BacktestModalProps, IBacktestsFormInput,} from '../../../global/type'
import {Loader, Slider} from '../../atoms'
@@ -42,7 +42,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
const [startDate, setStartDate] = useState<string>(defaultStartDateString);
const [endDate, setEndDate] = useState<string>(defaultEndDateString);
const { register, handleSubmit, setValue } = useForm<IBacktestsFormInput>({
const { register, handleSubmit, setValue, watch } = useForm<IBacktestsFormInput>({
defaultValues: {
startDate: defaultStartDateString,
endDate: defaultEndDateString,
@@ -51,9 +51,30 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
maxPositionTimeHours: null, // Default to null (disabled)
flipOnlyWhenInProfit: true, // Default to true
balance: 10000, // Default balance
closeEarlyWhenProfitable: false // Default to false
closeEarlyWhenProfitable: false, // Default to false
// Synth API defaults
useSynthApi: false,
useForPositionSizing: true,
useForSignalFiltering: true,
useForDynamicStopLoss: true
}
});
// Watch the useSynthApi value to conditionally show/hide sub-options
const useSynthApi = watch('useSynthApi');
// Reset sub-options when main Synth API toggle is turned off
useEffect(() => {
if (!useSynthApi) {
setValue('useForPositionSizing', false);
setValue('useForSignalFiltering', false);
setValue('useForDynamicStopLoss', false);
}
}, [useSynthApi, setValue]);
// State for advanced parameters dropdown
const [showAdvancedParams, setShowAdvancedParams] = useState(false);
const [selectedAccount, setSelectedAccount] = useState<string>('')
const [selectedTimeframe, setSelectedTimeframe] = useState<Timeframe>(Timeframe.OneHour)
const [selectedLoopQuantity, setLoopQuantity] = React.useState<number>(
@@ -127,36 +148,48 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
console.log(customScenario)
try {
// Create the TradingBotConfig
const tradingBotConfig: TradingBotConfig = {
// Create the TradingBotConfigRequest (note the Request suffix)
const tradingBotConfigRequest: TradingBotConfigRequest = {
accountName: form.accountName,
ticker: ticker as Ticker,
scenarioName: customScenario ? undefined : scenarioName,
scenario: customScenario,
scenario: customScenario ? {
name: customScenario.name || 'Custom Scenario',
indicators: customScenario.indicators?.map(indicator => ({
name: indicator.name || 'Unnamed Indicator',
type: indicator.type!,
signalType: indicator.signalType!,
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: customScenario.loopbackPeriod
} : undefined,
timeframe: form.timeframe,
botType: form.botType,
isForWatchingOnly: false, // Always false for backtests
isForBacktest: true, // Always true for backtests
cooldownPeriod: form.cooldownPeriod || 1,
maxLossStreak: form.maxLossStreak || 0,
maxPositionTimeHours: form.maxPositionTimeHours || null,
flipOnlyWhenInProfit: form.flipOnlyWhenInProfit ?? true,
flipPosition: form.botType === BotType.FlippingBot, // Set based on bot type
name: `Backtest-${customScenario ? customScenario.name : scenarioName}-${ticker}-${new Date().toISOString()}`,
botTradingBalance: form.balance,
moneyManagement: customMoneyManagement || moneyManagements?.find(m => m.name === selectedMoneyManagement) || moneyManagements?.[0] || {
name: 'placeholder',
leverage: 1,
stopLoss: 0.01,
takeProfit: 0.02,
timeframe: form.timeframe
},
closeEarlyWhenProfitable: form.closeEarlyWhenProfitable ?? false
closeEarlyWhenProfitable: form.closeEarlyWhenProfitable ?? false,
useSynthApi: form.useSynthApi ?? false,
useForPositionSizing: form.useForPositionSizing ?? true,
useForSignalFiltering: form.useForSignalFiltering ?? true,
useForDynamicStopLoss: form.useForDynamicStopLoss ?? true
};
// Create the RunBacktestRequest
const request: RunBacktestRequest = {
config: tradingBotConfig,
config: tradingBotConfigRequest, // Use the request object
startDate: new Date(form.startDate),
endDate: new Date(form.endDate),
balance: form.balance,
@@ -199,7 +232,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
function onMoneyManagementChange(e: any) {
if (e.target.value === 'custom') {
setShowCustomMoneyManagement(true)
setCustomMoneyManagement(e.target.value)
setCustomMoneyManagement(undefined) // Reset custom money management when switching to custom mode
} else {
setShowCustomMoneyManagement(false)
setCustomMoneyManagement(undefined)
@@ -210,7 +243,7 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
function onScenarioChange(e: any) {
if (e.target.value === 'custom') {
setShowCustomScenario(true)
setCustomScenario(e.target.value)
setCustomScenario(undefined) // Reset custom scenario when switching to custom mode
} else {
setShowCustomScenario(false)
setCustomScenario(undefined)
@@ -232,6 +265,11 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
useEffect(() => {
if (scenarios && scenarios.length > 0 && scenarios[0].name) {
setValue('scenarioName', scenarios[0].name);
setShowCustomScenario(false); // Hide custom scenario when scenarios are available
} else if (scenarios && scenarios.length === 0) {
// No scenarios available, automatically show custom scenario creation
setShowCustomScenario(true);
setValue('scenarioName', ''); // Clear any selected scenario
}
}, [scenarios, setValue]);
@@ -263,6 +301,12 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
if (moneyManagements && moneyManagements.length > 0){
setSelectedMoneyManagement(moneyManagements[0].name)
setCustomMoneyManagement(undefined)
setShowCustomMoneyManagement(false) // Hide custom money management when options are available
} else if (moneyManagements && moneyManagements.length === 0) {
// No money management options available, automatically show custom money management
setShowCustomMoneyManagement(true)
setSelectedMoneyManagement(undefined)
setCustomMoneyManagement(undefined)
}
}, [moneyManagements])
@@ -336,13 +380,19 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
},
})}
>
{moneyManagements.map((item) => (
<option key={item.name} value={item.name}>
{item.name}
</option>
))}
{moneyManagements.length === 0 ? (
<option value="" disabled>No money management available - create a custom one below</option>
) : (
<>
{moneyManagements.map((item) => (
<option key={item.name} value={item.name}>
{item.name}
</option>
))}
</>
)}
<option key="custom" value="custom">
Custom
{moneyManagements.length === 0 ? 'Create Custom Money Management' : 'Custom'}
</option>
</select>
</FormInput>
@@ -405,18 +455,31 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
},
})}
>
<option value="" disabled>Select a scenario</option>
{scenarios.length === 0 ? (
<option value="" disabled>No scenarios available - create a custom one below</option>
) : (
<option value="" disabled>Select a scenario</option>
)}
{scenarios.map((item) => (
<option key={item.name || 'unnamed'} value={item.name || ''}>
{item.name || 'Unnamed Scenario'}
</option>
))}
<option key="custom" value="custom">
Custom
{scenarios.length === 0 ? 'Create Custom Scenario' : 'Custom'}
</option>
</select>
</FormInput>
{showCustomScenario && (
<div className="mt-6">
<CustomScenario
onCreateScenario={setCustomScenario}
showCustomScenario={showCustomScenario}
></CustomScenario>
</div>
)}
<FormInput label="Tickers" htmlFor="tickers">
<select
className="select select-bordered w-full"
@@ -433,17 +496,8 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
</FormInput>
</div>
{showCustomScenario && (
<div className="mt-6">
<CustomScenario
onCreateScenario={setCustomScenario}
showCustomScenario={showCustomScenario}
></CustomScenario>
</div>
)}
{/* Fourth Row: Balance & Cooldown Period */}
<div className="grid grid-cols-2 gap-4">
{/* Fourth Row: Balance */}
<div className="grid grid-cols-1 gap-4">
<FormInput label="Balance" htmlFor="balance">
<input
type="number"
@@ -455,125 +509,310 @@ const BacktestModal: React.FC<BacktestModalProps> = ({
}}
/>
</FormInput>
<FormInput label="Cooldown Period (candles)" htmlFor="cooldownPeriod">
<input
type="number"
className="input input-bordered w-full"
min="1"
step="1"
{...register('cooldownPeriod', { valueAsNumber: true })}
/>
</FormInput>
</div>
{/* Fifth Row: Max Loss Streak & Max Position Time */}
<div className="grid grid-cols-2 gap-4">
<FormInput label="Max Loss Streak" htmlFor="maxLossStreak">
<input
type="number"
className="input input-bordered w-full"
min="0"
step="1"
{...register('maxLossStreak', { valueAsNumber: true })}
/>
</FormInput>
{/* Advanced Parameters Dropdown */}
<div className="divider">
<button
type="button"
className="btn btn-outline btn-sm normal-case"
onClick={() => setShowAdvancedParams(!showAdvancedParams)}
>
<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="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Advanced Parameters
<svg
className={`w-4 h-4 ml-2 transition-transform duration-200 ${showAdvancedParams ? '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>
<FormInput label="Max Position Time (hours)" htmlFor="maxPositionTimeHours">
<input
type="number"
className="input input-bordered w-full"
min="0"
step="0.5"
placeholder="Leave empty to disable"
{...register('maxPositionTimeHours', { valueAsNumber: true })}
/>
<div className="text-xs text-gray-500 mt-1">
Leave empty to disable time-based position closure
{showAdvancedParams && (
<div className="space-y-4 border border-primary rounded-lg p-4 bg-base-100">
{/* Cooldown Period & Dates */}
<div className="grid grid-cols-1 gap-4">
<FormInput
label={
<div className="flex items-center gap-2">
Cooldown Period (candles)
<div className="tooltip tooltip-top" data-tip="Number of candles to wait before allowing another trade after closing a position. Prevents overtrading and allows market to settle">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
}
htmlFor="cooldownPeriod"
>
<input
type="number"
className="input input-bordered w-full"
min="1"
step="1"
{...register('cooldownPeriod', { valueAsNumber: true })}
/>
</FormInput>
</div>
</FormInput>
</div>
{/* Sixth Row: Flip Only When In Profit & Close Early When Profitable */}
<div className="grid grid-cols-2 gap-4">
<FormInput label="Flip Only When In Profit" htmlFor="flipOnlyWhenInProfit">
<input
type="checkbox"
className="toggle toggle-primary"
{...register('flipOnlyWhenInProfit')}
/>
<div className="text-xs text-gray-500 mt-1">
If enabled, positions will only flip when current position is profitable
{/* Start Date & End Date */}
<div className="grid grid-cols-2 gap-4">
<FormInput label="Start Date" htmlFor="startDate">
<input
type="date"
className="input input-bordered w-full"
value={startDate}
onChange={(e) => {
setStartDate(e.target.value);
setValue('startDate', e.target.value);
}}
/>
</FormInput>
<FormInput label="End Date" htmlFor="endDate">
<input
type="date"
className="input input-bordered w-full"
value={endDate}
onChange={(e) => {
setEndDate(e.target.value);
setValue('endDate', e.target.value);
}}
/>
</FormInput>
</div>
</FormInput>
<FormInput label="Close Early When Profitable" htmlFor="closeEarlyWhenProfitable">
<input
type="checkbox"
className="toggle toggle-primary"
{...register('closeEarlyWhenProfitable')}
/>
<div className="text-xs text-gray-500 mt-1">
If enabled, positions will close early when they become profitable
{/* 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
label={
<div className="flex items-center gap-2">
Max Loss Streak
<div className="tooltip tooltip-top" data-tip="Maximum number of consecutive losing trades before stopping the bot. Set to 0 for no limit">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
}
htmlFor="maxLossStreak"
>
<input
type="number"
className="input input-bordered w-full"
min="0"
step="1"
{...register('maxLossStreak', { valueAsNumber: true })}
/>
</FormInput>
<FormInput
label={
<div className="flex items-center gap-2">
Max Position Time (hours)
<div className="tooltip tooltip-top" data-tip="Maximum time to hold a position before force closing. Leave empty to disable time-based position closure">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
}
htmlFor="maxPositionTimeHours"
>
<input
type="number"
className="input input-bordered w-full"
min="0"
step="0.5"
placeholder="Leave empty to disable"
{...register('maxPositionTimeHours', { valueAsNumber: true })}
/>
</FormInput>
</div>
</FormInput>
</div>
{/* Seventh Row: Save */}
<div className="grid grid-cols-1 gap-4">
<FormInput label="Save" htmlFor="save">
<input
type="checkbox"
className="toggle toggle-primary"
{...register('save')}
/>
</FormInput>
</div>
{/* Flip Only When In Profit & Close Early When Profitable */}
<div className="grid grid-cols-2 gap-4">
<FormInput
label={
<div className="flex items-center gap-2">
Flip Only When In Profit
<div className="tooltip tooltip-top" data-tip="If enabled, positions will only flip when current position is profitable. Helps avoid flipping during losing streaks">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
}
htmlFor="flipOnlyWhenInProfit"
>
<input
type="checkbox"
className="toggle toggle-primary"
{...register('flipOnlyWhenInProfit')}
/>
</FormInput>
{/* Eighth Row: Start Date & End Date */}
<div className="grid grid-cols-2 gap-4">
<FormInput label="Start Date" htmlFor="startDate">
<input
type="date"
className="input input-bordered w-full"
value={startDate}
onChange={(e) => {
setStartDate(e.target.value);
setValue('startDate', e.target.value);
}}
/>
</FormInput>
<FormInput
label={
<div className="flex items-center gap-2">
Close Early When Profitable
<div className="tooltip tooltip-top" data-tip="If enabled, positions will close early when they become profitable. Conservative approach to lock in gains">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
}
htmlFor="closeEarlyWhenProfitable"
>
<input
type="checkbox"
className="toggle toggle-primary"
{...register('closeEarlyWhenProfitable')}
/>
</FormInput>
</div>
<FormInput label="End Date" htmlFor="endDate">
<input
type="date"
className="input input-bordered w-full"
value={endDate}
onChange={(e) => {
setEndDate(e.target.value);
setValue('endDate', e.target.value);
}}
/>
</FormInput>
</div>
{/* Save Option */}
<div className="grid grid-cols-1 gap-4">
<FormInput
label={
<div className="flex items-center gap-2">
Save Backtest Results
<div className="tooltip tooltip-top" data-tip="Save the backtest results to your account for future reference and analysis">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
}
htmlFor="save"
>
<input
type="checkbox"
className="toggle toggle-primary"
{...register('save')}
/>
</FormInput>
</div>
{/* Loop Slider (if enabled) */}
{showLoopSlider && (
<FormInput label="Loop" htmlFor="loop">
<Slider
id="loopSlider"
min="1"
max="10"
value={selectedLoopQuantity.toString()}
onChange={(e) => setLoopQuantity(Number(e.target.value))}
></Slider>
</FormInput>
{/* Synth API Section */}
<div className="divider">Synth API Configuration</div>
<div className="grid grid-cols-1 gap-4">
<FormInput
label={
<div className="flex items-center gap-2">
Enable Synth API
<div className="tooltip tooltip-top" data-tip="Enable AI-powered probabilistic price forecasts and risk assessment using advanced machine learning models for enhanced trading decisions">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
}
htmlFor="useSynthApi"
>
<input
type="checkbox"
className="toggle toggle-primary"
{...register('useSynthApi')}
/>
</FormInput>
</div>
{/* Show sub-options only when Synth API is enabled */}
{useSynthApi && (
<div className="grid grid-cols-1 gap-4">
<FormInput
label={
<div className="flex items-center gap-2">
Use for Position Sizing
<div className="tooltip tooltip-top" data-tip="Use Synth predictions for position sizing adjustments and risk assessment. Optimizes trade size based on confidence levels">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
}
htmlFor="useForPositionSizing"
>
<input
type="checkbox"
className="toggle toggle-primary"
{...register('useForPositionSizing')}
/>
</FormInput>
<FormInput
label={
<div className="flex items-center gap-2">
Use for Signal Filtering
<div className="tooltip tooltip-top" data-tip="Use Synth predictions to filter trading signals. Only executes trades when AI confidence is high">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
}
htmlFor="useForSignalFiltering"
>
<input
type="checkbox"
className="toggle toggle-primary"
{...register('useForSignalFiltering')}
/>
</FormInput>
<FormInput
label={
<div className="flex items-center gap-2">
Use for Dynamic Stop Loss
<div className="tooltip tooltip-top" data-tip="Use Synth predictions for dynamic stop-loss/take-profit adjustments. Adapts levels based on market conditions">
<span className="badge badge-info badge-xs">i</span>
</div>
</div>
}
htmlFor="useForDynamicStopLoss"
>
<input
type="checkbox"
className="toggle toggle-primary"
{...register('useForDynamicStopLoss')}
/>
</FormInput>
</div>
)}
</div>
)}
</div>
<div className="modal-action">
<button type="submit" className="btn">
Run
<button
type="button"
className="btn btn-ghost"
onClick={closeModal}
>
Cancel
</button>
<button type="submit" className="btn btn-primary">
Run Backtest
</button>
</div>
</Modal>

View File

@@ -4,21 +4,16 @@ import {CardPosition, CardText} from '../../mollecules'
interface IBacktestRowDetailsProps {
backtest: Backtest;
optimizedMoneyManagement: {
stopLoss: number;
takeProfit: number;
};
}
const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
backtest,
optimizedMoneyManagement
backtest
}) => {
const {
candles,
positions,
walletBalances,
strategiesValues,
indicatorsValues,
signals,
statistics,
config
@@ -364,7 +359,7 @@ const BacktestRowDetails: React.FC<IBacktestRowDetailsProps> = ({
candles={candles}
positions={positions}
walletBalances={walletBalances}
strategiesValues={strategiesValues}
indicatorsValues={indicatorsValues}
signals={signals}
></TradeChart>
</figure>

View File

@@ -1,4 +1,4 @@
import {ChevronDownIcon, ChevronRightIcon, PlayIcon, TrashIcon} from '@heroicons/react/solid'
import {ChevronDownIcon, ChevronRightIcon, CogIcon, PlayIcon, TrashIcon} from '@heroicons/react/solid'
import React, {useEffect, useState} from 'react'
import useApiUrlStore from '../../../app/store/apiStore'
@@ -6,7 +6,7 @@ import type {Backtest} from '../../../generated/ManagingApi'
import {BacktestClient} from '../../../generated/ManagingApi'
import type {IBacktestCards} from '../../../global/type'
import {CardText, SelectColumnFilter, Table} from '../../mollecules'
import BotConfigModal from '../../mollecules/BotConfigModal/BotConfigModal'
import {UnifiedTradingModal} from '../index'
import Toast from '../../mollecules/Toast/Toast'
import BacktestRowDetails from './backtestRowDetails'
@@ -32,6 +32,10 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
const [showBotConfigModal, setShowBotConfigModal] = useState(false)
const [selectedBacktest, setSelectedBacktest] = useState<Backtest | null>(null)
// Backtest configuration modal state
const [showBacktestConfigModal, setShowBacktestConfigModal] = useState(false)
const [selectedBacktestForRerun, setSelectedBacktestForRerun] = useState<Backtest | null>(null)
const handleOpenBotConfigModal = (backtest: Backtest) => {
setSelectedBacktest(backtest)
setShowBotConfigModal(true)
@@ -42,6 +46,16 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
setSelectedBacktest(null)
}
const handleOpenBacktestConfigModal = (backtest: Backtest) => {
setSelectedBacktestForRerun(backtest)
setShowBacktestConfigModal(true)
}
const handleCloseBacktestConfigModal = () => {
setShowBacktestConfigModal(false)
setSelectedBacktestForRerun(null)
}
async function deleteBacktest(id: string) {
const t = new Toast('Deleting backtest')
const client = new BacktestClient({}, apiUrl)
@@ -213,6 +227,23 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
accessor: 'id',
disableFilters: true,
},
{
Cell: ({ cell }: any) => (
<>
<div className="tooltip" data-tip="Re-run 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>
</button>
</div>
</>
),
Header: '',
accessor: 'rerun',
disableFilters: true,
},
{
Cell: ({ cell }: any) => (
<>
@@ -429,18 +460,29 @@ const BacktestTable: React.FC<IBacktestCards> = ({ list, isFetching, setBacktest
renderRowSubCompontent={({ row }: any) => (
<BacktestRowDetails
backtest={row.original}
optimizedMoneyManagement={optimizedMoneyManagement}
/>
)}
/>
{/* Bot Configuration Modal */}
{selectedBacktest && (
<BotConfigModal
<UnifiedTradingModal
showModal={showBotConfigModal}
mode="create"
mode="createBot"
backtest={selectedBacktest}
onClose={handleCloseBotConfigModal}
closeModal={handleCloseBotConfigModal}
/>
)}
{/* Backtest Configuration Modal */}
{selectedBacktestForRerun && (
<UnifiedTradingModal
showModal={showBacktestConfigModal}
mode="backtest"
backtest={selectedBacktestForRerun}
closeModal={handleCloseBacktestConfigModal}
setBacktests={setBacktests}
showLoopSlider={true}
/>
)}
</>

View File

@@ -16,11 +16,11 @@ import {useEffect, useRef, useState} from 'react'
import type {
Candle,
IndicatorsResultBase,
IndicatorType,
KeyValuePairOfDateTimeAndDecimal,
Position,
Signal,
StrategiesResultBase,
StrategyType,
} from '../../../../generated/ManagingApi'
import {PositionStatus, TradeDirection,} from '../../../../generated/ManagingApi'
import useTheme from '../../../../hooks/useTheme'
@@ -45,7 +45,7 @@ type ITradeChartProps = {
positions: Position[]
signals: Signal[]
walletBalances?: KeyValuePairOfDateTimeAndDecimal[] | null
strategiesValues?: { [key in keyof typeof StrategyType]?: StrategiesResultBase; } | null;
indicatorsValues?: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; } | null;
stream?: Candle | null
width: number
height: number
@@ -56,7 +56,7 @@ const TradeChart = ({
positions,
signals,
walletBalances,
strategiesValues,
indicatorsValues,
stream,
width,
height,
@@ -246,9 +246,6 @@ const TradeChart = ({
const data: CandlestickData[] = candles.map((c) => mapCandle(c))
let diff = 0; // Default to 0 if there's not enough data to calculate the difference
console.log(data)
console.log(data.length)
if (data.length > 3) {
diff =
(data[data.length - 1].time as number) -
@@ -308,7 +305,7 @@ const TradeChart = ({
}
// Price panel
if (strategiesValues?.EmaTrend != null || strategiesValues?.EmaCross != null)
if (indicatorsValues?.EmaTrend != null || indicatorsValues?.EmaCross != null)
{
const emaSeries = chart.current.addLineSeries({
color: theme.secondary,
@@ -323,7 +320,7 @@ const TradeChart = ({
title: 'EMA',
})
const ema = strategiesValues.EmaTrend?.ema ?? strategiesValues.EmaCross?.ema
const ema = indicatorsValues.EmaTrend?.ema ?? indicatorsValues.EmaCross?.ema
const emaData = ema?.map((w) => {
return {
@@ -339,7 +336,7 @@ const TradeChart = ({
}
}
if (strategiesValues?.SuperTrend != null) {
if (indicatorsValues?.SuperTrend != null) {
const superTrendSeries = chart.current.addLineSeries({
color: theme.info,
lineWidth: 1,
@@ -351,7 +348,7 @@ const TradeChart = ({
})
const superTrend = strategiesValues.SuperTrend.superTrend?.map((w) => {
const superTrend = indicatorsValues.SuperTrend.superTrend?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.superTrend,
@@ -361,6 +358,46 @@ const TradeChart = ({
superTrendSeries.setData(superTrend)
}
// Display chandeliers exits
if (indicatorsValues?.ChandelierExit != null) {
const chandelierExitsLongsSeries = chart.current.addLineSeries({
color: theme.info,
lineWidth: 1,
priceLineVisible: false,
priceLineWidth: 1,
title: 'Chandelier Long',
pane: 0,
})
const chandelierExitsLongs = indicatorsValues.ChandelierExit.chandelierLong?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.chandelierExit,
}
})
// @ts-ignore
chandelierExitsLongsSeries.setData(chandelierExitsLongs)
const chandelierExitsShortsSeries = chart.current.addLineSeries({
color: theme.error,
lineWidth: 1,
priceLineVisible: false,
priceLineWidth: 1,
title: 'Chandelier Short',
pane: 0,
})
const chandelierExitsShorts = indicatorsValues.ChandelierExit.chandelierShort?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.chandelierExit,
}
})
// @ts-ignore
chandelierExitsShortsSeries.setData(chandelierExitsShorts)
}
if (markers.length > 0) {
series1.current.setMarkers(markers)
}
@@ -369,14 +406,14 @@ const TradeChart = ({
var paneCount = 1
if (strategiesValues?.RsiDivergence != null || strategiesValues?.RsiDivergenceConfirm != null)
if (indicatorsValues?.RsiDivergence != null || indicatorsValues?.RsiDivergenceConfirm != null)
{
const rsiSeries = chart.current.addLineSeries({
pane: paneCount,
title: 'RSI',
})
const rsi = strategiesValues.RsiDivergence?.rsi ?? strategiesValues.RsiDivergenceConfirm?.rsi
const rsi = indicatorsValues.RsiDivergence?.rsi ?? indicatorsValues.RsiDivergenceConfirm?.rsi
const rsiData = rsi?.map((w) => {
return {
@@ -397,7 +434,7 @@ const TradeChart = ({
paneCount++
}
if (strategiesValues?.Stc != null) {
if (indicatorsValues?.Stc != null) {
const stcSeries = chart.current.addBaselineSeries({
pane: paneCount,
baseValue: {price: 50, type: 'price'},
@@ -408,7 +445,7 @@ const TradeChart = ({
stcSeries.createPriceLine(buildLine(theme.error, 25, 'low'))
stcSeries.createPriceLine(buildLine(theme.info, 75, 'high'))
const stcData = strategiesValues?.Stc.stc?.map((w) => {
const stcData = indicatorsValues?.Stc.stc?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.stc,
@@ -430,7 +467,42 @@ const TradeChart = ({
paneCount++
}
if (strategiesValues?.MacdCross != null) {
if (indicatorsValues?.LaggingStc != null) {
const laggingStcSeries = chart.current.addBaselineSeries({
pane: paneCount,
baseValue: {price: 50, type: 'price'},
title: 'Lagging STC',
})
laggingStcSeries.createPriceLine(buildLine(theme.error, 25, 'low'))
laggingStcSeries.createPriceLine(buildLine(theme.info, 75, 'high'))
const stcData = indicatorsValues?.LaggingStc.stc?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.stc,
}
})
// @ts-ignore
laggingStcSeries.setData(stcData)
laggingStcSeries.applyOptions({
...baselineOptions,
priceLineVisible: true,
priceFormat: {
minMove: 1,
precision: 1,
type: 'price',
},
crosshairMarkerVisible: true,
})
paneCount++
}
console.log(indicatorsValues)
if (indicatorsValues?.MacdCross != null) {
console.log(indicatorsValues.MacdCross)
const histogramSeries = chart.current.addHistogramSeries({
color: theme.accent,
title: 'MACD',
@@ -441,7 +513,7 @@ const TradeChart = ({
}
})
const macd = strategiesValues.MacdCross.macd?.map((w) => {
const macd = indicatorsValues.MacdCross.macd?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.histogram,
@@ -472,7 +544,7 @@ const TradeChart = ({
crosshairMarkerVisible: true,
})
const macdData = strategiesValues.MacdCross.macd?.map((w) => {
const macdData = indicatorsValues.MacdCross.macd?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.macd,
@@ -497,7 +569,7 @@ const TradeChart = ({
},
})
const signalData = strategiesValues.MacdCross.macd?.map((w) => {
const signalData = indicatorsValues.MacdCross.macd?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.signal,
@@ -510,7 +582,7 @@ const TradeChart = ({
paneCount++
}
if (strategiesValues?.StochRsiTrend){
if (indicatorsValues?.StochRsiTrend){
const stochRsiSeries = chart.current.addLineSeries({
...baselineOptions,
priceLineVisible: false,
@@ -518,7 +590,7 @@ const TradeChart = ({
pane: paneCount,
})
const stochRsi = strategiesValues.StochRsiTrend.stochRsi?.map((w) => {
const stochRsi = indicatorsValues.StochRsiTrend.stochRsi?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.stochRsi,
@@ -529,7 +601,7 @@ const TradeChart = ({
paneCount++
}
if (strategiesValues?.StDev != null) {
if (indicatorsValues?.StDev != null) {
const stDevSeries = chart.current.addLineSeries({
color: theme.primary,
lineWidth: 1,
@@ -539,7 +611,7 @@ const TradeChart = ({
pane: paneCount,
})
const stDev = strategiesValues.StDev.stdDev?.map((w) => {
const stDev = indicatorsValues.StDev.stdDev?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.stdDev,
@@ -557,7 +629,7 @@ const TradeChart = ({
crosshairMarkerVisible: true,
})
const zScore = strategiesValues.StDev.stdDev?.map((w) => {
const zScore = indicatorsValues.StDev.stdDev?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.zScore,
@@ -569,6 +641,47 @@ const TradeChart = ({
paneCount++
}
// Display dual EMA crossover
if (indicatorsValues?.DualEmaCross != null) {
const fastEmaSeries = chart.current.addLineSeries({
color: theme.info,
lineWidth: 1,
priceLineVisible: false,
priceLineWidth: 1,
title: 'Fast EMA',
pane: paneCount,
})
const fastEma = indicatorsValues.DualEmaCross.fastEma?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.ema,
}
})
// @ts-ignore
fastEmaSeries.setData(fastEma)
const slowEmaSeries = chart.current.addLineSeries({
color: theme.primary,
lineWidth: 1,
priceLineVisible: false,
priceLineWidth: 1,
title: 'Slow EMA',
pane: paneCount,
})
const slowEma = indicatorsValues.DualEmaCross.slowEma?.map((w) => {
return {
time: moment(w.date).unix(),
value: w.ema,
}
})
// @ts-ignore
slowEmaSeries.setData(slowEma)
paneCount++
}
if (walletBalances != null && walletBalances.length > 0) {
const walletSeries = chart.current.addBaselineSeries({
baseValue: {price: walletBalances[0].value, type: 'price'},

View File

@@ -0,0 +1,124 @@
# UnifiedTradingModal
A unified modal component that replaces both `BacktestModal` and `BotConfigModal`. This component handles three different modes:
- **backtest**: Run backtests with multiple tickers
- **createBot**: Create a new trading bot (optionally from a backtest)
- **updateBot**: Update an existing bot's configuration
## Features
### ✅ **Unified Interface**
- Single component for all trading configuration needs
- Mode-specific form fields and validation
- Consistent UI/UX across all use cases
### ✅ **Advanced Configuration**
- **Advanced Parameters**: Collapsible section with cooldown periods, position limits, trading options
- **Risk Management**: Complete `RiskManagement` configuration with preset levels (Conservative, Moderate, Aggressive)
- **Synth API Integration**: AI-powered probabilistic forecasts and risk assessment
### ✅ **Smart Defaults**
- Context-aware initialization based on mode
- Automatic data loading and form population
- Preset risk management configurations
## Usage Examples
### 1. Backtest Mode
```tsx
import { UnifiedTradingModal } from '../../components/organism'
<UnifiedTradingModal
showModal={showModal}
closeModal={() => setShowModal(false)}
mode="backtest"
setBacktests={setBacktests}
showLoopSlider={true} // Optional: enable loop optimization
/>
```
### 2. Create Bot Mode
```tsx
<UnifiedTradingModal
showModal={showModal}
closeModal={() => setShowModal(false)}
mode="createBot"
/>
```
### 3. Create Bot from Backtest
```tsx
<UnifiedTradingModal
showModal={showModal}
closeModal={() => setShowModal(false)}
mode="createBot"
backtest={selectedBacktest} // Initialize from backtest
/>
```
### 4. Update Bot Mode
```tsx
<UnifiedTradingModal
showModal={showModal}
closeModal={() => setShowModal(false)}
mode="updateBot"
existingBot={{
identifier: bot.identifier,
config: bot.config
}}
/>
```
## Migration Guide
### From BacktestModal
```tsx
// Old
<BacktestModal
showModal={showModal}
closeModal={closeModal}
setBacktests={setBacktests}
showLoopSlider={true}
/>
// New
<UnifiedTradingModal
mode="backtest"
showModal={showModal}
closeModal={closeModal}
setBacktests={setBacktests}
showLoopSlider={true}
/>
```
### From BotConfigModal
```tsx
// Old
<BotConfigModal
showModal={showModal}
mode="create"
backtest={backtest}
onClose={closeModal}
/>
// New
<UnifiedTradingModal
mode="createBot"
showModal={showModal}
closeModal={closeModal}
backtest={backtest}
/>
```
## Risk Management Features
The component includes a comprehensive Risk Management section with:
- **Preset Levels**: Conservative, Moderate, Aggressive configurations
- **Kelly Criterion**: Position sizing based on edge and odds
- **Expected Utility**: Risk-adjusted decision making
- **Probability Thresholds**: Adverse and favorable signal validation
- **Liquidation Risk**: Maximum acceptable liquidation probability
All risk management parameters use the official `RiskManagement` type from the API and integrate seamlessly with the backend trading logic.

View File

@@ -0,0 +1 @@
export { default } from './UnifiedTradingModal'

View File

@@ -2,8 +2,12 @@ export { default as TradeChart } from './Trading/TradeChart/TradeChart'
export { default as CardPositionItem } from './Trading/CardPositionItem'
export { default as ActiveBots } from './ActiveBots/ActiveBots'
export { default as BacktestCards } from './Backtest/backtestCards'
export { default as BacktestModal } from './Backtest/backtestModal'
export { default as BacktestTable } from './Backtest/backtestTable'
// @deprecated - Use UnifiedTradingModal instead
export { default as BacktestModal } from './Backtest/backtestModal'
export { default as UnifiedTradingModal } from './UnifiedTradingModal'
export { default as CustomMoneyManagement } from './CustomMoneyManagement/CustomMoneyManagement'
export { default as CustomScenario } from './CustomScenario/CustomScenario'
export { default as SpotLightBadge } from './SpotLightBadge/SpotLightBadge'
export { default as StatusBadge } from './StatusBadge/StatusBadge'
export { default as PositionsList } from './Positions/PositionList'

View File

@@ -337,6 +337,45 @@ export class BacktestClient extends AuthorizedApiBase {
return Promise.resolve<FileResponse>(null as any);
}
backtest_Map(moneyManagementRequest: MoneyManagementRequest): Promise<MoneyManagement> {
let url_ = this.baseUrl + "/Backtest";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(moneyManagementRequest);
let options_: RequestInit = {
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processBacktest_Map(_response);
});
}
protected processBacktest_Map(response: Response): Promise<MoneyManagement> {
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 MoneyManagement;
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<MoneyManagement>(null as any);
}
backtest_Backtest(id: string): Promise<Backtest> {
let url_ = this.baseUrl + "/Backtest/{id}";
if (id === undefined || id === null)
@@ -726,6 +765,45 @@ export class BotClient extends AuthorizedApiBase {
return Promise.resolve<TradingBotResponse[]>(null as any);
}
bot_Map(moneyManagementRequest: MoneyManagementRequest): Promise<MoneyManagement> {
let url_ = this.baseUrl + "/Bot";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(moneyManagementRequest);
let options_: RequestInit = {
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processBot_Map(_response);
});
}
protected processBot_Map(response: Response): Promise<MoneyManagement> {
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 MoneyManagement;
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<MoneyManagement>(null as any);
}
bot_OpenPositionManually(request: OpenPositionManuallyRequest): Promise<Position> {
let url_ = this.baseUrl + "/Bot/OpenPosition";
url_ = url_.replace(/[?&]$/, "");
@@ -1432,7 +1510,7 @@ export class ScenarioClient extends AuthorizedApiBase {
this.baseUrl = baseUrl ?? "http://localhost:5000";
}
scenario_GetScenarios(): Promise<Scenario[]> {
scenario_GetScenarios(): Promise<ScenarioViewModel[]> {
let url_ = this.baseUrl + "/Scenario";
url_ = url_.replace(/[?&]$/, "");
@@ -1450,13 +1528,13 @@ export class ScenarioClient extends AuthorizedApiBase {
});
}
protected processScenario_GetScenarios(response: Response): Promise<Scenario[]> {
protected processScenario_GetScenarios(response: Response): Promise<ScenarioViewModel[]> {
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 Scenario[];
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as ScenarioViewModel[];
return result200;
});
} else if (status !== 200 && status !== 204) {
@@ -1464,10 +1542,10 @@ export class ScenarioClient extends AuthorizedApiBase {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<Scenario[]>(null as any);
return Promise.resolve<ScenarioViewModel[]>(null as any);
}
scenario_CreateScenario(name: string | null | undefined, loopbackPeriod: number | null | undefined, strategies: string[]): Promise<Scenario> {
scenario_CreateScenario(name: string | null | undefined, loopbackPeriod: number | null | undefined, strategies: string[]): Promise<ScenarioViewModel> {
let url_ = this.baseUrl + "/Scenario?";
if (name !== undefined && name !== null)
url_ += "name=" + encodeURIComponent("" + name) + "&";
@@ -1493,13 +1571,13 @@ export class ScenarioClient extends AuthorizedApiBase {
});
}
protected processScenario_CreateScenario(response: Response): Promise<Scenario> {
protected processScenario_CreateScenario(response: Response): Promise<ScenarioViewModel> {
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 Scenario;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as ScenarioViewModel;
return result200;
});
} else if (status !== 200 && status !== 204) {
@@ -1507,7 +1585,7 @@ export class ScenarioClient extends AuthorizedApiBase {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<Scenario>(null as any);
return Promise.resolve<ScenarioViewModel>(null as any);
}
scenario_DeleteScenario(name: string | null | undefined): Promise<FileResponse> {
@@ -1600,7 +1678,7 @@ export class ScenarioClient extends AuthorizedApiBase {
return Promise.resolve<FileResponse>(null as any);
}
scenario_GetIndicators(): Promise<Indicator[]> {
scenario_GetIndicators(): Promise<IndicatorViewModel[]> {
let url_ = this.baseUrl + "/Scenario/indicator";
url_ = url_.replace(/[?&]$/, "");
@@ -1618,13 +1696,13 @@ export class ScenarioClient extends AuthorizedApiBase {
});
}
protected processScenario_GetIndicators(response: Response): Promise<Indicator[]> {
protected processScenario_GetIndicators(response: Response): Promise<IndicatorViewModel[]> {
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 Indicator[];
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as IndicatorViewModel[];
return result200;
});
} else if (status !== 200 && status !== 204) {
@@ -1632,10 +1710,10 @@ export class ScenarioClient extends AuthorizedApiBase {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<Indicator[]>(null as any);
return Promise.resolve<IndicatorViewModel[]>(null as any);
}
scenario_CreateIndicator(indicatorType: IndicatorType | undefined, name: string | null | undefined, period: number | null | undefined, fastPeriods: number | null | undefined, slowPeriods: number | null | undefined, signalPeriods: number | null | undefined, multiplier: number | null | undefined, stochPeriods: number | null | undefined, smoothPeriods: number | null | undefined, cyclePeriods: number | null | undefined): Promise<Indicator> {
scenario_CreateIndicator(indicatorType: IndicatorType | undefined, name: string | null | undefined, period: number | null | undefined, fastPeriods: number | null | undefined, slowPeriods: number | null | undefined, signalPeriods: number | null | undefined, multiplier: number | null | undefined, stochPeriods: number | null | undefined, smoothPeriods: number | null | undefined, cyclePeriods: number | null | undefined): Promise<IndicatorViewModel> {
let url_ = this.baseUrl + "/Scenario/indicator?";
if (indicatorType === null)
throw new Error("The parameter 'indicatorType' cannot be null.");
@@ -1675,13 +1753,13 @@ export class ScenarioClient extends AuthorizedApiBase {
});
}
protected processScenario_CreateIndicator(response: Response): Promise<Indicator> {
protected processScenario_CreateIndicator(response: Response): Promise<IndicatorViewModel> {
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 Indicator;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as IndicatorViewModel;
return result200;
});
} else if (status !== 200 && status !== 204) {
@@ -1689,7 +1767,7 @@ export class ScenarioClient extends AuthorizedApiBase {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<Indicator>(null as any);
return Promise.resolve<IndicatorViewModel>(null as any);
}
scenario_DeleteIndicator(name: string | null | undefined): Promise<FileResponse> {
@@ -2779,7 +2857,7 @@ export interface Backtest {
walletBalances: KeyValuePairOfDateTimeAndDecimal[];
optimizedMoneyManagement: MoneyManagement;
user: User;
strategiesValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
score: number;
}
@@ -2796,11 +2874,16 @@ export interface TradingBotConfig {
maxLossStreak: number;
flipPosition: boolean;
name: string;
riskManagement?: RiskManagement | null;
scenario?: Scenario | null;
scenarioName?: string | null;
maxPositionTimeHours?: number | null;
closeEarlyWhenProfitable?: boolean;
flipOnlyWhenInProfit: boolean;
useSynthApi?: boolean;
useForPositionSizing?: boolean;
useForSignalFiltering?: boolean;
useForDynamicStopLoss?: boolean;
}
export interface MoneyManagement {
@@ -2937,6 +3020,29 @@ export enum BotType {
FlippingBot = "FlippingBot",
}
export interface RiskManagement {
adverseProbabilityThreshold: number;
favorableProbabilityThreshold: number;
riskAversion: number;
kellyMinimumThreshold: number;
kellyMaximumCap: number;
maxLiquidationProbability: number;
signalValidationTimeHorizonHours: number;
positionMonitoringTimeHorizonHours: number;
positionWarningThreshold: number;
positionAutoCloseThreshold: number;
kellyFractionalMultiplier: number;
riskTolerance: RiskToleranceLevel;
useExpectedUtility: boolean;
useKellyCriterion: boolean;
}
export enum RiskToleranceLevel {
Conservative = "Conservative",
Moderate = "Moderate",
Aggressive = "Aggressive",
}
export interface Scenario {
name?: string | null;
indicators?: Indicator[] | null;
@@ -3086,6 +3192,7 @@ export interface Signal extends ValueObject {
indicatorType: IndicatorType;
signalType: SignalType;
user?: User | null;
indicatorName: string;
}
export enum SignalStatus {
@@ -3226,19 +3333,68 @@ export interface SuperTrendResult extends ResultBase {
}
export interface RunBacktestRequest {
config?: TradingBotConfig | null;
config?: TradingBotConfigRequest | null;
startDate?: Date;
endDate?: Date;
balance?: number;
watchOnly?: boolean;
save?: boolean;
}
export interface TradingBotConfigRequest {
accountName: string;
ticker: Ticker;
timeframe: Timeframe;
isForWatchingOnly: boolean;
botTradingBalance: number;
botType: BotType;
name: string;
cooldownPeriod: number;
maxLossStreak: number;
scenario?: ScenarioRequest | null;
scenarioName?: string | null;
moneyManagementName?: string | null;
moneyManagement?: MoneyManagement | null;
moneyManagement?: MoneyManagementRequest | null;
maxPositionTimeHours?: number | null;
closeEarlyWhenProfitable?: boolean;
flipOnlyWhenInProfit?: boolean;
useSynthApi?: boolean;
useForPositionSizing?: boolean;
useForSignalFiltering?: boolean;
useForDynamicStopLoss?: boolean;
}
export interface ScenarioRequest {
name: string;
indicators: IndicatorRequest[];
loopbackPeriod?: number | null;
}
export interface IndicatorRequest {
name: string;
type: IndicatorType;
signalType: SignalType;
minimumHistory?: number;
period?: number | null;
fastPeriods?: number | null;
slowPeriods?: number | null;
signalPeriods?: number | null;
multiplier?: number | null;
smoothPeriods?: number | null;
stochPeriods?: number | null;
cyclePeriods?: number | null;
}
export interface MoneyManagementRequest {
name: string;
timeframe: Timeframe;
stopLoss: number;
takeProfit: number;
leverage: number;
}
export interface StartBotRequest {
config?: TradingBotConfig | null;
moneyManagementName?: string | null;
config?: TradingBotConfigRequest | null;
}
export interface TradingBotResponse {
@@ -3265,8 +3421,9 @@ export interface ClosePositionRequest {
export interface UpdateBotConfigRequest {
identifier: string;
config: TradingBotConfig;
config: TradingBotConfigRequest;
moneyManagementName?: string | null;
moneyManagement?: MoneyManagement | null;
}
export interface TickerInfos {
@@ -3372,6 +3529,29 @@ export interface BestAgentsResponse {
totalPages?: number;
}
export interface ScenarioViewModel {
name: string;
indicators: IndicatorViewModel[];
loopbackPeriod?: number | null;
userName: string;
}
export interface IndicatorViewModel {
name: string;
type: IndicatorType;
signalType: SignalType;
minimumHistory: number;
period?: number | null;
fastPeriods?: number | null;
slowPeriods?: number | null;
signalPeriods?: number | null;
multiplier?: number | null;
smoothPeriods?: number | null;
stochPeriods?: number | null;
cyclePeriods?: number | null;
userName: string;
}
export enum RiskLevel {
Low = "Low",
Medium = "Medium",

View File

@@ -0,0 +1,887 @@
//----------------------
// <auto-generated>
// Generated using the NSwag toolchain v14.3.0.0 (NJsonSchema v11.2.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
/* tslint:disable */
/* eslint-disable */
// ReSharper disable InconsistentNaming
export interface Account {
name: string;
exchange: TradingExchanges;
type: AccountType;
key?: string | null;
secret?: string | null;
user?: User | null;
balances?: Balance[] | null;
isPrivyWallet?: boolean;
}
export enum TradingExchanges {
Binance = "Binance",
Kraken = "Kraken",
Ftx = "Ftx",
Evm = "Evm",
GmxV2 = "GmxV2",
}
export enum AccountType {
Cex = "Cex",
Trader = "Trader",
Watch = "Watch",
Auth = "Auth",
Privy = "Privy",
}
export interface User {
name?: string | null;
accounts?: Account[] | null;
agentName?: string | null;
avatarUrl?: string | null;
telegramChannel?: string | null;
}
export interface Balance {
tokenImage?: string | null;
tokenName?: string | null;
amount?: number;
price?: number;
value?: number;
tokenAdress?: string | null;
chain?: Chain | null;
}
export interface Chain {
id?: string | null;
rpcUrl?: string | null;
name?: string | null;
chainId?: number;
}
export interface GmxClaimableSummary {
claimableFundingFees?: FundingFeesData | null;
claimableUiFees?: UiFeesData | null;
rebateStats?: RebateStatsData | null;
}
export interface FundingFeesData {
totalUsdc?: number;
}
export interface UiFeesData {
totalUsdc?: number;
}
export interface RebateStatsData {
totalRebateUsdc?: number;
discountUsdc?: number;
rebateFactor?: number;
discountFactor?: number;
}
export interface Backtest {
id: string;
finalPnl: number;
winRate: number;
growthPercentage: number;
hodlPercentage: number;
config: TradingBotConfig;
positions: Position[];
signals: Signal[];
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;
}
export interface TradingBotConfig {
accountName: string;
moneyManagement: MoneyManagement;
ticker: Ticker;
timeframe: Timeframe;
isForWatchingOnly: boolean;
botTradingBalance: number;
botType: BotType;
isForBacktest: boolean;
cooldownPeriod: number;
maxLossStreak: number;
flipPosition: boolean;
name: string;
riskManagement?: RiskManagement | null;
scenario?: Scenario | null;
scenarioName?: string | null;
maxPositionTimeHours?: number | null;
closeEarlyWhenProfitable?: boolean;
flipOnlyWhenInProfit: boolean;
useSynthApi?: boolean;
useForPositionSizing?: boolean;
useForSignalFiltering?: boolean;
useForDynamicStopLoss?: boolean;
}
export interface MoneyManagement {
name: string;
timeframe: Timeframe;
stopLoss: number;
takeProfit: number;
leverage: number;
user?: User | null;
}
export enum Timeframe {
FiveMinutes = "FiveMinutes",
FifteenMinutes = "FifteenMinutes",
ThirtyMinutes = "ThirtyMinutes",
OneHour = "OneHour",
FourHour = "FourHour",
OneDay = "OneDay",
OneMinute = "OneMinute",
}
export enum Ticker {
AAVE = "AAVE",
ADA = "ADA",
APE = "APE",
ALGO = "ALGO",
ARB = "ARB",
ATOM = "ATOM",
AVAX = "AVAX",
BNB = "BNB",
BTC = "BTC",
BAL = "BAL",
CHZ = "CHZ",
COMP = "COMP",
CRO = "CRO",
CRV = "CRV",
DOGE = "DOGE",
DOT = "DOT",
DYDX = "DYDX",
ENS = "ENS",
ETC = "ETC",
ETH = "ETH",
FIL = "FIL",
FLM = "FLM",
FTM = "FTM",
GALA = "GALA",
GMX = "GMX",
GRT = "GRT",
IMX = "IMX",
JASMY = "JASMY",
KSM = "KSM",
LDO = "LDO",
LINK = "LINK",
LRC = "LRC",
LTC = "LTC",
MANA = "MANA",
MATIC = "MATIC",
MKR = "MKR",
NEAR = "NEAR",
OP = "OP",
PEPE = "PEPE",
QTUM = "QTUM",
REN = "REN",
ROSE = "ROSE",
RSR = "RSR",
RUNE = "RUNE",
SAND = "SAND",
SOL = "SOL",
SRM = "SRM",
SUSHI = "SUSHI",
THETA = "THETA",
UNI = "UNI",
USDC = "USDC",
USDT = "USDT",
WIF = "WIF",
XMR = "XMR",
XRP = "XRP",
XTZ = "XTZ",
SHIB = "SHIB",
STX = "STX",
ORDI = "ORDI",
APT = "APT",
BOME = "BOME",
MEME = "MEME",
FLOKI = "FLOKI",
MEW = "MEW",
TAO = "TAO",
BONK = "BONK",
WLD = "WLD",
TBTC = "tBTC",
WBTC_b = "WBTC_b",
EIGEN = "EIGEN",
SUI = "SUI",
SEI = "SEI",
USDC_e = "USDC_e",
DAI = "DAI",
TIA = "TIA",
TRX = "TRX",
TON = "TON",
PENDLE = "PENDLE",
WstETH = "wstETH",
USDe = "USDe",
SATS = "SATS",
POL = "POL",
XLM = "XLM",
BCH = "BCH",
ICP = "ICP",
RENDER = "RENDER",
INJ = "INJ",
TRUMP = "TRUMP",
MELANIA = "MELANIA",
ENA = "ENA",
FARTCOIN = "FARTCOIN",
AI16Z = "AI16Z",
ANIME = "ANIME",
BERA = "BERA",
VIRTUAL = "VIRTUAL",
PENGU = "PENGU",
ONDO = "ONDO",
FET = "FET",
AIXBT = "AIXBT",
CAKE = "CAKE",
S = "S",
JUP = "JUP",
HYPE = "HYPE",
OM = "OM",
DOLO = "DOLO",
Unknown = "Unknown",
}
export enum BotType {
SimpleBot = "SimpleBot",
ScalpingBot = "ScalpingBot",
FlippingBot = "FlippingBot",
}
export interface RiskManagement {
adverseProbabilityThreshold: number;
favorableProbabilityThreshold: number;
riskAversion: number;
kellyMinimumThreshold: number;
kellyMaximumCap: number;
maxLiquidationProbability: number;
signalValidationTimeHorizonHours: number;
positionMonitoringTimeHorizonHours: number;
positionWarningThreshold: number;
positionAutoCloseThreshold: number;
kellyFractionalMultiplier: number;
riskTolerance: RiskToleranceLevel;
useExpectedUtility: boolean;
useKellyCriterion: boolean;
}
export enum RiskToleranceLevel {
Conservative = "Conservative",
Moderate = "Moderate",
Aggressive = "Aggressive",
}
export interface Scenario {
name?: string | null;
indicators?: Indicator[] | null;
loopbackPeriod?: number | null;
user?: User | null;
}
export interface Indicator {
name?: string | null;
type?: IndicatorType;
signalType?: SignalType;
minimumHistory?: number;
period?: number | null;
fastPeriods?: number | null;
slowPeriods?: number | null;
signalPeriods?: number | null;
multiplier?: number | null;
smoothPeriods?: number | null;
stochPeriods?: number | null;
cyclePeriods?: number | null;
user?: User | null;
}
export enum IndicatorType {
RsiDivergence = "RsiDivergence",
RsiDivergenceConfirm = "RsiDivergenceConfirm",
MacdCross = "MacdCross",
EmaCross = "EmaCross",
ThreeWhiteSoldiers = "ThreeWhiteSoldiers",
SuperTrend = "SuperTrend",
ChandelierExit = "ChandelierExit",
EmaTrend = "EmaTrend",
Composite = "Composite",
StochRsiTrend = "StochRsiTrend",
Stc = "Stc",
StDev = "StDev",
LaggingStc = "LaggingStc",
SuperTrendCrossEma = "SuperTrendCrossEma",
DualEmaCross = "DualEmaCross",
}
export enum SignalType {
Signal = "Signal",
Trend = "Trend",
Context = "Context",
}
export interface Position {
accountName: string;
date: Date;
originDirection: TradeDirection;
ticker: Ticker;
moneyManagement: MoneyManagement;
open: Trade;
stopLoss: Trade;
takeProfit1: Trade;
takeProfit2?: Trade | null;
profitAndLoss?: ProfitAndLoss | null;
status: PositionStatus;
signalIdentifier?: string | null;
identifier: string;
initiator: PositionInitiator;
user: User;
}
export enum TradeDirection {
None = "None",
Short = "Short",
Long = "Long",
}
export interface Trade {
fee?: number;
date: Date;
direction: TradeDirection;
status: TradeStatus;
tradeType: TradeType;
ticker: Ticker;
quantity: number;
price: number;
leverage?: number;
exchangeOrderId: string;
message?: string | null;
}
export enum TradeStatus {
PendingOpen = "PendingOpen",
Requested = "Requested",
Cancelled = "Cancelled",
Filled = "Filled",
}
export enum TradeType {
Limit = "Limit",
Market = "Market",
StopMarket = "StopMarket",
StopLimit = "StopLimit",
StopLoss = "StopLoss",
TakeProfit = "TakeProfit",
StopLossProfit = "StopLossProfit",
StopLossProfitLimit = "StopLossProfitLimit",
StopLossLimit = "StopLossLimit",
TakeProfitLimit = "TakeProfitLimit",
TrailingStop = "TrailingStop",
TrailingStopLimit = "TrailingStopLimit",
StopLossAndLimit = "StopLossAndLimit",
SettlePosition = "SettlePosition",
}
export interface ProfitAndLoss {
realized?: number;
net?: number;
averageOpenPrice?: number;
}
export enum PositionStatus {
New = "New",
Canceled = "Canceled",
Rejected = "Rejected",
Updating = "Updating",
PartiallyFilled = "PartiallyFilled",
Filled = "Filled",
Flipped = "Flipped",
Finished = "Finished",
}
export enum PositionInitiator {
PaperTrading = "PaperTrading",
Bot = "Bot",
User = "User",
CopyTrading = "CopyTrading",
}
export interface ValueObject {
}
export interface Signal extends ValueObject {
status: SignalStatus;
direction: TradeDirection;
confidence: Confidence;
timeframe: Timeframe;
date: Date;
candle: Candle;
identifier: string;
ticker: Ticker;
exchange: TradingExchanges;
indicatorType: IndicatorType;
signalType: SignalType;
user?: User | null;
indicatorName: string;
}
export enum SignalStatus {
WaitingForPosition = "WaitingForPosition",
PositionOpen = "PositionOpen",
Expired = "Expired",
}
export enum Confidence {
Low = "Low",
Medium = "Medium",
High = "High",
None = "None",
}
export interface Candle {
exchange: TradingExchanges;
ticker: string;
openTime: Date;
date: Date;
open: number;
close: number;
volume?: number;
high: number;
low: number;
baseVolume?: number;
quoteVolume?: number;
tradeCount?: number;
takerBuyBaseVolume?: number;
takerBuyQuoteVolume?: number;
timeframe: Timeframe;
}
export interface PerformanceMetrics {
count?: number;
sharpeRatio?: number;
maxDrawdown?: number;
maxDrawdownPc?: number;
maxDrawdownRecoveryTime?: string;
winningTrades?: number;
loosingTrades?: number;
totalPnL?: number;
}
export interface KeyValuePairOfDateTimeAndDecimal {
key?: Date;
value?: number;
}
export interface IndicatorsResultBase {
ema?: EmaResult[] | null;
fastEma?: EmaResult[] | null;
slowEma?: EmaResult[] | null;
macd?: MacdResult[] | null;
rsi?: RsiResult[] | null;
stoch?: StochResult[] | null;
stochRsi?: StochRsiResult[] | null;
bollingerBands?: BollingerBandsResult[] | null;
chandelierShort?: ChandelierResult[] | null;
stc?: StcResult[] | null;
stdDev?: StdDevResult[] | null;
superTrend?: SuperTrendResult[] | null;
chandelierLong?: ChandelierResult[] | null;
}
export interface ResultBase {
date?: Date;
}
export interface EmaResult extends ResultBase {
ema?: number | null;
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
}
export interface MacdResult extends ResultBase {
macd?: number | null;
signal?: number | null;
histogram?: number | null;
fastEma?: number | null;
slowEma?: number | null;
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
}
export interface RsiResult extends ResultBase {
rsi?: number | null;
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
}
/** Stochastic indicator results includes aliases for those who prefer the simpler K,D,J outputs. See documentation for more information. */
export interface StochResult extends ResultBase {
oscillator?: number | null;
signal?: number | null;
percentJ?: number | null;
k?: number | null;
d?: number | null;
j?: number | null;
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
}
export interface StochRsiResult extends ResultBase {
stochRsi?: number | null;
signal?: number | null;
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
}
export interface BollingerBandsResult extends ResultBase {
sma?: number | null;
upperBand?: number | null;
lowerBand?: number | null;
percentB?: number | null;
zScore?: number | null;
width?: number | null;
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
}
export interface ChandelierResult extends ResultBase {
chandelierExit?: number | null;
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
}
export interface StcResult extends ResultBase {
stc?: number | null;
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
}
export interface StdDevResult extends ResultBase {
stdDev?: number | null;
mean?: number | null;
zScore?: number | null;
stdDevSma?: number | null;
"skender.Stock.Indicators.IReusableResult.Value"?: number | null;
}
export interface SuperTrendResult extends ResultBase {
superTrend?: number | null;
upperBand?: number | null;
lowerBand?: number | null;
}
export interface RunBacktestRequest {
config?: TradingBotConfigRequest | null;
startDate?: Date;
endDate?: Date;
balance?: number;
watchOnly?: boolean;
save?: boolean;
}
export interface TradingBotConfigRequest {
accountName: string;
ticker: Ticker;
timeframe: Timeframe;
isForWatchingOnly: boolean;
botTradingBalance: number;
botType: BotType;
name: string;
cooldownPeriod: number;
maxLossStreak: number;
scenario?: ScenarioRequest | null;
scenarioName?: string | null;
moneyManagementName?: string | null;
moneyManagement?: MoneyManagementRequest | null;
maxPositionTimeHours?: number | null;
closeEarlyWhenProfitable?: boolean;
flipOnlyWhenInProfit?: boolean;
useSynthApi?: boolean;
useForPositionSizing?: boolean;
useForSignalFiltering?: boolean;
useForDynamicStopLoss?: boolean;
}
export interface ScenarioRequest {
name: string;
indicators: IndicatorRequest[];
loopbackPeriod?: number | null;
}
export interface IndicatorRequest {
name: string;
type: IndicatorType;
signalType: SignalType;
minimumHistory?: number;
period?: number | null;
fastPeriods?: number | null;
slowPeriods?: number | null;
signalPeriods?: number | null;
multiplier?: number | null;
smoothPeriods?: number | null;
stochPeriods?: number | null;
cyclePeriods?: number | null;
}
export interface MoneyManagementRequest {
name: string;
timeframe: Timeframe;
stopLoss: number;
takeProfit: number;
leverage: number;
}
export interface StartBotRequest {
config?: TradingBotConfigRequest | null;
}
export interface TradingBotResponse {
status: string;
signals: Signal[];
positions: Position[];
candles: Candle[];
winRate: number;
profitAndLoss: number;
identifier: string;
agentName: string;
config: TradingBotConfig;
}
export interface OpenPositionManuallyRequest {
identifier?: string | null;
direction?: TradeDirection;
}
export interface ClosePositionRequest {
identifier?: string | null;
positionId?: string | null;
}
export interface UpdateBotConfigRequest {
identifier: string;
config: TradingBotConfigRequest;
moneyManagementName?: string | null;
moneyManagement?: MoneyManagement | null;
}
export interface TickerInfos {
ticker?: Ticker;
imageUrl?: string | null;
}
export interface SpotlightOverview {
spotlights: Spotlight[];
dateTime: Date;
identifier?: string;
scenarioCount?: number;
}
export interface Spotlight {
scenario: Scenario;
tickerSignals: TickerSignal[];
}
export interface TickerSignal {
ticker: Ticker;
fiveMinutes: Signal[];
fifteenMinutes: Signal[];
oneHour: Signal[];
fourHour: Signal[];
oneDay: Signal[];
}
export interface StrategiesStatisticsViewModel {
totalStrategiesRunning?: number;
changeInLast24Hours?: number;
}
export interface TopStrategiesViewModel {
topStrategies?: StrategyPerformance[] | null;
}
export interface StrategyPerformance {
strategyName?: string | null;
pnL?: number;
}
export interface UserStrategyDetailsViewModel {
name?: string | null;
state?: string | null;
pnL?: number;
roiPercentage?: number;
roiLast24H?: number;
runtime?: Date;
winRate?: number;
totalVolumeTraded?: number;
volumeLast24H?: number;
wins?: number;
losses?: number;
positions?: Position[] | null;
identifier?: string | null;
scenarioName?: string | null;
}
export interface PlatformSummaryViewModel {
totalAgents?: number;
totalActiveStrategies?: number;
totalPlatformPnL?: number;
totalPlatformVolume?: number;
totalPlatformVolumeLast24h?: number;
agentSummaries?: AgentSummaryViewModel[] | null;
timeFilter?: string | null;
}
export interface AgentSummaryViewModel {
agentName?: string | null;
totalPnL?: number;
pnLLast24h?: number;
totalROI?: number;
roiLast24h?: number;
wins?: number;
losses?: number;
averageWinRate?: number;
activeStrategiesCount?: number;
totalVolume?: number;
volumeLast24h?: number;
}
export interface AgentBalanceHistory {
agentName?: string | null;
agentBalances?: AgentBalance[] | null;
}
export interface AgentBalance {
agentName?: string | null;
totalValue?: number;
totalAccountUsdValue?: number;
botsAllocationUsdValue?: number;
pnL?: number;
time?: Date;
}
export interface BestAgentsResponse {
agents?: AgentBalanceHistory[] | null;
totalCount?: number;
currentPage?: number;
pageSize?: number;
totalPages?: number;
}
export interface ScenarioViewModel {
name: string;
indicators: IndicatorViewModel[];
loopbackPeriod?: number | null;
userName: string;
}
export interface IndicatorViewModel {
name: string;
type: IndicatorType;
signalType: SignalType;
minimumHistory: number;
period?: number | null;
fastPeriods?: number | null;
slowPeriods?: number | null;
signalPeriods?: number | null;
multiplier?: number | null;
smoothPeriods?: number | null;
stochPeriods?: number | null;
cyclePeriods?: number | null;
userName: string;
}
export enum RiskLevel {
Low = "Low",
Medium = "Medium",
High = "High",
Adaptive = "Adaptive",
}
export interface PrivyInitAddressResponse {
success?: boolean;
usdcHash?: string | null;
orderVaultHash?: string | null;
exchangeRouterHash?: string | null;
error?: string | null;
}
export interface LoginRequest {
name: string;
address: string;
signature: string;
message: string;
}
export interface Workflow {
name: string;
usage: WorkflowUsage;
flows: IFlow[];
description: string;
}
export enum WorkflowUsage {
Trading = "Trading",
Task = "Task",
}
export interface IFlow {
id: string;
name: string;
type: FlowType;
description: string;
acceptedInputs: FlowOutput[];
children?: IFlow[] | null;
parameters: FlowParameter[];
parentId?: string;
output?: string | null;
outputTypes: FlowOutput[];
}
export enum FlowType {
RsiDivergence = "RsiDivergence",
FeedTicker = "FeedTicker",
OpenPosition = "OpenPosition",
}
export enum FlowOutput {
Signal = "Signal",
Candles = "Candles",
Position = "Position",
MoneyManagement = "MoneyManagement",
}
export interface FlowParameter {
value?: any | null;
name?: string | null;
}
export interface SyntheticWorkflow {
name: string;
usage: WorkflowUsage;
description: string;
flows: SyntheticFlow[];
}
export interface SyntheticFlow {
id: string;
parentId?: string | null;
type: FlowType;
parameters: SyntheticFlowParameter[];
}
export interface SyntheticFlowParameter {
value: string;
name: string;
}
export interface FileResponse {
data: Blob;
status: number;
fileName?: string;
headers?: { [name: string]: any };
}

View File

@@ -0,0 +1,50 @@
// Import types from ManagingApi
import type {Backtest, RiskManagement, TradingBotConfig} from '../generated/ManagingApi'
// Import the existing IBacktestsFormInput from the correct file
import type {IBacktestsFormInput} from './type.tsx'
export interface IRiskManagementInput {
adverseProbabilityThreshold: number
favorableProbabilityThreshold: number
riskAversion: number
kellyMinimumThreshold: number
kellyMaximumCap: number
maxLiquidationProbability: number
signalValidationTimeHorizonHours: number
positionMonitoringTimeHorizonHours: number
positionWarningThreshold: number
positionAutoCloseThreshold: number
kellyFractionalMultiplier: number
riskTolerance: 'Conservative' | 'Moderate' | 'Aggressive'
useExpectedUtility: boolean
useKellyCriterion: boolean
}
export interface IUnifiedTradingConfigInput extends IBacktestsFormInput {
// Bot-specific fields
name?: string
isForWatchingOnly?: boolean
flipPosition?: boolean
// Risk Management fields
riskManagement?: RiskManagement
useCustomRiskManagement?: boolean
}
export interface UnifiedTradingModalProps {
showModal: boolean
closeModal: () => void
mode: 'backtest' | 'createBot' | 'updateBot'
showLoopSlider?: boolean
// For backtests
setBacktests?: React.Dispatch<React.SetStateAction<Backtest[]>>
// For bot creation/update
backtest?: Backtest // Backtest object when creating from backtest
existingBot?: {
identifier: string
config: TradingBotConfig // TradingBotConfig from API
}
}

View File

@@ -10,11 +10,11 @@ import type {
FlowOutput,
FlowType,
IFlow,
Indicator,
IndicatorViewModel,
MoneyManagement,
Position,
RiskLevel,
Scenario,
ScenarioViewModel,
Signal,
Ticker,
Timeframe,
@@ -112,6 +112,11 @@ export type IBacktestsFormInput = {
maxPositionTimeHours?: number | null
flipOnlyWhenInProfit?: boolean
closeEarlyWhenProfitable?: boolean
// Synth API fields
useSynthApi?: boolean
useForPositionSizing?: boolean
useForSignalFiltering?: boolean
useForDynamicStopLoss?: boolean
}
export type IBacktestCards = {
@@ -123,7 +128,7 @@ export type IBacktestCards = {
export type IFormInput = {
children: React.ReactNode
htmlFor: string
label: string
label: React.ReactNode
inline?: boolean
}
@@ -177,9 +182,9 @@ export type IScenarioFormInput = {
loopbackPeriod: number | undefined
}
export type IScenarioList = {
list: Scenario[]
indicators?: Indicator[]
setScenarios?: React.Dispatch<React.SetStateAction<Scenario[]>>
list: ScenarioViewModel[]
indicators?: IndicatorViewModel[]
setScenarios?: React.Dispatch<React.SetStateAction<ScenarioViewModel[]>>
}
export type IMoneyManagementList = {

View File

@@ -1,11 +1,11 @@
import React, { useState } from 'react'
import React, {useState} from 'react'
import 'react-toastify/dist/ReactToastify.css'
import { BacktestTable } from '../../components/organism'
import BacktestModal from '../../components/organism/Backtest/backtestModal'
import type { Backtest } from '../../generated/ManagingApi'
import UnifiedTradingModal from '../../components/organism/UnifiedTradingModal'
import type {Backtest} from '../../generated/ManagingApi'
const BacktestLoop: React.FC = () => {
const [backtestingResult, setBacktestingResult] = useState<Backtest[]>([])
const [backtestingResult, setBacktest] = useState<Backtest[]>([])
const [showModal, setShowModal] = useState(false)
@@ -20,13 +20,13 @@ const BacktestLoop: React.FC = () => {
return (
<div className="container mx-auto">
<button className="btn" onClick={openModal}>
Run optimized loop
Run Backtest with Loop
</button>
<BacktestTable list={backtestingResult} isFetching={false} />
<BacktestModal
<UnifiedTradingModal
mode="backtest"
showModal={showModal}
closeModal={closeModal}
setBacktests={setBacktestingResult}
setBacktests={setBacktest}
showLoopSlider={true}
/>
</div>

View File

@@ -1,8 +1,8 @@
import React, { useState } from 'react'
import React, {useState} from 'react'
import 'react-toastify/dist/ReactToastify.css'
import { BacktestCards, BacktestModal } from '../../components/organism'
import type { Backtest } from '../../generated/ManagingApi'
import {BacktestCards, UnifiedTradingModal} from '../../components/organism'
import type {Backtest} from '../../generated/ManagingApi'
const BacktestPlayground: React.FC = () => {
const [backtestingResult, setBacktest] = useState<Backtest[]>([])
@@ -23,7 +23,8 @@ const BacktestPlayground: React.FC = () => {
Run New Backtest
</button>
<BacktestCards list={backtestingResult} setBacktests={setBacktest} />
<BacktestModal
<UnifiedTradingModal
mode="backtest"
showModal={showModal}
closeModal={closeModal}
setBacktests={setBacktest}

View File

@@ -6,7 +6,7 @@ import 'react-toastify/dist/ReactToastify.css'
import useApiUrlStore from '../../app/store/apiStore'
import {Loader} from '../../components/atoms'
import {Modal, Toast} from '../../components/mollecules'
import {BacktestModal, BacktestTable} from '../../components/organism'
import {BacktestTable, UnifiedTradingModal} from '../../components/organism'
import type {Backtest} from '../../generated/ManagingApi'
import {BacktestClient} from '../../generated/ManagingApi'
@@ -101,7 +101,8 @@ const BacktestScanner: React.FC = () => {
<BacktestTable list={backtestingResult} isFetching={isLoading} setBacktests={setBacktest} />
<BacktestModal
<UnifiedTradingModal
mode="backtest"
showModal={showModal}
closeModal={closeModal}
setBacktests={setBacktest}

View File

@@ -5,8 +5,7 @@ import useApiUrlStore from '../../app/store/apiStore'
import {CardPosition, CardSignal, CardText, Toast,} from '../../components/mollecules'
import ManualPositionModal from '../../components/mollecules/ManualPositionModal'
import TradesModal from '../../components/mollecules/TradesModal/TradesModal'
import BotConfigModal from '../../components/mollecules/BotConfigModal/BotConfigModal'
import {TradeChart} from '../../components/organism'
import {TradeChart, UnifiedTradingModal} from '../../components/organism'
import type {BotType, MoneyManagement, Position, TradingBotResponse} from '../../generated/ManagingApi'
import {BotClient} from '../../generated/ManagingApi'
import type {IBotList} from '../../global/type'
@@ -40,7 +39,7 @@ const BotList: React.FC<IBotList> = ({ list }) => {
const [showTradesModal, setShowTradesModal] = useState(false)
const [selectedBotForTrades, setSelectedBotForTrades] = useState<{ identifier: string; agentName: string } | null>(null)
const [showBotConfigModal, setShowBotConfigModal] = useState(false)
const [botConfigModalMode, setBotConfigModalMode] = useState<'create' | 'update'>('create')
const [botConfigModalMode, setBotConfigModalMode] = useState<'createBot' | 'updateBot'>('createBot')
const [selectedBotForUpdate, setSelectedBotForUpdate] = useState<{
identifier: string
config: any
@@ -219,13 +218,13 @@ const BotList: React.FC<IBotList> = ({ list }) => {
}
function openCreateBotModal() {
setBotConfigModalMode('create')
setBotConfigModalMode('createBot')
setSelectedBotForUpdate(null)
setShowBotConfigModal(true)
}
function openUpdateBotModal(bot: TradingBotResponse) {
setBotConfigModalMode('update')
setBotConfigModalMode('updateBot')
setSelectedBotForUpdate({
identifier: bot.identifier,
config: bot.config
@@ -339,14 +338,17 @@ const BotList: React.FC<IBotList> = ({ list }) => {
setSelectedBotForTrades(null)
}}
/>
<BotConfigModal
showModal={showBotConfigModal}
<UnifiedTradingModal
mode={botConfigModalMode}
existingBot={selectedBotForUpdate || undefined}
onClose={() => {
showModal={showBotConfigModal}
closeModal={() => {
setShowBotConfigModal(false)
setSelectedBotForUpdate(null)
}}
existingBot={selectedBotForUpdate ? {
identifier: selectedBotForUpdate.identifier,
config: selectedBotForUpdate.config
} : undefined}
/>
</div>
)

View File

@@ -5,7 +5,7 @@ import 'react-toastify/dist/ReactToastify.css'
import useApiUrlStore from '../../app/store/apiStore'
import {Toast} from '../../components/mollecules'
import type {Indicator} from '../../generated/ManagingApi'
import type {IndicatorViewModel} from '../../generated/ManagingApi'
import {IndicatorType, ScenarioClient, Timeframe,} from '../../generated/ManagingApi'
import IndicatorTable from './indicatorTable'
@@ -28,7 +28,7 @@ const IndicatorList: React.FC = () => {
const [indicatorType, setIndicatorType] = useState<IndicatorType>(
IndicatorType.RsiDivergence
)
const [indicators, setIndicators] = useState<Indicator[]>([])
const [indicators, setIndicators] = useState<IndicatorViewModel[]>([])
const [showModal, setShowModal] = useState(false)
const { register, handleSubmit } = useForm<IIndicatorFormInput>()
const { apiUrl } = useApiUrlStore()
@@ -49,7 +49,7 @@ const IndicatorList: React.FC = () => {
form.smoothPeriods,
form.cyclePeriods
)
.then((indicator: Indicator) => {
.then((indicator: IndicatorViewModel) => {
t.update('success', 'Indicator created')
setIndicators((arr) => [...arr, indicator])
})
@@ -68,7 +68,7 @@ const IndicatorList: React.FC = () => {
}
useEffect(() => {
scenarioClient.scenario_GetIndicators().then((data: Indicator[]) => {
scenarioClient.scenario_GetIndicators().then((data: IndicatorViewModel[]) => {
setIndicators(data)
})
}, [])

View File

@@ -3,15 +3,15 @@ import React, {useEffect, useState} from 'react'
import useApiUrlStore from '../../app/store/apiStore'
import {SelectColumnFilter, Table, Toast} from '../../components/mollecules'
import type {Indicator} from '../../generated/ManagingApi'
import type {IndicatorViewModel} from '../../generated/ManagingApi'
import {ScenarioClient} from '../../generated/ManagingApi'
interface IIndicatorList {
list: Indicator[]
list: IndicatorViewModel[]
}
const IndicatorTable: React.FC<IIndicatorList> = ({ list }) => {
const [rows, setRows] = useState<Indicator[]>([])
const [rows, setRows] = useState<IndicatorViewModel[]>([])
const { apiUrl } = useApiUrlStore()
async function deleteIndicator(name: string) {
@@ -41,12 +41,6 @@ const IndicatorTable: React.FC<IIndicatorList> = ({ list }) => {
accessor: 'type',
disableSortBy: true,
},
{
Filter: SelectColumnFilter,
Header: 'Timeframe',
accessor: 'timeframe',
disableSortBy: true,
},
{
Filter: SelectColumnFilter,
Header: 'Signal',

View File

@@ -17,7 +17,7 @@ const tabs: TabsType = [
{
Component: IndicatorList,
index: 2,
label: 'Strategies',
label: 'Indicators',
},
]

View File

@@ -4,15 +4,15 @@ import 'react-toastify/dist/ReactToastify.css'
import useApiUrlStore from '../../app/store/apiStore'
import {Toast} from '../../components/mollecules'
import {ScenarioModal} from '../../components/organism'
import type {Indicator, Scenario} from '../../generated/ManagingApi'
import type {IndicatorViewModel, ScenarioViewModel} from '../../generated/ManagingApi'
import {ScenarioClient} from '../../generated/ManagingApi'
import type {IScenarioFormInput} from '../../global/type'
import ScenarioTable from './scenarioTable'
const ScenarioList: React.FC = () => {
const [indicators, setIndicators] = useState<Indicator[]>([])
const [scenarios, setScenarios] = useState<Scenario[]>([])
const [indicators, setIndicators] = useState<IndicatorViewModel[]>([])
const [scenarios, setScenarios] = useState<ScenarioViewModel[]>([])
const [showModal, setShowModal] = useState(false)
const { apiUrl } = useApiUrlStore()
const client = new ScenarioClient({}, apiUrl)
@@ -21,7 +21,7 @@ const ScenarioList: React.FC = () => {
const t = new Toast('Creating scenario')
await client
.scenario_CreateScenario(form.name, form.loopbackPeriod, form.indicators)
.then((data: Scenario) => {
.then((data: ScenarioViewModel) => {
t.update('success', 'Scenario created')
setScenarios((arr) => [...arr, data])
})

View File

@@ -4,14 +4,14 @@ import React, {useEffect, useState} from 'react'
import useApiUrlStore from '../../app/store/apiStore'
import {Table, Toast} from '../../components/mollecules'
import {ScenarioModal} from '../../components/organism'
import type {Indicator, Scenario} from '../../generated/ManagingApi'
import type {IndicatorViewModel, ScenarioViewModel} from '../../generated/ManagingApi'
import {ScenarioClient} from '../../generated/ManagingApi'
import type {IScenarioFormInput, IScenarioList} from '../../global/type'
const ScenarioTable: React.FC<IScenarioList> = ({ list, indicators = [], setScenarios }) => {
const [rows, setRows] = useState<Scenario[]>([])
const [rows, setRows] = useState<ScenarioViewModel[]>([])
const [showUpdateModal, setShowUpdateModal] = useState(false)
const [selectedScenario, setSelectedScenario] = useState<Scenario | null>(null)
const [selectedScenario, setSelectedScenario] = useState<ScenarioViewModel | null>(null)
const { apiUrl } = useApiUrlStore()
const client = new ScenarioClient({}, apiUrl)
@@ -53,7 +53,7 @@ const ScenarioTable: React.FC<IScenarioList> = ({ list, indicators = [], setScen
})
}
function openUpdateModal(scenario: Scenario) {
function openUpdateModal(scenario: ScenarioViewModel) {
setSelectedScenario(scenario)
setShowUpdateModal(true)
}
@@ -77,7 +77,7 @@ const ScenarioTable: React.FC<IScenarioList> = ({ list, indicators = [], setScen
{
Cell: ({ cell }: any) => (
<>
{cell.row.values.indicators.map((indicator: Indicator) => (
{cell.row.values.indicators.map((indicator: IndicatorViewModel) => (
<div
key={indicator.name}
className="badge badge-primary badge-outline"

View File

@@ -1,7 +1,7 @@
import {ChevronDownIcon, ChevronRightIcon,} from '@heroicons/react/solid'
import React, {useEffect, useMemo, useState} from 'react'
import {useNavigate} from 'react-router-dom'
import {FiKey, FiPlay, FiTrash2, FiTrendingUp} from 'react-icons/fi'
import {FiCopy, FiKey, FiPlay, FiTrash2, FiTrendingUp} from 'react-icons/fi'
import useApiUrlStore from '../../../app/store/apiStore'
import {SelectColumnFilter, Table, Toast,} from '../../../components/mollecules'
@@ -55,6 +55,16 @@ const AccountTable: React.FC<IAccountList> = ({ list, isFetching }) => {
}
}
async function copyToClipboard(text: string) {
const t = new Toast('Copying to clipboard...')
try {
await navigator.clipboard.writeText(text)
t.update('success', 'Address copied to clipboard!')
} catch (err) {
t.update('error', 'Failed to copy to clipboard')
}
}
const columns = useMemo(
() => [
{
@@ -97,9 +107,18 @@ const AccountTable: React.FC<IAccountList> = ({ list, isFetching }) => {
{
Cell: ({ cell }: any) => (
<>
<div className="tooltip" data-tip={cell.row.values.key}>
{cell.row.values.key.substring(0, 6)}...
{cell.row.values.key.slice(-4)}
<div className="flex items-center space-x-2">
<div className="tooltip" data-tip={cell.row.values.key}>
{cell.row.values.key.substring(0, 6)}...
{cell.row.values.key.slice(-4)}
</div>
<button
className="btn btn-xs btn-ghost"
onClick={() => copyToClipboard(cell.row.values.key)}
title="Copy full address"
>
<FiCopy className="h-3 w-3" />
</button>
</div>
</>
),