Update cache for userStrategy details

This commit is contained in:
2025-08-14 17:42:07 +07:00
parent 9d0c7cf834
commit aacb92018f
5 changed files with 390 additions and 35 deletions

View File

@@ -1,4 +0,0 @@
{
"schemaVersion": 2,
"dockerfilePath": "./src/Managing.Pinky/Dockerfile-pinky"
}

View File

@@ -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);
}

View File

@@ -295,10 +295,15 @@ namespace Managing.Application.ManageBot
}
// Check if bot already exists in database
var existingBot = await _botRepository.GetBotByIdentifierAsync(bot.Identifier);
if (existingBot != null)
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(
@@ -307,12 +312,15 @@ namespace Managing.Application.ManageBot
}
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;
}

View 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

View File

@@ -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 = () => {