Update cache for userStrategy details
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
"dockerfilePath": "./src/Managing.Pinky/Dockerfile-pinky"
|
||||
}
|
||||
@@ -398,16 +398,6 @@ public class DataController : ControllerBase
|
||||
[HttpGet("GetUserStrategy")]
|
||||
public async Task<ActionResult<UserStrategyDetailsViewModel>> GetUserStrategy(string agentName, string strategyName)
|
||||
{
|
||||
string cacheKey = $"UserStrategy_{agentName}_{strategyName}";
|
||||
|
||||
// Check if the user strategy details are already cached
|
||||
var cachedDetails = _cacheService.GetValue<UserStrategyDetailsViewModel>(cacheKey);
|
||||
|
||||
if (cachedDetails != null)
|
||||
{
|
||||
return Ok(cachedDetails);
|
||||
}
|
||||
|
||||
// Get the specific strategy for the user
|
||||
var strategy = await _mediator.Send(new GetUserStrategyCommand(agentName, strategyName));
|
||||
|
||||
@@ -419,9 +409,6 @@ public class DataController : ControllerBase
|
||||
// Map the strategy to a view model using the shared method
|
||||
var result = MapStrategyToViewModel(strategy);
|
||||
|
||||
// Cache the results for 5 minutes
|
||||
_cacheService.SaveValue(cacheKey, result, TimeSpan.FromMinutes(5));
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -295,24 +295,32 @@ namespace Managing.Application.ManageBot
|
||||
}
|
||||
|
||||
// Check if bot already exists in database
|
||||
var existingBot = await _botRepository.GetBotByIdentifierAsync(bot.Identifier);
|
||||
|
||||
if (existingBot != null)
|
||||
{
|
||||
// Update existing bot
|
||||
await _botRepository.UpdateBot(bot);
|
||||
_tradingBotLogger.LogDebug(
|
||||
"Updated bot statistics for bot {BotId}: Wins={Wins}, Losses={Losses}, PnL={PnL}, ROI={ROI}%, Volume={Volume}, Fees={Fees}",
|
||||
bot.Identifier, bot.TradeWins, bot.TradeLosses, bot.Pnl, bot.Roi, bot.Volume, bot.Fees);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert new bot
|
||||
await _botRepository.InsertBotAsync(bot);
|
||||
_tradingBotLogger.LogInformation(
|
||||
"Created new bot statistics for bot {BotId}: Wins={Wins}, Losses={Losses}, PnL={PnL}, ROI={ROI}%, Volume={Volume}, Fees={Fees}",
|
||||
bot.Identifier, bot.TradeWins, bot.TradeLosses, bot.Pnl, bot.Roi, bot.Volume, bot.Fees);
|
||||
}
|
||||
await ServiceScopeHelpers.WithScopedService<IBotRepository>(
|
||||
_scopeFactory,
|
||||
async repo =>
|
||||
{
|
||||
var existingBot = await repo.GetBotByIdentifierAsync(bot.Identifier);
|
||||
if (existingBot == null)
|
||||
{
|
||||
_tradingBotLogger.LogInformation("Creating new bot statistics for bot {BotId}",
|
||||
bot.Identifier);
|
||||
// Update existing bot
|
||||
await _botRepository.UpdateBot(bot);
|
||||
_tradingBotLogger.LogDebug(
|
||||
"Updated bot statistics for bot {BotId}: Wins={Wins}, Losses={Losses}, PnL={PnL}, ROI={ROI}%, Volume={Volume}, Fees={Fees}",
|
||||
bot.Identifier, bot.TradeWins, bot.TradeLosses, bot.Pnl, bot.Roi, bot.Volume, bot.Fees);
|
||||
}
|
||||
else
|
||||
{
|
||||
_tradingBotLogger.LogInformation("Updating existing bot statistics for bot {BotId}",
|
||||
bot.Identifier);
|
||||
// Insert new bot
|
||||
await _botRepository.InsertBotAsync(bot);
|
||||
_tradingBotLogger.LogInformation(
|
||||
"Created new bot statistics for bot {BotId}: Wins={Wins}, Losses={Losses}, PnL={PnL}, ROI={ROI}%, Volume={Volume}, Fees={Fees}",
|
||||
bot.Identifier, bot.TradeWins, bot.TradeLosses, bot.Pnl, bot.Roi, bot.Volume, bot.Fees);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
358
src/Managing.WebApp/src/pages/dashboardPage/agentStrategy.tsx
Normal file
358
src/Managing.WebApp/src/pages/dashboardPage/agentStrategy.tsx
Normal file
@@ -0,0 +1,358 @@
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {PlayIcon, StopIcon} from '@heroicons/react/solid'
|
||||
import {
|
||||
BotClient,
|
||||
BotStatus,
|
||||
DataClient,
|
||||
Position,
|
||||
TradeDirection,
|
||||
UserStrategyDetailsViewModel
|
||||
} from '../../generated/ManagingApi'
|
||||
import useApiUrlStore from '../../app/store/apiStore'
|
||||
import {Toast} from '../../components/mollecules'
|
||||
|
||||
interface AgentStrategyProps {
|
||||
index: number
|
||||
}
|
||||
|
||||
const AgentStrategy: React.FC<AgentStrategyProps> = ({ index }) => {
|
||||
const { apiUrl } = useApiUrlStore()
|
||||
const botClient = new BotClient({}, apiUrl)
|
||||
|
||||
const [strategyData, setStrategyData] = useState<UserStrategyDetailsViewModel | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [agentName, setAgentName] = useState('')
|
||||
const [strategyName, setStrategyName] = useState('')
|
||||
const [activeTab, setActiveTab] = useState<'stats' | 'history'>('stats')
|
||||
|
||||
const fetchStrategyData = async () => {
|
||||
if (!agentName || !strategyName) return
|
||||
|
||||
try {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
const dataClient = new DataClient({}, apiUrl)
|
||||
const data = await dataClient.data_GetUserStrategy(agentName, strategyName)
|
||||
setStrategyData(data)
|
||||
} catch (err) {
|
||||
setError('Failed to load strategy data')
|
||||
console.error('Error fetching strategy data:', err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchStrategyData()
|
||||
}, [agentName, strategyName])
|
||||
|
||||
const formatCurrency = (value?: number | null) => {
|
||||
if (value === null || value === undefined) return '$0.00'
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
minimumFractionDigits: 2,
|
||||
}).format(value)
|
||||
}
|
||||
|
||||
const formatPercentage = (value?: number | null) => {
|
||||
if (value === null || value === undefined) return '0%'
|
||||
return `${value >= 0 ? '+' : ''}${value.toFixed(2)}%`
|
||||
}
|
||||
|
||||
const formatDuration = (runtime?: Date | null) => {
|
||||
if (!runtime) return '0h'
|
||||
const now = new Date()
|
||||
const start = new Date(runtime)
|
||||
const diffMs = now.getTime() - start.getTime()
|
||||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
|
||||
const diffHours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
|
||||
|
||||
if (diffDays > 0) {
|
||||
return `${diffDays} week${diffDays > 6 ? 's' : ''} ${diffHours}h`
|
||||
}
|
||||
return `${diffHours}h`
|
||||
}
|
||||
|
||||
const getStatusBadge = (state: string | null | undefined) => {
|
||||
const badgeClass = state === 'Up'
|
||||
? 'badge badge-success'
|
||||
: state === 'Down'
|
||||
? 'badge badge-error'
|
||||
: 'badge badge-neutral'
|
||||
|
||||
return <span className={badgeClass}>{state}</span>
|
||||
}
|
||||
|
||||
const toggleStrategyStatus = (status: string | null | undefined, identifier: string) => {
|
||||
const isUp = status === 'Up'
|
||||
const t = new Toast(isUp ? 'Stopping strategy' : 'Starting strategy')
|
||||
|
||||
console.log('toggleStrategyStatus', status, identifier)
|
||||
if (status === 'Up') {
|
||||
botClient
|
||||
.bot_Stop(identifier)
|
||||
.then(() => {
|
||||
t.update('success', 'Strategy stopped')
|
||||
// Refresh the strategy data to get updated status
|
||||
fetchStrategyData()
|
||||
})
|
||||
.catch((err) => {
|
||||
t.update('error', err)
|
||||
})
|
||||
} else if (status === 'Down' || status === 'None') {
|
||||
botClient
|
||||
.bot_Restart(identifier)
|
||||
.then(() => {
|
||||
t.update('success', 'Strategy started')
|
||||
// Refresh the strategy data to get updated status
|
||||
fetchStrategyData()
|
||||
})
|
||||
.catch((err) => {
|
||||
t.update('error', err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-6">
|
||||
{/* Header Section */}
|
||||
<div className="mb-6">
|
||||
<div className="flex items-center gap-4 mb-4">
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Agent Name</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter agent name"
|
||||
className="input input-bordered w-full max-w-xs"
|
||||
value={agentName}
|
||||
onChange={(e) => setAgentName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Strategy Name</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter strategy name"
|
||||
className="input input-bordered w-full max-w-xs"
|
||||
value={strategyName}
|
||||
onChange={(e) => setStrategyName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-primary mt-8"
|
||||
onClick={fetchStrategyData}
|
||||
disabled={!agentName || !strategyName}
|
||||
>
|
||||
Load Strategy
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{loading && (
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<span className="loading loading-spinner loading-lg"></span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="alert alert-error mb-6">
|
||||
<span>{error}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{strategyData && (
|
||||
<>
|
||||
{/* Strategy Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<h1 className="text-3xl font-bold">{strategyData.name || '[Strategy Name]'}</h1>
|
||||
{getStatusBadge(strategyData.state as BotStatus)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
className={`btn btn-outline ${strategyData.state === 'Up' ? 'btn-error' : 'btn-success'}`}
|
||||
onClick={() => toggleStrategyStatus(strategyData.state, strategyData.identifier || '')}
|
||||
>
|
||||
{strategyData.state === 'Up' ? (
|
||||
<StopIcon width={15} />
|
||||
) : (
|
||||
<PlayIcon width={15} />
|
||||
)}
|
||||
</button>
|
||||
<button className="btn btn-outline">Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main Statistics */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
||||
<div className="stat bg-base-200 rounded-lg">
|
||||
<div className="stat-title">Winrate</div>
|
||||
<div className="stat-value text-success">{strategyData.winRate || 0}%</div>
|
||||
</div>
|
||||
<div className="stat bg-base-200 rounded-lg">
|
||||
<div className="stat-title">PnL</div>
|
||||
<div className="stat-value text-success">{formatCurrency(strategyData.pnL)}</div>
|
||||
</div>
|
||||
<div className="stat bg-base-200 rounded-lg">
|
||||
<div className="stat-title">Backtest</div>
|
||||
<div className="stat-value">3</div>
|
||||
</div>
|
||||
<div className="stat bg-base-200 rounded-lg">
|
||||
<div className="stat-title">Runtime</div>
|
||||
<div className="stat-value text-sm">{formatDuration(strategyData.runtime)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chart Placeholder */}
|
||||
<div className="bg-gradient-to-r from-blue-500 to-blue-700 rounded-lg h-64 mb-8 flex items-center justify-center">
|
||||
<div className="text-white text-center">
|
||||
<div className="text-4xl mb-2">📈</div>
|
||||
<div>Strategy Performance Chart</div>
|
||||
<div className="text-sm opacity-75">Chart visualization will be implemented here</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Information Tabs */}
|
||||
<div className="mb-6">
|
||||
<h2 className="text-xl font-semibold mb-4">Informations</h2>
|
||||
<div className="tabs tabs-bordered">
|
||||
<button
|
||||
className={`tab tab-bordered ${activeTab === 'stats' ? 'tab-active' : ''}`}
|
||||
onClick={() => setActiveTab('stats')}
|
||||
>
|
||||
⚡ Strategy Stats
|
||||
</button>
|
||||
<button
|
||||
className={`tab tab-bordered ${activeTab === 'history' ? 'tab-active' : ''}`}
|
||||
onClick={() => setActiveTab('history')}
|
||||
>
|
||||
📋 Trade History
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tab Content */}
|
||||
{activeTab === 'stats' && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="card bg-base-200">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">Total Volume Traded</h3>
|
||||
<div className="text-2xl font-bold">{formatCurrency(strategyData.totalVolumeTraded)}</div>
|
||||
<div className="text-sm text-success">
|
||||
{formatCurrency(strategyData.volumeLast24H)} Today
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-base-200">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">Win / Loss</h3>
|
||||
<div className="text-2xl font-bold">
|
||||
{strategyData.wins || 0}/{strategyData.losses || 0}
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
{((strategyData.winRate || 0) / 100).toFixed(1)}% Avg Winrate
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-base-200">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">ROI</h3>
|
||||
<div className="text-2xl font-bold">{formatPercentage(strategyData.roiPercentage)}</div>
|
||||
<div className="text-sm text-success">
|
||||
{formatPercentage(strategyData.roiLast24H)} Today
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-base-200">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">PnL $</h3>
|
||||
<div className="text-2xl font-bold">{formatCurrency(strategyData.pnL)}</div>
|
||||
<div className="text-sm">
|
||||
{formatCurrency((strategyData.pnL || 0) * 0.1)} Today
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-base-200">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">PnL %</h3>
|
||||
<div className="text-2xl font-bold text-success">
|
||||
{formatPercentage(strategyData.roiPercentage)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="card bg-base-200">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">Total Runtime</h3>
|
||||
<div className="text-2xl font-bold">{formatDuration(strategyData.runtime)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'history' && (
|
||||
<div className="card bg-base-200">
|
||||
<div className="card-body">
|
||||
<h3 className="card-title">Trade History</h3>
|
||||
{strategyData.positions && Object.keys(strategyData.positions).length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table table-zebra">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Symbol</th>
|
||||
<th>Side</th>
|
||||
<th>Size</th>
|
||||
<th>Entry Price</th>
|
||||
<th>Exit Price</th>
|
||||
<th>PnL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.values(strategyData.positions).map((position: Position, index: number) => (
|
||||
console.log(position),
|
||||
<tr key={index}>
|
||||
<td>{new Date(position.Open.date || '').toLocaleDateString()}</td>
|
||||
<td>{position.ticker}</td>
|
||||
<td>
|
||||
<span className={`badge ${position.Open.direction === TradeDirection.Long ? 'badge-success' : 'badge-error'}`}>
|
||||
{position.Open.direction === TradeDirection.Long ? 'LONG' : 'SHORT'}
|
||||
</span>
|
||||
</td>
|
||||
<td>{position.Open.quantity}</td>
|
||||
<td>{formatCurrency(position.Open.price)}</td>
|
||||
<td>{formatCurrency(position.TakeProfit1?.price || 0)}</td>
|
||||
<td className={position.ProfitAndLoss?.realized && position.ProfitAndLoss.realized > 0 ? 'text-success' : 'text-error'}>
|
||||
{formatCurrency(position.ProfitAndLoss?.realized || 0)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<div className="text-4xl mb-2">📋</div>
|
||||
<div>No trade history available</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AgentStrategy
|
||||
@@ -8,6 +8,7 @@ import Monitoring from './monitoring'
|
||||
import BestAgents from './analytics/bestAgents'
|
||||
import AgentSearch from './agentSearch'
|
||||
import AgentIndex from './agentIndex'
|
||||
import AgentStrategy from './agentStrategy'
|
||||
|
||||
const tabs: ITabsType = [
|
||||
{
|
||||
@@ -35,6 +36,11 @@ const tabs: ITabsType = [
|
||||
index: 5,
|
||||
label: 'Agent Index',
|
||||
},
|
||||
{
|
||||
Component: AgentStrategy,
|
||||
index: 6,
|
||||
label: 'Agent Strategy',
|
||||
},
|
||||
]
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
|
||||
Reference in New Issue
Block a user