Add manual close for bot positions

This commit is contained in:
2025-04-25 17:33:38 +07:00
parent d5dead3d8f
commit 6579bbc06f
8 changed files with 198 additions and 23 deletions

View File

@@ -1,8 +1,9 @@
import React, {useEffect, useState} from 'react'
import type {Position, UserStrategyDetailsViewModel} from '../../../generated/ManagingApi'
import {DataClient} from '../../../generated/ManagingApi'
import type {ClosePositionRequest, Position, UserStrategyDetailsViewModel} from '../../../generated/ManagingApi'
import {BotClient, DataClient} from '../../../generated/ManagingApi'
import useApiUrlStore from '../../../app/store/apiStore'
import Modal from '../Modal/Modal'
import Toast from '../Toast/Toast'
interface TradesModalProps {
showModal: boolean
@@ -18,6 +19,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
const { apiUrl } = useApiUrlStore()
const [strategyData, setStrategyData] = useState<UserStrategyDetailsViewModel | null>(null)
const [loading, setLoading] = useState<boolean>(false)
const [closingPosition, setClosingPosition] = useState<string | null>(null)
useEffect(() => {
if (showModal && botName) {
@@ -36,11 +38,42 @@ const TradesModal: React.FC<TradesModalProps> = ({
setStrategyData(data)
} catch (error) {
console.error('Error fetching strategy data:', error)
const errorToast = new Toast('Failed to fetch strategy data', false)
errorToast.update('error', 'Failed to fetch strategy data')
} finally {
setLoading(false)
}
}
const closePosition = async (position: Position) => {
if (!botName) return
try {
setClosingPosition(position.identifier)
const loadingToast = new Toast(`Closing trade ${position.identifier}...`, true)
// Use BotClient instead of fetch
const botClient = new BotClient({}, apiUrl)
const request: ClosePositionRequest = {
botName: botName,
positionId: position.identifier
}
await botClient.bot_ClosePosition(request)
loadingToast.update('success', 'Trade closed successfully!')
// Refresh data after closing
fetchStrategyData()
} catch (error) {
console.error('Error closing position:', error)
const errorToast = new Toast('Failed to close trade', false)
errorToast.update('error', `Failed to close trade: ${error instanceof Error ? error.message : 'Unknown error'}`)
} finally {
setClosingPosition(null)
}
}
return (
<Modal
showModal={showModal}
@@ -73,6 +106,7 @@ const TradesModal: React.FC<TradesModalProps> = ({
<th>Entry Price</th>
<th>Quantity</th>
<th>PnL</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@@ -89,11 +123,26 @@ const TradesModal: React.FC<TradesModalProps> = ({
<td className={position.profitAndLoss?.realized && position.profitAndLoss.realized > 0 ? 'text-success' : 'text-error'}>
{position.profitAndLoss?.realized?.toFixed(2) || '0.00'} $
</td>
<td>
{position.status !== 'Finished' && (
<button
className="btn btn-xs btn-error"
onClick={() => closePosition(position)}
disabled={closingPosition === position.identifier}
>
{closingPosition === position.identifier ? (
<span className="loading loading-spinner loading-xs"></span>
) : (
'Close'
)}
</button>
)}
</td>
</tr>
))
) : (
<tr>
<td colSpan={6} className="text-center">No trades found</td>
<td colSpan={7} className="text-center">No trades found</td>
</tr>
)}
</tbody>

View File

@@ -804,6 +804,45 @@ export class BotClient extends AuthorizedApiBase {
}
return Promise.resolve<Position>(null as any);
}
bot_ClosePosition(request: ClosePositionRequest): Promise<Position> {
let url_ = this.baseUrl + "/Bot/ClosePosition";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(request);
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_ClosePosition(_response);
});
}
protected processBot_ClosePosition(response: Response): Promise<Position> {
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 Position;
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<Position>(null as any);
}
}
export class DataClient extends AuthorizedApiBase {
@@ -2827,6 +2866,11 @@ export interface OpenPositionManuallyRequest {
direction?: TradeDirection;
}
export interface ClosePositionRequest {
botName?: string | null;
positionId?: string | null;
}
export interface SpotlightOverview {
spotlights: Spotlight[];
dateTime: Date;