Add manual close for bot positions
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user