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:
@@ -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;"]
|
||||
@@ -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",
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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'},
|
||||
|
||||
@@ -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.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
export { default } from './UnifiedTradingModal'
|
||||
@@ -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'
|
||||
|
||||
@@ -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",
|
||||
|
||||
887
src/Managing.WebApp/src/generated/ManagingApiTypes.ts
Normal file
887
src/Managing.WebApp/src/generated/ManagingApiTypes.ts
Normal 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 };
|
||||
}
|
||||
50
src/Managing.WebApp/src/global/type.ts
Normal file
50
src/Managing.WebApp/src/global/type.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}, [])
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -17,7 +17,7 @@ const tabs: TabsType = [
|
||||
{
|
||||
Component: IndicatorList,
|
||||
index: 2,
|
||||
label: 'Strategies',
|
||||
label: 'Indicators',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -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])
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user