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")]
|
[HttpGet("GetUserStrategy")]
|
||||||
public async Task<ActionResult<UserStrategyDetailsViewModel>> GetUserStrategy(string agentName, string strategyName)
|
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
|
// Get the specific strategy for the user
|
||||||
var strategy = await _mediator.Send(new GetUserStrategyCommand(agentName, strategyName));
|
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
|
// Map the strategy to a view model using the shared method
|
||||||
var result = MapStrategyToViewModel(strategy);
|
var result = MapStrategyToViewModel(strategy);
|
||||||
|
|
||||||
// Cache the results for 5 minutes
|
|
||||||
_cacheService.SaveValue(cacheKey, result, TimeSpan.FromMinutes(5));
|
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -295,24 +295,32 @@ namespace Managing.Application.ManageBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if bot already exists in database
|
// Check if bot already exists in database
|
||||||
var existingBot = await _botRepository.GetBotByIdentifierAsync(bot.Identifier);
|
await ServiceScopeHelpers.WithScopedService<IBotRepository>(
|
||||||
|
_scopeFactory,
|
||||||
if (existingBot != null)
|
async repo =>
|
||||||
{
|
{
|
||||||
// Update existing bot
|
var existingBot = await repo.GetBotByIdentifierAsync(bot.Identifier);
|
||||||
await _botRepository.UpdateBot(bot);
|
if (existingBot == null)
|
||||||
_tradingBotLogger.LogDebug(
|
{
|
||||||
"Updated bot statistics for bot {BotId}: Wins={Wins}, Losses={Losses}, PnL={PnL}, ROI={ROI}%, Volume={Volume}, Fees={Fees}",
|
_tradingBotLogger.LogInformation("Creating new bot statistics for bot {BotId}",
|
||||||
bot.Identifier, bot.TradeWins, bot.TradeLosses, bot.Pnl, bot.Roi, bot.Volume, bot.Fees);
|
bot.Identifier);
|
||||||
}
|
// Update existing bot
|
||||||
else
|
await _botRepository.UpdateBot(bot);
|
||||||
{
|
_tradingBotLogger.LogDebug(
|
||||||
// Insert new bot
|
"Updated bot statistics for bot {BotId}: Wins={Wins}, Losses={Losses}, PnL={PnL}, ROI={ROI}%, Volume={Volume}, Fees={Fees}",
|
||||||
await _botRepository.InsertBotAsync(bot);
|
bot.Identifier, bot.TradeWins, bot.TradeLosses, bot.Pnl, bot.Roi, bot.Volume, bot.Fees);
|
||||||
_tradingBotLogger.LogInformation(
|
}
|
||||||
"Created new bot statistics for bot {BotId}: Wins={Wins}, Losses={Losses}, PnL={PnL}, ROI={ROI}%, Volume={Volume}, Fees={Fees}",
|
else
|
||||||
bot.Identifier, bot.TradeWins, bot.TradeLosses, bot.Pnl, bot.Roi, bot.Volume, bot.Fees);
|
{
|
||||||
}
|
_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;
|
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 BestAgents from './analytics/bestAgents'
|
||||||
import AgentSearch from './agentSearch'
|
import AgentSearch from './agentSearch'
|
||||||
import AgentIndex from './agentIndex'
|
import AgentIndex from './agentIndex'
|
||||||
|
import AgentStrategy from './agentStrategy'
|
||||||
|
|
||||||
const tabs: ITabsType = [
|
const tabs: ITabsType = [
|
||||||
{
|
{
|
||||||
@@ -35,6 +36,11 @@ const tabs: ITabsType = [
|
|||||||
index: 5,
|
index: 5,
|
||||||
label: 'Agent Index',
|
label: 'Agent Index',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Component: AgentStrategy,
|
||||||
|
index: 6,
|
||||||
|
label: 'Agent Strategy',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const Dashboard: React.FC = () => {
|
const Dashboard: React.FC = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user