Implement spot position history retrieval in SpotBot and related services
- Added CheckSpotPositionInExchangeHistory method to SpotBot for verifying closed positions against exchange history. - Enhanced logging for Web3Proxy errors during position verification. - Introduced GetSpotPositionHistory method in IEvmManager, IExchangeService, and IWeb3ProxyService interfaces. - Implemented GetSpotPositionHistory in EvmManager and ExchangeService to fetch historical swap data. - Updated GMX SDK integration to support fetching spot position history. - Modified generated API types to include new trading type and position history structures.
This commit is contained in:
@@ -94,6 +94,24 @@ public interface IEvmManager
|
|||||||
int pageIndex = 0,
|
int pageIndex = 0,
|
||||||
int pageSize = 20);
|
int pageSize = 20);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the spot position history (swap executions) for a specific ticker and account from GMX
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="account">The trading account</param>
|
||||||
|
/// <param name="ticker">The ticker to get history for</param>
|
||||||
|
/// <param name="fromDate">Optional start date for filtering</param>
|
||||||
|
/// <param name="toDate">Optional end date for filtering</param>
|
||||||
|
/// <param name="pageIndex">Page index for pagination (default: 0)</param>
|
||||||
|
/// <param name="pageSize">Page size for pagination (default: 20)</param>
|
||||||
|
/// <returns>Spot position history response containing swap executions</returns>
|
||||||
|
Task<List<Position>> GetSpotPositionHistory(
|
||||||
|
Account account,
|
||||||
|
Ticker ticker,
|
||||||
|
DateTime? fromDate = null,
|
||||||
|
DateTime? toDate = null,
|
||||||
|
int pageIndex = 0,
|
||||||
|
int pageSize = 20);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the staked KUDAI balance for a specific address
|
/// Gets the staked KUDAI balance for a specific address
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -73,4 +73,12 @@ public interface IExchangeService
|
|||||||
Ticker ticker,
|
Ticker ticker,
|
||||||
DateTime? fromDate = null,
|
DateTime? fromDate = null,
|
||||||
DateTime? toDate = null);
|
DateTime? toDate = null);
|
||||||
|
|
||||||
|
Task<List<Position>> GetSpotPositionHistory(
|
||||||
|
Account account,
|
||||||
|
Ticker ticker,
|
||||||
|
DateTime? fromDate = null,
|
||||||
|
DateTime? toDate = null,
|
||||||
|
int pageIndex = 0,
|
||||||
|
int pageSize = 20);
|
||||||
}
|
}
|
||||||
@@ -31,5 +31,13 @@ namespace Managing.Application.Abstractions.Services
|
|||||||
string? ticker = null,
|
string? ticker = null,
|
||||||
DateTime? fromDate = null,
|
DateTime? fromDate = null,
|
||||||
DateTime? toDate = null);
|
DateTime? toDate = null);
|
||||||
|
|
||||||
|
Task<List<Position>> GetGmxSpotPositionHistoryAsync(
|
||||||
|
string account,
|
||||||
|
int pageIndex = 0,
|
||||||
|
int pageSize = 20,
|
||||||
|
string? ticker = null,
|
||||||
|
DateTime? fromDate = null,
|
||||||
|
DateTime? toDate = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -337,11 +337,31 @@ public class SpotBot : TradingBotBase, ITradingBot
|
|||||||
// No token balance found - check if position was closed
|
// No token balance found - check if position was closed
|
||||||
if (internalPosition.Status == PositionStatus.Filled)
|
if (internalPosition.Status == PositionStatus.Filled)
|
||||||
{
|
{
|
||||||
|
var (positionFoundInHistory, hadWeb3ProxyError) =
|
||||||
|
await CheckSpotPositionInExchangeHistory(internalPosition);
|
||||||
|
|
||||||
|
if (hadWeb3ProxyError)
|
||||||
|
{
|
||||||
|
await LogWarningAsync(
|
||||||
|
$"⏳ Web3Proxy Error During Spot Position Verification\n" +
|
||||||
|
$"Position: `{internalPosition.Identifier}`\n" +
|
||||||
|
$"Cannot verify if position is closed\n" +
|
||||||
|
$"Will retry on next execution cycle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (positionFoundInHistory)
|
||||||
|
{
|
||||||
|
internalPosition.Status = PositionStatus.Finished;
|
||||||
|
await HandleClosedPosition(internalPosition);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await LogDebugAsync(
|
await LogDebugAsync(
|
||||||
$"⚠️ Position Status Check\n" +
|
$"⚠️ Position Status Check\n" +
|
||||||
$"Internal position `{internalPosition.Identifier}` shows Filled\n" +
|
$"Internal position `{internalPosition.Identifier}` shows Filled\n" +
|
||||||
$"But no token balance found on broker\n" +
|
$"But no token balance found on broker or in history\n" +
|
||||||
$"Position may have been closed");
|
$"Will retry verification on next cycle");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,6 +371,56 @@ public class SpotBot : TradingBotBase, ITradingBot
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<(bool found, bool hadError)> CheckSpotPositionInExchangeHistory(Position position)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await LogDebugAsync(
|
||||||
|
$"🔍 Checking Spot Position History for Position: `{position.Identifier}`\nTicker: `{Config.Ticker}`");
|
||||||
|
|
||||||
|
List<Position> positionHistory = null;
|
||||||
|
await ServiceScopeHelpers.WithScopedService<IExchangeService>(_scopeFactory,
|
||||||
|
async exchangeService =>
|
||||||
|
{
|
||||||
|
var fromDate = DateTime.UtcNow.AddHours(-24);
|
||||||
|
var toDate = DateTime.UtcNow;
|
||||||
|
positionHistory =
|
||||||
|
await exchangeService.GetSpotPositionHistory(Account, Config.Ticker, fromDate, toDate);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (positionHistory != null && positionHistory.Any())
|
||||||
|
{
|
||||||
|
var recentPosition = positionHistory
|
||||||
|
.OrderByDescending(p => p.Date)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (recentPosition != null)
|
||||||
|
{
|
||||||
|
await LogDebugAsync(
|
||||||
|
$"✅ Spot Position Found in History\n" +
|
||||||
|
$"Position: `{position.Identifier}`\n" +
|
||||||
|
$"Ticker: `{recentPosition.Ticker}`\n" +
|
||||||
|
$"Date: `{recentPosition.Date}`");
|
||||||
|
return (true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await LogDebugAsync(
|
||||||
|
$"❌ No Spot Position Found in Exchange History\nPosition: `{position.Identifier}`\nPosition may still be open or data is delayed");
|
||||||
|
return (false, false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Error checking spot position history for position {PositionId}", position.Identifier);
|
||||||
|
await LogWarningAsync(
|
||||||
|
$"⚠️ Web3Proxy Error During Spot Position History Check\n" +
|
||||||
|
$"Position: `{position.Identifier}`\n" +
|
||||||
|
$"Error: {ex.Message}\n" +
|
||||||
|
$"Will retry on next execution cycle");
|
||||||
|
return (false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task HandleOrderManagementAndPositionStatus(LightSignal signal, Position internalPosition,
|
protected override async Task HandleOrderManagementAndPositionStatus(LightSignal signal, Position internalPosition,
|
||||||
Position positionForSignal)
|
Position positionForSignal)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -464,6 +464,7 @@ public class BundleBacktestHealthCheckWorker : BackgroundService
|
|||||||
// Some jobs are still pending or running - bundle is genuinely stuck
|
// Some jobs are still pending or running - bundle is genuinely stuck
|
||||||
// Reset any stale running jobs back to pending
|
// Reset any stale running jobs back to pending
|
||||||
var runningJobs = jobs.Where(j => j.Status == JobStatus.Running).ToList();
|
var runningJobs = jobs.Where(j => j.Status == JobStatus.Running).ToList();
|
||||||
|
var resetJobCount = 0;
|
||||||
|
|
||||||
foreach (var job in runningJobs)
|
foreach (var job in runningJobs)
|
||||||
{
|
{
|
||||||
@@ -481,13 +482,14 @@ public class BundleBacktestHealthCheckWorker : BackgroundService
|
|||||||
job.AssignedWorkerId = null;
|
job.AssignedWorkerId = null;
|
||||||
job.LastHeartbeat = null;
|
job.LastHeartbeat = null;
|
||||||
await jobRepository.UpdateAsync(job);
|
await jobRepository.UpdateAsync(job);
|
||||||
|
resetJobCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update bundle timestamp to give it another chance
|
// Update bundle timestamp to give it another chance
|
||||||
bundle.UpdatedAt = DateTime.UtcNow;
|
bundle.UpdatedAt = DateTime.UtcNow;
|
||||||
bundle.ErrorMessage =
|
bundle.ErrorMessage =
|
||||||
$"Bundle was stuck. Reset {runningJobs.Count(j => j.Status == JobStatus.Pending)} stale jobs to pending.";
|
$"Bundle was stuck. Reset {resetJobCount} stale jobs to pending.";
|
||||||
}
|
}
|
||||||
|
|
||||||
await backtestRepository.UpdateBundleBacktestRequestAsync(bundle);
|
await backtestRepository.UpdateBundleBacktestRequestAsync(bundle);
|
||||||
|
|||||||
@@ -47,4 +47,12 @@ public interface IExchangeProcessor
|
|||||||
Ticker ticker,
|
Ticker ticker,
|
||||||
DateTime? fromDate = null,
|
DateTime? fromDate = null,
|
||||||
DateTime? toDate = null);
|
DateTime? toDate = null);
|
||||||
|
|
||||||
|
Task<List<Position>> GetSpotPositionHistory(
|
||||||
|
Account account,
|
||||||
|
Ticker ticker,
|
||||||
|
DateTime? fromDate = null,
|
||||||
|
DateTime? toDate = null,
|
||||||
|
int pageIndex = 0,
|
||||||
|
int pageSize = 20);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,6 +193,18 @@ namespace Managing.Infrastructure.Exchanges
|
|||||||
return processor.GetPositionHistory(account, ticker, fromDate, toDate);
|
return processor.GetPositionHistory(account, ticker, fromDate, toDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<List<Position>> GetSpotPositionHistory(
|
||||||
|
Account account,
|
||||||
|
Ticker ticker,
|
||||||
|
DateTime? fromDate = null,
|
||||||
|
DateTime? toDate = null,
|
||||||
|
int pageIndex = 0,
|
||||||
|
int pageSize = 20)
|
||||||
|
{
|
||||||
|
var processor = GetProcessor(account);
|
||||||
|
return processor.GetSpotPositionHistory(account, ticker, fromDate, toDate, pageIndex, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
|
public async Task<List<Trade>> GetTrades(Account account, Ticker ticker)
|
||||||
{
|
{
|
||||||
var processor = GetProcessor(account);
|
var processor = GetProcessor(account);
|
||||||
|
|||||||
@@ -48,5 +48,13 @@ namespace Managing.Infrastructure.Exchanges.Exchanges
|
|||||||
Ticker ticker,
|
Ticker ticker,
|
||||||
DateTime? fromDate = null,
|
DateTime? fromDate = null,
|
||||||
DateTime? toDate = null);
|
DateTime? toDate = null);
|
||||||
|
|
||||||
|
public abstract Task<List<Position>> GetSpotPositionHistory(
|
||||||
|
Account account,
|
||||||
|
Ticker ticker,
|
||||||
|
DateTime? fromDate = null,
|
||||||
|
DateTime? toDate = null,
|
||||||
|
int pageIndex = 0,
|
||||||
|
int pageSize = 20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,6 +217,17 @@ public class EvmProcessor : BaseProcessor
|
|||||||
return await _evmManager.GetPositionHistory(account, ticker, fromDate, toDate);
|
return await _evmManager.GetPositionHistory(account, ticker, fromDate, toDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<List<Position>> GetSpotPositionHistory(
|
||||||
|
Account account,
|
||||||
|
Ticker ticker,
|
||||||
|
DateTime? fromDate = null,
|
||||||
|
DateTime? toDate = null,
|
||||||
|
int pageIndex = 0,
|
||||||
|
int pageSize = 20)
|
||||||
|
{
|
||||||
|
return await _evmManager.GetSpotPositionHistory(account, ticker, fromDate, toDate, pageIndex, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
#region Not implemented
|
#region Not implemented
|
||||||
|
|
||||||
public override void LoadClient(Account account)
|
public override void LoadClient(Account account)
|
||||||
|
|||||||
@@ -1002,6 +1002,25 @@ public class EvmManager : IEvmManager
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<Position>> GetSpotPositionHistory(
|
||||||
|
Account account,
|
||||||
|
Ticker ticker,
|
||||||
|
DateTime? fromDate = null,
|
||||||
|
DateTime? toDate = null,
|
||||||
|
int pageIndex = 0,
|
||||||
|
int pageSize = 20)
|
||||||
|
{
|
||||||
|
var result = await _web3ProxyService.GetGmxSpotPositionHistoryAsync(
|
||||||
|
account.Key,
|
||||||
|
pageIndex,
|
||||||
|
pageSize,
|
||||||
|
ticker.ToString(),
|
||||||
|
fromDate,
|
||||||
|
toDate);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<decimal> GetKudaiStakedBalance(string address)
|
public async Task<decimal> GetKudaiStakedBalance(string address)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -697,5 +697,98 @@ namespace Managing.Infrastructure.Evm.Services
|
|||||||
|
|
||||||
return positions;
|
return positions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<Position>> GetGmxSpotPositionHistoryAsync(
|
||||||
|
string account,
|
||||||
|
int pageIndex = 0,
|
||||||
|
int pageSize = 20,
|
||||||
|
string? ticker = null,
|
||||||
|
DateTime? fromDate = null,
|
||||||
|
DateTime? toDate = null)
|
||||||
|
{
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
account,
|
||||||
|
pageIndex,
|
||||||
|
pageSize,
|
||||||
|
ticker,
|
||||||
|
fromDateTime = fromDate?.ToString("O"),
|
||||||
|
toDateTime = toDate?.ToString("O")
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await GetGmxServiceAsync<GetGmxPositionHistoryResponse>("/spot-position-history", payload);
|
||||||
|
|
||||||
|
if (response == null)
|
||||||
|
{
|
||||||
|
throw new Web3ProxyException("Spot position history response is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.Success)
|
||||||
|
{
|
||||||
|
throw new Web3ProxyException($"Spot position history request failed: {response.Error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var positions = new List<Position>();
|
||||||
|
if (response.Positions != null)
|
||||||
|
{
|
||||||
|
foreach (var g in response.Positions)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tickerEnum = MiscExtensions.ParseEnum<Ticker>(g.Ticker);
|
||||||
|
var directionEnum = MiscExtensions.ParseEnum<TradeDirection>(g.Direction);
|
||||||
|
|
||||||
|
var position = new Position(
|
||||||
|
Guid.NewGuid(),
|
||||||
|
0,
|
||||||
|
directionEnum,
|
||||||
|
tickerEnum,
|
||||||
|
null,
|
||||||
|
PositionInitiator.Bot,
|
||||||
|
g.Date,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Status = MiscExtensions.ParseEnum<PositionStatus>(g.Status)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (g.Open != null)
|
||||||
|
{
|
||||||
|
position.Open = new Trade(
|
||||||
|
g.Open.Date,
|
||||||
|
MiscExtensions.ParseEnum<TradeDirection>(g.Open.Direction),
|
||||||
|
MiscExtensions.ParseEnum<TradeStatus>(g.Open.Status),
|
||||||
|
TradeType.Market,
|
||||||
|
tickerEnum,
|
||||||
|
(decimal)g.Open.Quantity,
|
||||||
|
(decimal)g.Open.Price,
|
||||||
|
(decimal)g.Open.Leverage,
|
||||||
|
g.Open.ExchangeOrderId,
|
||||||
|
string.Empty
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
position.ProfitAndLoss = new ProfitAndLoss
|
||||||
|
{
|
||||||
|
Realized = g.ProfitAndLoss?.Realized != null
|
||||||
|
? (decimal)g.ProfitAndLoss.Realized
|
||||||
|
: (decimal)g.Pnl,
|
||||||
|
Net = g.ProfitAndLoss?.Net != null ? (decimal)g.ProfitAndLoss.Net : (decimal)g.Pnl
|
||||||
|
};
|
||||||
|
|
||||||
|
position.UiFees = g.UiFees.HasValue ? (decimal)g.UiFees.Value : 0;
|
||||||
|
position.GasFees = g.GasFees.HasValue ? (decimal)g.GasFees.Value : 0;
|
||||||
|
|
||||||
|
positions.Add(position);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to map GMX spot position history entry");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return positions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// See https://aka.ms/new-console-template for more information
|
// See https://aka.ms/new-console-template for more information
|
||||||
|
|
||||||
using NJsonSchema.CodeGeneration.TypeScript;
|
using NJsonSchema.CodeGeneration.TypeScript;
|
||||||
using NSwag;
|
using NSwag;
|
||||||
@@ -23,8 +23,11 @@ for (int i = 0; i < 10; i++)
|
|||||||
solutionDirectory = parent.FullName;
|
solutionDirectory = parent.FullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetDirectory = Path.Combine(solutionDirectory, "src", "Managing.WebApp", "src", "generated");
|
var targetWebAppDirectory = Path.Combine(solutionDirectory, "src", "Managing.WebApp", "src", "generated");
|
||||||
Directory.CreateDirectory(targetDirectory); // Ensure the directory exists
|
Directory.CreateDirectory(targetWebAppDirectory); // Ensure the directory exists
|
||||||
|
|
||||||
|
var targetWeb3ProxyDirectory = Path.Combine(solutionDirectory, "src", "Managing.Web3Proxy", "src", "generated");
|
||||||
|
Directory.CreateDirectory(targetWeb3ProxyDirectory);
|
||||||
|
|
||||||
var settings = new TypeScriptClientGeneratorSettings
|
var settings = new TypeScriptClientGeneratorSettings
|
||||||
{
|
{
|
||||||
@@ -69,7 +72,7 @@ if (autoGeneratedEndIndex != -1)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
File.WriteAllText(Path.Combine(targetDirectory, "ManagingApi.ts"), codeApiClient);
|
File.WriteAllText(Path.Combine(targetWebAppDirectory, "ManagingApi.ts"), codeApiClient);
|
||||||
|
|
||||||
var settingsTypes = new TypeScriptClientGeneratorSettings
|
var settingsTypes = new TypeScriptClientGeneratorSettings
|
||||||
{
|
{
|
||||||
@@ -92,4 +95,6 @@ var settingsTypes = new TypeScriptClientGeneratorSettings
|
|||||||
|
|
||||||
var generatorTypes = new TypeScriptClientGenerator(document, settingsTypes);
|
var generatorTypes = new TypeScriptClientGenerator(document, settingsTypes);
|
||||||
var codeTypes = generatorTypes.GenerateFile();
|
var codeTypes = generatorTypes.GenerateFile();
|
||||||
File.WriteAllText(Path.Combine(targetDirectory, "ManagingApiTypes.ts"), codeTypes);
|
|
||||||
|
File.WriteAllText(Path.Combine(targetWebAppDirectory, "ManagingApiTypes.ts"), codeTypes);
|
||||||
|
File.WriteAllText(Path.Combine(targetWeb3ProxyDirectory, "ManagingApiTypes.ts"), codeTypes);
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
|
|
||||||
export interface Account {
|
export interface Account {
|
||||||
|
id?: number;
|
||||||
name: string;
|
name: string;
|
||||||
exchange: TradingExchanges;
|
exchange: TradingExchanges;
|
||||||
type: AccountType;
|
type: AccountType;
|
||||||
@@ -45,6 +46,9 @@ export interface User {
|
|||||||
agentName?: string | null;
|
agentName?: string | null;
|
||||||
avatarUrl?: string | null;
|
avatarUrl?: string | null;
|
||||||
telegramChannel?: string | null;
|
telegramChannel?: string | null;
|
||||||
|
ownerWalletAddress?: string | null;
|
||||||
|
isAdmin?: boolean;
|
||||||
|
lastConnectionDate?: Date | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Balance {
|
export interface Balance {
|
||||||
@@ -219,9 +223,100 @@ export interface SendTokenRequest {
|
|||||||
chainId?: number | null;
|
chainId?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExchangeApprovalStatus {
|
export interface ExchangeInitializedStatus {
|
||||||
exchange?: TradingExchanges;
|
exchange?: TradingExchanges;
|
||||||
isApproved?: boolean;
|
isInitialized?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginatedBundleBacktestRequestsResponse {
|
||||||
|
bundleRequests?: BundleBacktestRequestListItemResponse[];
|
||||||
|
totalCount?: number;
|
||||||
|
currentPage?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
totalPages?: number;
|
||||||
|
hasNextPage?: boolean;
|
||||||
|
hasPreviousPage?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BundleBacktestRequestListItemResponse {
|
||||||
|
requestId?: string;
|
||||||
|
name?: string;
|
||||||
|
version?: number;
|
||||||
|
status?: string;
|
||||||
|
createdAt?: Date;
|
||||||
|
completedAt?: Date | null;
|
||||||
|
updatedAt?: Date;
|
||||||
|
totalBacktests?: number;
|
||||||
|
completedBacktests?: number;
|
||||||
|
failedBacktests?: number;
|
||||||
|
progressPercentage?: number;
|
||||||
|
userId?: number | null;
|
||||||
|
userName?: string | null;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
currentBacktest?: string | null;
|
||||||
|
estimatedTimeRemainingSeconds?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BundleBacktestRequestSortableColumn {
|
||||||
|
RequestId = "RequestId",
|
||||||
|
Name = "Name",
|
||||||
|
Status = "Status",
|
||||||
|
CreatedAt = "CreatedAt",
|
||||||
|
CompletedAt = "CompletedAt",
|
||||||
|
TotalBacktests = "TotalBacktests",
|
||||||
|
CompletedBacktests = "CompletedBacktests",
|
||||||
|
FailedBacktests = "FailedBacktests",
|
||||||
|
ProgressPercentage = "ProgressPercentage",
|
||||||
|
UserId = "UserId",
|
||||||
|
UserName = "UserName",
|
||||||
|
UpdatedAt = "UpdatedAt",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BundleBacktestRequestStatus {
|
||||||
|
Pending = "Pending",
|
||||||
|
Running = "Running",
|
||||||
|
Completed = "Completed",
|
||||||
|
Failed = "Failed",
|
||||||
|
Cancelled = "Cancelled",
|
||||||
|
Saved = "Saved",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BundleBacktestRequestSummaryResponse {
|
||||||
|
statusSummary?: BundleBacktestRequestStatusSummary[];
|
||||||
|
totalRequests?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BundleBacktestRequestStatusSummary {
|
||||||
|
status?: string;
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginatedUsersResponse {
|
||||||
|
users?: UserListItemResponse[];
|
||||||
|
totalCount?: number;
|
||||||
|
currentPage?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
totalPages?: number;
|
||||||
|
hasNextPage?: boolean;
|
||||||
|
hasPreviousPage?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserListItemResponse {
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
agentName?: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
telegramChannel?: string;
|
||||||
|
ownerWalletAddress?: string;
|
||||||
|
isAdmin?: boolean;
|
||||||
|
lastConnectionDate?: Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum UserSortableColumn {
|
||||||
|
Id = "Id",
|
||||||
|
Name = "Name",
|
||||||
|
OwnerWalletAddress = "OwnerWalletAddress",
|
||||||
|
AgentName = "AgentName",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Backtest {
|
export interface Backtest {
|
||||||
@@ -233,17 +328,18 @@ export interface Backtest {
|
|||||||
config: TradingBotConfig;
|
config: TradingBotConfig;
|
||||||
positions: { [key: string]: Position; };
|
positions: { [key: string]: Position; };
|
||||||
signals: { [key: string]: LightSignal; };
|
signals: { [key: string]: LightSignal; };
|
||||||
candles: Candle[];
|
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
statistics: PerformanceMetrics;
|
statistics: PerformanceMetrics;
|
||||||
fees: number;
|
fees: number;
|
||||||
walletBalances: KeyValuePairOfDateTimeAndDecimal[];
|
|
||||||
user: User;
|
user: User;
|
||||||
score: number;
|
score: number;
|
||||||
requestId?: string;
|
requestId?: string;
|
||||||
metadata?: any | null;
|
metadata?: any | null;
|
||||||
scoreMessage?: string;
|
scoreMessage?: string;
|
||||||
|
initialBalance: number;
|
||||||
|
netPnl: number;
|
||||||
|
positionCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TradingBotConfig {
|
export interface TradingBotConfig {
|
||||||
@@ -253,7 +349,7 @@ export interface TradingBotConfig {
|
|||||||
timeframe: Timeframe;
|
timeframe: Timeframe;
|
||||||
isForWatchingOnly: boolean;
|
isForWatchingOnly: boolean;
|
||||||
botTradingBalance: number;
|
botTradingBalance: number;
|
||||||
isForBacktest: boolean;
|
tradingType: TradingType;
|
||||||
cooldownPeriod: number;
|
cooldownPeriod: number;
|
||||||
maxLossStreak: number;
|
maxLossStreak: number;
|
||||||
flipPosition: boolean;
|
flipPosition: boolean;
|
||||||
@@ -268,6 +364,9 @@ export interface TradingBotConfig {
|
|||||||
useForPositionSizing?: boolean;
|
useForPositionSizing?: boolean;
|
||||||
useForSignalFiltering?: boolean;
|
useForSignalFiltering?: boolean;
|
||||||
useForDynamicStopLoss?: boolean;
|
useForDynamicStopLoss?: boolean;
|
||||||
|
isForCopyTrading?: boolean;
|
||||||
|
masterBotIdentifier?: string | null;
|
||||||
|
masterBotUserId?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LightMoneyManagement {
|
export interface LightMoneyManagement {
|
||||||
@@ -288,6 +387,13 @@ export enum Timeframe {
|
|||||||
OneMinute = "OneMinute",
|
OneMinute = "OneMinute",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum TradingType {
|
||||||
|
Futures = "Futures",
|
||||||
|
BacktestFutures = "BacktestFutures",
|
||||||
|
BacktestSpot = "BacktestSpot",
|
||||||
|
Spot = "Spot",
|
||||||
|
}
|
||||||
|
|
||||||
export interface RiskManagement {
|
export interface RiskManagement {
|
||||||
adverseProbabilityThreshold: number;
|
adverseProbabilityThreshold: number;
|
||||||
favorableProbabilityThreshold: number;
|
favorableProbabilityThreshold: number;
|
||||||
@@ -327,9 +433,18 @@ export interface LightIndicator {
|
|||||||
slowPeriods?: number | null;
|
slowPeriods?: number | null;
|
||||||
signalPeriods?: number | null;
|
signalPeriods?: number | null;
|
||||||
multiplier?: number | null;
|
multiplier?: number | null;
|
||||||
|
stDev?: number | null;
|
||||||
smoothPeriods?: number | null;
|
smoothPeriods?: number | null;
|
||||||
stochPeriods?: number | null;
|
stochPeriods?: number | null;
|
||||||
cyclePeriods?: number | null;
|
cyclePeriods?: number | null;
|
||||||
|
kFactor?: number | null;
|
||||||
|
dFactor?: number | null;
|
||||||
|
tenkanPeriods?: number | null;
|
||||||
|
kijunPeriods?: number | null;
|
||||||
|
senkouBPeriods?: number | null;
|
||||||
|
offsetPeriods?: number | null;
|
||||||
|
senkouOffset?: number | null;
|
||||||
|
chikouOffset?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum IndicatorType {
|
export enum IndicatorType {
|
||||||
@@ -343,11 +458,15 @@ export enum IndicatorType {
|
|||||||
EmaTrend = "EmaTrend",
|
EmaTrend = "EmaTrend",
|
||||||
Composite = "Composite",
|
Composite = "Composite",
|
||||||
StochRsiTrend = "StochRsiTrend",
|
StochRsiTrend = "StochRsiTrend",
|
||||||
|
StochasticCross = "StochasticCross",
|
||||||
Stc = "Stc",
|
Stc = "Stc",
|
||||||
StDev = "StDev",
|
StDev = "StDev",
|
||||||
LaggingStc = "LaggingStc",
|
LaggingStc = "LaggingStc",
|
||||||
SuperTrendCrossEma = "SuperTrendCrossEma",
|
SuperTrendCrossEma = "SuperTrendCrossEma",
|
||||||
DualEmaCross = "DualEmaCross",
|
DualEmaCross = "DualEmaCross",
|
||||||
|
BollingerBandsPercentBMomentumBreakout = "BollingerBandsPercentBMomentumBreakout",
|
||||||
|
BollingerBandsVolatilityProtection = "BollingerBandsVolatilityProtection",
|
||||||
|
IchimokuKumoTrend = "IchimokuKumoTrend",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SignalType {
|
export enum SignalType {
|
||||||
@@ -357,8 +476,8 @@ export enum SignalType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Position {
|
export interface Position {
|
||||||
accountName: string;
|
|
||||||
date: Date;
|
date: Date;
|
||||||
|
accountId: number;
|
||||||
originDirection: TradeDirection;
|
originDirection: TradeDirection;
|
||||||
ticker: Ticker;
|
ticker: Ticker;
|
||||||
moneyManagement: LightMoneyManagement;
|
moneyManagement: LightMoneyManagement;
|
||||||
@@ -367,12 +486,16 @@ export interface Position {
|
|||||||
TakeProfit1: Trade;
|
TakeProfit1: Trade;
|
||||||
TakeProfit2?: Trade | null;
|
TakeProfit2?: Trade | null;
|
||||||
ProfitAndLoss?: ProfitAndLoss | null;
|
ProfitAndLoss?: ProfitAndLoss | null;
|
||||||
|
uiFees?: number;
|
||||||
|
gasFees?: number;
|
||||||
status: PositionStatus;
|
status: PositionStatus;
|
||||||
signalIdentifier?: string | null;
|
signalIdentifier?: string | null;
|
||||||
identifier: string;
|
identifier: string;
|
||||||
initiator: PositionInitiator;
|
initiator: PositionInitiator;
|
||||||
user: User;
|
user: User;
|
||||||
initiatorIdentifier: string;
|
initiatorIdentifier: string;
|
||||||
|
recoveryAttempted?: boolean;
|
||||||
|
tradingType?: TradingType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TradeDirection {
|
export enum TradeDirection {
|
||||||
@@ -430,7 +553,6 @@ export enum PositionStatus {
|
|||||||
Canceled = "Canceled",
|
Canceled = "Canceled",
|
||||||
Rejected = "Rejected",
|
Rejected = "Rejected",
|
||||||
Updating = "Updating",
|
Updating = "Updating",
|
||||||
PartiallyFilled = "PartiallyFilled",
|
|
||||||
Filled = "Filled",
|
Filled = "Filled",
|
||||||
Flipped = "Flipped",
|
Flipped = "Flipped",
|
||||||
Finished = "Finished",
|
Finished = "Finished",
|
||||||
@@ -495,25 +617,10 @@ export interface PerformanceMetrics {
|
|||||||
totalPnL?: number;
|
totalPnL?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyValuePairOfDateTimeAndDecimal {
|
|
||||||
key?: Date;
|
|
||||||
value?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DeleteBacktestsRequest {
|
export interface DeleteBacktestsRequest {
|
||||||
backtestIds: string[];
|
backtestIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaginatedBacktestsResponse {
|
|
||||||
backtests?: LightBacktestResponse[] | null;
|
|
||||||
totalCount?: number;
|
|
||||||
currentPage?: number;
|
|
||||||
pageSize?: number;
|
|
||||||
totalPages?: number;
|
|
||||||
hasNextPage?: boolean;
|
|
||||||
hasPreviousPage?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LightBacktestResponse {
|
export interface LightBacktestResponse {
|
||||||
id: string;
|
id: string;
|
||||||
config: TradingBotConfig;
|
config: TradingBotConfig;
|
||||||
@@ -528,6 +635,36 @@ export interface LightBacktestResponse {
|
|||||||
sharpeRatio: number;
|
sharpeRatio: number;
|
||||||
score: number;
|
score: number;
|
||||||
scoreMessage: string;
|
scoreMessage: string;
|
||||||
|
initialBalance: number;
|
||||||
|
netPnl: number;
|
||||||
|
positionCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginatedBacktestsResponse {
|
||||||
|
backtests?: LightBacktestResponse[] | null;
|
||||||
|
totalCount?: number;
|
||||||
|
currentPage?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
totalPages?: number;
|
||||||
|
hasNextPage?: boolean;
|
||||||
|
hasPreviousPage?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BacktestSortableColumn {
|
||||||
|
Score = "Score",
|
||||||
|
FinalPnl = "FinalPnl",
|
||||||
|
NetPnl = "NetPnl",
|
||||||
|
WinRate = "WinRate",
|
||||||
|
GrowthPercentage = "GrowthPercentage",
|
||||||
|
HodlPercentage = "HodlPercentage",
|
||||||
|
Duration = "Duration",
|
||||||
|
Timeframe = "Timeframe",
|
||||||
|
IndicatorsCount = "IndicatorsCount",
|
||||||
|
MaxDrawdown = "MaxDrawdown",
|
||||||
|
Fees = "Fees",
|
||||||
|
SharpeRatio = "SharpeRatio",
|
||||||
|
Ticker = "Ticker",
|
||||||
|
Name = "Name",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LightBacktest {
|
export interface LightBacktest {
|
||||||
@@ -544,18 +681,27 @@ export interface LightBacktest {
|
|||||||
sharpeRatio?: number | null;
|
sharpeRatio?: number | null;
|
||||||
score?: number;
|
score?: number;
|
||||||
scoreMessage?: string | null;
|
scoreMessage?: string | null;
|
||||||
|
metadata?: any | null;
|
||||||
|
ticker?: string | null;
|
||||||
|
initialBalance?: number;
|
||||||
|
netPnl?: number;
|
||||||
|
positionCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RunBacktestRequest {
|
export interface RunBacktestRequest {
|
||||||
config?: TradingBotConfigRequest | null;
|
config?: TradingBotConfigRequest | null;
|
||||||
startDate?: Date;
|
startDate?: Date;
|
||||||
endDate?: Date;
|
endDate?: Date;
|
||||||
|
balance?: number;
|
||||||
|
watchOnly?: boolean;
|
||||||
save?: boolean;
|
save?: boolean;
|
||||||
withCandles?: boolean;
|
withCandles?: boolean;
|
||||||
|
moneyManagementName?: string | null;
|
||||||
|
moneyManagement?: MoneyManagement | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TradingBotConfigRequest {
|
export interface TradingBotConfigRequest {
|
||||||
accountName: string;
|
accountName?: string | null;
|
||||||
ticker: Ticker;
|
ticker: Ticker;
|
||||||
timeframe: Timeframe;
|
timeframe: Timeframe;
|
||||||
isForWatchingOnly: boolean;
|
isForWatchingOnly: boolean;
|
||||||
@@ -575,6 +721,7 @@ export interface TradingBotConfigRequest {
|
|||||||
useForPositionSizing?: boolean;
|
useForPositionSizing?: boolean;
|
||||||
useForSignalFiltering?: boolean;
|
useForSignalFiltering?: boolean;
|
||||||
useForDynamicStopLoss?: boolean;
|
useForDynamicStopLoss?: boolean;
|
||||||
|
tradingType?: TradingType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScenarioRequest {
|
export interface ScenarioRequest {
|
||||||
@@ -593,9 +740,18 @@ export interface IndicatorRequest {
|
|||||||
slowPeriods?: number | null;
|
slowPeriods?: number | null;
|
||||||
signalPeriods?: number | null;
|
signalPeriods?: number | null;
|
||||||
multiplier?: number | null;
|
multiplier?: number | null;
|
||||||
|
stDev?: number | null;
|
||||||
smoothPeriods?: number | null;
|
smoothPeriods?: number | null;
|
||||||
stochPeriods?: number | null;
|
stochPeriods?: number | null;
|
||||||
cyclePeriods?: number | null;
|
cyclePeriods?: number | null;
|
||||||
|
kFactor?: number | null;
|
||||||
|
dFactor?: number | null;
|
||||||
|
tenkanPeriods?: number | null;
|
||||||
|
kijunPeriods?: number | null;
|
||||||
|
senkouBPeriods?: number | null;
|
||||||
|
offsetPeriods?: number | null;
|
||||||
|
senkouOffset?: number | null;
|
||||||
|
chikouOffset?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MoneyManagementRequest {
|
export interface MoneyManagementRequest {
|
||||||
@@ -606,6 +762,10 @@ export interface MoneyManagementRequest {
|
|||||||
leverage: number;
|
leverage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MoneyManagement extends LightMoneyManagement {
|
||||||
|
user?: User | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface BundleBacktestRequest {
|
export interface BundleBacktestRequest {
|
||||||
requestId: string;
|
requestId: string;
|
||||||
user: User;
|
user: User;
|
||||||
@@ -613,8 +773,76 @@ export interface BundleBacktestRequest {
|
|||||||
completedAt?: Date | null;
|
completedAt?: Date | null;
|
||||||
status: BundleBacktestRequestStatus;
|
status: BundleBacktestRequestStatus;
|
||||||
name: string;
|
name: string;
|
||||||
backtestRequestsJson: string;
|
version: number;
|
||||||
results?: string[] | null;
|
universalConfigJson: string;
|
||||||
|
dateTimeRangesJson: string;
|
||||||
|
moneyManagementVariantsJson: string;
|
||||||
|
tickerVariantsJson: string;
|
||||||
|
results?: string[];
|
||||||
|
totalBacktests: number;
|
||||||
|
completedBacktests: number;
|
||||||
|
failedBacktests: number;
|
||||||
|
progressPercentage?: number;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
progressInfo?: string | null;
|
||||||
|
currentBacktest?: string | null;
|
||||||
|
estimatedTimeRemainingSeconds?: number | null;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RunBundleBacktestRequest {
|
||||||
|
name: string;
|
||||||
|
universalConfig: BundleBacktestUniversalConfig;
|
||||||
|
dateTimeRanges: DateTimeRange[];
|
||||||
|
moneyManagementVariants: MoneyManagementVariant[];
|
||||||
|
tickerVariants: Ticker[];
|
||||||
|
saveAsTemplate: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BundleBacktestUniversalConfig {
|
||||||
|
timeframe: Timeframe;
|
||||||
|
isForWatchingOnly: boolean;
|
||||||
|
botTradingBalance: number;
|
||||||
|
botName: string;
|
||||||
|
tradingType: TradingType;
|
||||||
|
flipPosition: boolean;
|
||||||
|
cooldownPeriod?: number | null;
|
||||||
|
maxLossStreak?: number;
|
||||||
|
scenario?: ScenarioRequest | null;
|
||||||
|
scenarioName?: string | null;
|
||||||
|
maxPositionTimeHours?: number | null;
|
||||||
|
closeEarlyWhenProfitable?: boolean;
|
||||||
|
flipOnlyWhenInProfit?: boolean;
|
||||||
|
useSynthApi?: boolean;
|
||||||
|
useForPositionSizing?: boolean;
|
||||||
|
useForSignalFiltering?: boolean;
|
||||||
|
useForDynamicStopLoss?: boolean;
|
||||||
|
watchOnly?: boolean;
|
||||||
|
save?: boolean;
|
||||||
|
withCandles?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DateTimeRange {
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MoneyManagementVariant {
|
||||||
|
moneyManagement?: MoneyManagementRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BundleBacktestRequestViewModel {
|
||||||
|
requestId: string;
|
||||||
|
createdAt: Date;
|
||||||
|
completedAt?: Date | null;
|
||||||
|
status: BundleBacktestRequestStatus;
|
||||||
|
name: string;
|
||||||
|
version: number;
|
||||||
|
universalConfig: BundleBacktestUniversalConfig;
|
||||||
|
dateTimeRanges: DateTimeRange[];
|
||||||
|
moneyManagementVariants: MoneyManagementVariant[];
|
||||||
|
tickerVariants: Ticker[];
|
||||||
|
results?: string[];
|
||||||
totalBacktests: number;
|
totalBacktests: number;
|
||||||
completedBacktests: number;
|
completedBacktests: number;
|
||||||
failedBacktests: number;
|
failedBacktests: number;
|
||||||
@@ -625,17 +853,18 @@ export interface BundleBacktestRequest {
|
|||||||
estimatedTimeRemainingSeconds?: number | null;
|
estimatedTimeRemainingSeconds?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BundleBacktestRequestStatus {
|
export interface BundleBacktestStatusResponse {
|
||||||
Pending = "Pending",
|
bundleRequestId?: string;
|
||||||
Running = "Running",
|
status?: string | null;
|
||||||
Completed = "Completed",
|
totalJobs?: number;
|
||||||
Failed = "Failed",
|
completedJobs?: number;
|
||||||
Cancelled = "Cancelled",
|
failedJobs?: number;
|
||||||
}
|
runningJobs?: number;
|
||||||
|
pendingJobs?: number;
|
||||||
export interface RunBundleBacktestRequest {
|
progressPercentage?: number;
|
||||||
name: string;
|
createdAt?: Date;
|
||||||
requests: RunBacktestRequest[];
|
completedAt?: Date | null;
|
||||||
|
errorMessage?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GeneticRequest {
|
export interface GeneticRequest {
|
||||||
@@ -726,14 +955,15 @@ export interface RunGeneticRequest {
|
|||||||
eligibleIndicators?: IndicatorType[] | null;
|
eligibleIndicators?: IndicatorType[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MoneyManagement extends LightMoneyManagement {
|
|
||||||
user?: User | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StartBotRequest {
|
export interface StartBotRequest {
|
||||||
config?: TradingBotConfigRequest | null;
|
config?: TradingBotConfigRequest | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StartCopyTradingRequest {
|
||||||
|
masterBotIdentifier?: string;
|
||||||
|
botTradingBalance?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SaveBotRequest extends StartBotRequest {
|
export interface SaveBotRequest extends StartBotRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,12 +980,14 @@ export interface TradingBotResponse {
|
|||||||
candles: Candle[];
|
candles: Candle[];
|
||||||
winRate: number;
|
winRate: number;
|
||||||
profitAndLoss: number;
|
profitAndLoss: number;
|
||||||
|
roi: number;
|
||||||
identifier: string;
|
identifier: string;
|
||||||
agentName: string;
|
agentName: string;
|
||||||
createDate: Date;
|
createDate: Date;
|
||||||
startupTime: Date;
|
startupTime: Date;
|
||||||
name: string;
|
name: string;
|
||||||
ticker: Ticker;
|
ticker: Ticker;
|
||||||
|
masterAgentName?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaginatedResponseOfTradingBotResponse {
|
export interface PaginatedResponseOfTradingBotResponse {
|
||||||
@@ -768,7 +1000,19 @@ export interface PaginatedResponseOfTradingBotResponse {
|
|||||||
hasNextPage?: boolean;
|
hasNextPage?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OpenPositionManuallyRequest {
|
export enum BotSortableColumn {
|
||||||
|
CreateDate = "CreateDate",
|
||||||
|
Name = "Name",
|
||||||
|
Ticker = "Ticker",
|
||||||
|
Status = "Status",
|
||||||
|
StartupTime = "StartupTime",
|
||||||
|
Roi = "Roi",
|
||||||
|
Pnl = "Pnl",
|
||||||
|
WinRate = "WinRate",
|
||||||
|
AgentName = "AgentName",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateManualSignalRequest {
|
||||||
identifier?: string;
|
identifier?: string;
|
||||||
direction?: TradeDirection;
|
direction?: TradeDirection;
|
||||||
}
|
}
|
||||||
@@ -788,6 +1032,7 @@ export interface UpdateBotConfigRequest {
|
|||||||
export interface TickerInfos {
|
export interface TickerInfos {
|
||||||
ticker?: Ticker;
|
ticker?: Ticker;
|
||||||
imageUrl?: string | null;
|
imageUrl?: string | null;
|
||||||
|
name?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpotlightOverview {
|
export interface SpotlightOverview {
|
||||||
@@ -819,9 +1064,18 @@ export interface IndicatorBase {
|
|||||||
slowPeriods?: number | null;
|
slowPeriods?: number | null;
|
||||||
signalPeriods?: number | null;
|
signalPeriods?: number | null;
|
||||||
multiplier?: number | null;
|
multiplier?: number | null;
|
||||||
|
stDev?: number | null;
|
||||||
smoothPeriods?: number | null;
|
smoothPeriods?: number | null;
|
||||||
stochPeriods?: number | null;
|
stochPeriods?: number | null;
|
||||||
cyclePeriods?: number | null;
|
cyclePeriods?: number | null;
|
||||||
|
kFactor?: number | null;
|
||||||
|
dFactor?: number | null;
|
||||||
|
tenkanPeriods?: number | null;
|
||||||
|
kijunPeriods?: number | null;
|
||||||
|
senkouBPeriods?: number | null;
|
||||||
|
offsetPeriods?: number | null;
|
||||||
|
senkouOffset?: number | null;
|
||||||
|
chikouOffset?: number | null;
|
||||||
user?: User | null;
|
user?: User | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -853,6 +1107,7 @@ export interface IndicatorsResultBase {
|
|||||||
stdDev?: StdDevResult[] | null;
|
stdDev?: StdDevResult[] | null;
|
||||||
superTrend?: SuperTrendResult[] | null;
|
superTrend?: SuperTrendResult[] | null;
|
||||||
chandelierLong?: ChandelierResult[] | null;
|
chandelierLong?: ChandelierResult[] | null;
|
||||||
|
ichimoku?: IchimokuResult[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResultBase {
|
export interface ResultBase {
|
||||||
@@ -929,6 +1184,14 @@ export interface SuperTrendResult extends ResultBase {
|
|||||||
lowerBand?: number | null;
|
lowerBand?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IchimokuResult extends ResultBase {
|
||||||
|
tenkanSen?: number | null;
|
||||||
|
kijunSen?: number | null;
|
||||||
|
senkouSpanA?: number | null;
|
||||||
|
senkouSpanB?: number | null;
|
||||||
|
chikouSpan?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface GetCandlesWithIndicatorsRequest {
|
export interface GetCandlesWithIndicatorsRequest {
|
||||||
ticker?: Ticker;
|
ticker?: Ticker;
|
||||||
startDate?: Date;
|
startDate?: Date;
|
||||||
@@ -949,6 +1212,7 @@ export interface TopStrategiesViewModel {
|
|||||||
export interface StrategyPerformance {
|
export interface StrategyPerformance {
|
||||||
strategyName?: string | null;
|
strategyName?: string | null;
|
||||||
pnL?: number;
|
pnL?: number;
|
||||||
|
netPnL?: number;
|
||||||
agentName?: string | null;
|
agentName?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -960,65 +1224,75 @@ export interface StrategyRoiPerformance {
|
|||||||
strategyName?: string | null;
|
strategyName?: string | null;
|
||||||
roi?: number;
|
roi?: number;
|
||||||
pnL?: number;
|
pnL?: number;
|
||||||
|
netPnL?: number;
|
||||||
volume?: number;
|
volume?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TopAgentsByPnLViewModel {
|
|
||||||
topAgentsByPnL?: AgentPerformance[] | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AgentPerformance {
|
|
||||||
agentName?: string | null;
|
|
||||||
pnL?: number;
|
|
||||||
totalROI?: number;
|
|
||||||
totalVolume?: number;
|
|
||||||
activeStrategiesCount?: number;
|
|
||||||
totalBalance?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserStrategyDetailsViewModel {
|
export interface UserStrategyDetailsViewModel {
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
state?: BotStatus;
|
state?: BotStatus;
|
||||||
pnL?: number;
|
pnL?: number;
|
||||||
|
netPnL?: number;
|
||||||
roiPercentage?: number;
|
roiPercentage?: number;
|
||||||
roiLast24H?: number;
|
runtime?: Date | null;
|
||||||
runtime?: Date;
|
totalRuntimeSeconds?: number;
|
||||||
|
lastStartTime?: Date | null;
|
||||||
|
lastStopTime?: Date | null;
|
||||||
|
accumulatedRunTimeSeconds?: number;
|
||||||
winRate?: number;
|
winRate?: number;
|
||||||
totalVolumeTraded?: number;
|
totalVolumeTraded?: number;
|
||||||
volumeLast24H?: number;
|
volumeLast24H?: number;
|
||||||
wins?: number;
|
wins?: number;
|
||||||
losses?: number;
|
losses?: number;
|
||||||
positions?: Position[] | null;
|
positions?: PositionViewModel[] | null;
|
||||||
identifier?: string;
|
identifier?: string;
|
||||||
walletBalances?: { [key: string]: number; } | null;
|
walletBalances?: { [key: string]: number; } | null;
|
||||||
ticker?: Ticker;
|
ticker?: Ticker;
|
||||||
|
masterAgentName?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PositionViewModel {
|
||||||
|
date: Date;
|
||||||
|
accountId: number;
|
||||||
|
originDirection: TradeDirection;
|
||||||
|
ticker: Ticker;
|
||||||
|
Open: Trade;
|
||||||
|
StopLoss: Trade;
|
||||||
|
TakeProfit1: Trade;
|
||||||
|
ProfitAndLoss?: ProfitAndLoss | null;
|
||||||
|
uiFees?: number;
|
||||||
|
gasFees?: number;
|
||||||
|
status: PositionStatus;
|
||||||
|
signalIdentifier?: string | null;
|
||||||
|
identifier: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlatformSummaryViewModel {
|
export interface PlatformSummaryViewModel {
|
||||||
|
lastUpdated?: Date;
|
||||||
|
lastSnapshot?: Date;
|
||||||
|
hasPendingChanges?: boolean;
|
||||||
totalAgents?: number;
|
totalAgents?: number;
|
||||||
totalActiveStrategies?: number;
|
totalActiveStrategies?: number;
|
||||||
totalPlatformPnL?: number;
|
totalPlatformPnL?: number;
|
||||||
totalPlatformVolume?: number;
|
totalPlatformVolume?: number;
|
||||||
totalPlatformVolumeLast24h?: number;
|
openInterest?: number;
|
||||||
totalOpenInterest?: number;
|
|
||||||
totalPositionCount?: number;
|
totalPositionCount?: number;
|
||||||
agentsChange24h?: number;
|
totalPlatformFees?: number;
|
||||||
strategiesChange24h?: number;
|
dailySnapshots?: DailySnapshot[] | null;
|
||||||
pnLChange24h?: number;
|
|
||||||
volumeChange24h?: number;
|
|
||||||
openInterestChange24h?: number;
|
|
||||||
positionCountChange24h?: number;
|
|
||||||
volumeByAsset?: { [key in keyof typeof Ticker]?: number; } | null;
|
volumeByAsset?: { [key in keyof typeof Ticker]?: number; } | null;
|
||||||
positionCountByAsset?: { [key in keyof typeof Ticker]?: number; } | null;
|
positionCountByAsset?: { [key in keyof typeof Ticker]?: number; } | null;
|
||||||
positionCountByDirection?: { [key in keyof typeof TradeDirection]?: number; } | null;
|
positionCountByDirection?: { [key in keyof typeof TradeDirection]?: number; } | null;
|
||||||
lastUpdated?: Date;
|
|
||||||
last24HourSnapshot?: Date;
|
|
||||||
volumeHistory?: VolumeHistoryPoint[] | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VolumeHistoryPoint {
|
export interface DailySnapshot {
|
||||||
date?: Date;
|
date?: Date;
|
||||||
volume?: number;
|
totalAgents?: number;
|
||||||
|
totalStrategies?: number;
|
||||||
|
totalVolume?: number;
|
||||||
|
totalPnL?: number;
|
||||||
|
netPnL?: number;
|
||||||
|
totalOpenInterest?: number;
|
||||||
|
totalPositionCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PaginatedAgentIndexResponse {
|
export interface PaginatedAgentIndexResponse {
|
||||||
@@ -1038,16 +1312,19 @@ export interface PaginatedAgentIndexResponse {
|
|||||||
export interface AgentSummaryViewModel {
|
export interface AgentSummaryViewModel {
|
||||||
agentName?: string | null;
|
agentName?: string | null;
|
||||||
totalPnL?: number;
|
totalPnL?: number;
|
||||||
|
netPnL?: number;
|
||||||
totalROI?: number;
|
totalROI?: number;
|
||||||
wins?: number;
|
wins?: number;
|
||||||
losses?: number;
|
losses?: number;
|
||||||
activeStrategiesCount?: number;
|
activeStrategiesCount?: number;
|
||||||
totalVolume?: number;
|
totalVolume?: number;
|
||||||
totalBalance?: number;
|
totalBalance?: number;
|
||||||
|
totalFees?: number;
|
||||||
|
backtestCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SortableFields {
|
export enum SortableFields {
|
||||||
TotalPnL = "TotalPnL",
|
NetPnL = "NetPnL",
|
||||||
TotalROI = "TotalROI",
|
TotalROI = "TotalROI",
|
||||||
Wins = "Wins",
|
Wins = "Wins",
|
||||||
Losses = "Losses",
|
Losses = "Losses",
|
||||||
@@ -1059,25 +1336,82 @@ export enum SortableFields {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentBalanceHistory {
|
export interface AgentBalanceHistory {
|
||||||
|
userId?: number;
|
||||||
agentName?: string | null;
|
agentName?: string | null;
|
||||||
agentBalances?: AgentBalance[] | null;
|
agentBalances?: AgentBalance[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentBalance {
|
export interface AgentBalance {
|
||||||
agentName?: string | null;
|
userId?: number;
|
||||||
totalValue?: number;
|
totalBalanceValue?: number;
|
||||||
totalAccountUsdValue?: number;
|
usdcWalletValue?: number;
|
||||||
|
usdcInPositionsValue?: number;
|
||||||
botsAllocationUsdValue?: number;
|
botsAllocationUsdValue?: number;
|
||||||
pnL?: number;
|
pnL?: number;
|
||||||
time?: Date;
|
time?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BestAgentsResponse {
|
export interface JobStatusResponse {
|
||||||
agents?: AgentBalanceHistory[] | null;
|
jobId?: string;
|
||||||
|
status?: string | null;
|
||||||
|
progressPercentage?: number;
|
||||||
|
createdAt?: Date;
|
||||||
|
startedAt?: Date | null;
|
||||||
|
completedAt?: Date | null;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
result?: LightBacktest | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginatedJobsResponse {
|
||||||
|
jobs?: JobListItemResponse[];
|
||||||
totalCount?: number;
|
totalCount?: number;
|
||||||
currentPage?: number;
|
currentPage?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
totalPages?: number;
|
totalPages?: number;
|
||||||
|
hasNextPage?: boolean;
|
||||||
|
hasPreviousPage?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JobListItemResponse {
|
||||||
|
jobId?: string;
|
||||||
|
status?: string;
|
||||||
|
jobType?: string;
|
||||||
|
progressPercentage?: number;
|
||||||
|
priority?: number;
|
||||||
|
userId?: number;
|
||||||
|
bundleRequestId?: string | null;
|
||||||
|
geneticRequestId?: string | null;
|
||||||
|
assignedWorkerId?: string | null;
|
||||||
|
createdAt?: Date;
|
||||||
|
startedAt?: Date | null;
|
||||||
|
completedAt?: Date | null;
|
||||||
|
lastHeartbeat?: Date | null;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
startDate?: Date;
|
||||||
|
endDate?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JobSummaryResponse {
|
||||||
|
statusSummary?: JobStatusSummary[];
|
||||||
|
jobTypeSummary?: JobTypeSummary[];
|
||||||
|
statusTypeSummary?: JobStatusTypeSummary[];
|
||||||
|
totalJobs?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JobStatusSummary {
|
||||||
|
status?: string;
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JobTypeSummary {
|
||||||
|
jobType?: string;
|
||||||
|
count?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JobStatusTypeSummary {
|
||||||
|
status?: string;
|
||||||
|
jobType?: string;
|
||||||
|
count?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScenarioViewModel {
|
export interface ScenarioViewModel {
|
||||||
@@ -1120,11 +1454,69 @@ export interface PrivyInitAddressResponse {
|
|||||||
isAlreadyInitialized?: boolean;
|
isAlreadyInitialized?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IndicatorRequestDto {
|
||||||
|
indicatorName: string;
|
||||||
|
strategyDescription: string;
|
||||||
|
documentationUrl?: string | null;
|
||||||
|
imageUrl?: string | null;
|
||||||
|
requesterName: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface LoginRequest {
|
export interface LoginRequest {
|
||||||
name: string;
|
name: string;
|
||||||
address: string;
|
address: string;
|
||||||
signature: string;
|
signature: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
ownerWalletAddress?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaginatedWhitelistAccountsResponse {
|
||||||
|
accounts?: WhitelistAccount[] | null;
|
||||||
|
totalCount?: number;
|
||||||
|
pageNumber?: number;
|
||||||
|
pageSize?: number;
|
||||||
|
totalPages?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WhitelistAccount {
|
||||||
|
id?: number;
|
||||||
|
privyId?: string | null;
|
||||||
|
privyCreationDate?: Date;
|
||||||
|
embeddedWallet?: string | null;
|
||||||
|
externalEthereumAccount?: string | null;
|
||||||
|
twitterAccount?: string | null;
|
||||||
|
isWhitelisted?: boolean;
|
||||||
|
createdAt?: Date;
|
||||||
|
updatedAt?: Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrivyWebhookDto {
|
||||||
|
type?: string | null;
|
||||||
|
user?: PrivyUserDto | null;
|
||||||
|
wallet?: PrivyWalletDto | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrivyUserDto {
|
||||||
|
created_at?: number;
|
||||||
|
has_accepted_terms?: boolean;
|
||||||
|
id?: string | null;
|
||||||
|
is_guest?: boolean;
|
||||||
|
linked_accounts?: PrivyLinkedAccountDto[] | null;
|
||||||
|
mfa_methods?: any[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrivyLinkedAccountDto {
|
||||||
|
address?: string | null;
|
||||||
|
first_verified_at?: number | null;
|
||||||
|
latest_verified_at?: number | null;
|
||||||
|
type?: string | null;
|
||||||
|
verified_at?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrivyWalletDto {
|
||||||
|
type?: string | null;
|
||||||
|
address?: string | null;
|
||||||
|
chain_type?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileResponse {
|
export interface FileResponse {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
TradeStatus,
|
TradeStatus,
|
||||||
TradeType
|
TradeType
|
||||||
} from '../../generated/ManagingApiTypes.js';
|
} from '../../generated/ManagingApiTypes.js';
|
||||||
|
import {TradeActionType} from '../../generated/gmxsdk/types/tradeHistory.js';
|
||||||
|
|
||||||
// Cache implementation for markets info data
|
// Cache implementation for markets info data
|
||||||
interface CacheEntry {
|
interface CacheEntry {
|
||||||
@@ -1532,6 +1533,134 @@ export const getPositionHistoryImpl = async (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation function to get spot position (swap) history on GMX
|
||||||
|
* This returns swap executions (MarketSwap) so we can reconcile spot positions.
|
||||||
|
*/
|
||||||
|
export const getSpotPositionHistoryImpl = async (
|
||||||
|
sdk: GmxSdk,
|
||||||
|
pageIndex: number = 0,
|
||||||
|
pageSize: number = 20,
|
||||||
|
ticker?: string,
|
||||||
|
fromDateTime?: string,
|
||||||
|
toDateTime?: string
|
||||||
|
): Promise<Position[]> => {
|
||||||
|
return executeWithFallback(
|
||||||
|
async (sdk, retryCount) => {
|
||||||
|
const fromTimestamp = fromDateTime ? Math.floor(new Date(fromDateTime).getTime() / 1000) : undefined;
|
||||||
|
const toTimestamp = toDateTime ? Math.floor(new Date(toDateTime).getTime() / 1000) : undefined;
|
||||||
|
|
||||||
|
const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk);
|
||||||
|
|
||||||
|
if (!marketsInfoData || !tokensData) {
|
||||||
|
throw new Error("No markets or tokens info data");
|
||||||
|
}
|
||||||
|
|
||||||
|
const tradeActions = await sdk.trades.getTradeHistory({
|
||||||
|
pageIndex,
|
||||||
|
pageSize,
|
||||||
|
fromTxTimestamp: fromTimestamp,
|
||||||
|
toTxTimestamp: toTimestamp,
|
||||||
|
marketsInfoData,
|
||||||
|
tokensData,
|
||||||
|
marketsDirectionsFilter: undefined,
|
||||||
|
orderEventCombinations: [{
|
||||||
|
eventName: TradeActionType.OrderExecuted,
|
||||||
|
orderType: OrderType.MarketSwap,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
let positions: Position[] = [];
|
||||||
|
|
||||||
|
for (const action of tradeActions) {
|
||||||
|
console.log(`📊 Action:`, action);
|
||||||
|
// Some swap actions don't carry marketInfo; derive from target/initial collateral token instead.
|
||||||
|
const initialToken = (action as any).initialCollateralToken;
|
||||||
|
const targetToken = (action as any).targetCollateralToken || initialToken;
|
||||||
|
if (!targetToken && !initialToken) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetSymbol = targetToken?.symbol || 'UNKNOWN';
|
||||||
|
const initialSymbol = initialToken?.symbol || targetSymbol;
|
||||||
|
const isSellToUsdc = (targetSymbol?.toUpperCase?.() === 'USDC');
|
||||||
|
const direction = isSellToUsdc ? TradeDirection.Short : TradeDirection.Long;
|
||||||
|
|
||||||
|
const targetDecimals = targetToken?.decimals ?? 18;
|
||||||
|
const initialDecimals = initialToken?.decimals ?? 18;
|
||||||
|
|
||||||
|
const executionAmountOut = (action as any).executionAmountOut ?? 0n; // output (target token)
|
||||||
|
const initialAmount = (action as any).initialCollateralDeltaAmount ?? 0n; // input (initial token)
|
||||||
|
|
||||||
|
const outputQty = Number(executionAmountOut) / Math.pow(10, targetDecimals);
|
||||||
|
const inputQty = Number(initialAmount) / Math.pow(10, initialDecimals);
|
||||||
|
|
||||||
|
// Base ticker & quantity: Long -> target token received; Short -> initial token sold
|
||||||
|
const baseSymbol = direction === TradeDirection.Short ? initialSymbol : targetSymbol;
|
||||||
|
const tickerSymbol = (ticker as any) || baseSymbol;
|
||||||
|
const quantity = direction === TradeDirection.Short ? inputQty : outputQty;
|
||||||
|
|
||||||
|
let price = 0;
|
||||||
|
if (quantity > 0) {
|
||||||
|
// Quote per base: input value / base qty (Long: USDC/BTC; Short: USDC/BTC)
|
||||||
|
const numerator = direction === TradeDirection.Short ? outputQty : inputQty;
|
||||||
|
price = numerator / quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pnlUsd = (action as any).pnlUsd ? Number((action as any).pnlUsd) / 1e30 : 0;
|
||||||
|
|
||||||
|
const timestampSec = (action as any).timestamp || 0;
|
||||||
|
const date = new Date(Number(timestampSec) * 1000);
|
||||||
|
const exchangeOrderId = action.transaction?.hash || (action as any).id;
|
||||||
|
|
||||||
|
const position: Position = {
|
||||||
|
ticker: tickerSymbol as any,
|
||||||
|
direction,
|
||||||
|
price,
|
||||||
|
quantity,
|
||||||
|
leverage: 1,
|
||||||
|
status: PositionStatus.Finished,
|
||||||
|
tradeType: TradeType.Market,
|
||||||
|
date,
|
||||||
|
exchangeOrderId,
|
||||||
|
pnl: pnlUsd,
|
||||||
|
ProfitAndLoss: {
|
||||||
|
realized: pnlUsd,
|
||||||
|
net: pnlUsd,
|
||||||
|
averageOpenPrice: undefined
|
||||||
|
},
|
||||||
|
Open: {
|
||||||
|
ticker: tickerSymbol as any,
|
||||||
|
direction,
|
||||||
|
price,
|
||||||
|
quantity,
|
||||||
|
leverage: 1,
|
||||||
|
status: TradeStatus.Filled,
|
||||||
|
tradeType: TradeType.Market,
|
||||||
|
date,
|
||||||
|
exchangeOrderId,
|
||||||
|
fee: 0,
|
||||||
|
message: "Spot swap execution"
|
||||||
|
} as Trade
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
positions.push(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`📊 Positions:`, positions);
|
||||||
|
|
||||||
|
if (ticker) {
|
||||||
|
positions = positions.filter(p =>
|
||||||
|
p.ticker === (ticker as any) ||
|
||||||
|
p.Open?.ticker === (ticker as any)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return positions;
|
||||||
|
}, sdk, 0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation function to get positions on GMX with fallback RPC support
|
* Implementation function to get positions on GMX with fallback RPC support
|
||||||
* @param sdk The GMX SDK client
|
* @param sdk The GMX SDK client
|
||||||
@@ -1816,6 +1945,61 @@ export async function getPositionHistory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets spot position (swap) history on GMX
|
||||||
|
* @param this The FastifyRequest instance
|
||||||
|
* @param reply The FastifyReply instance
|
||||||
|
* @param account The wallet address of the user
|
||||||
|
* @param pageIndex The page index for pagination (default: 0)
|
||||||
|
* @param pageSize The number of items per page (default: 20)
|
||||||
|
* @param ticker Optional ticker filter
|
||||||
|
* @param fromDateTime Optional start datetime (ISO 8601 format)
|
||||||
|
* @param toDateTime Optional end datetime (ISO 8601 format)
|
||||||
|
* @returns The response object with success status and positions array
|
||||||
|
*/
|
||||||
|
export async function getSpotPositionHistory(
|
||||||
|
this: FastifyRequest,
|
||||||
|
reply: FastifyReply,
|
||||||
|
account: string,
|
||||||
|
pageIndex?: number,
|
||||||
|
pageSize?: number,
|
||||||
|
ticker?: string,
|
||||||
|
fromDateTime?: string,
|
||||||
|
toDateTime?: string
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
getPositionHistorySchema.parse({
|
||||||
|
account,
|
||||||
|
pageIndex: pageIndex ?? 0,
|
||||||
|
pageSize: pageSize ?? 20,
|
||||||
|
ticker,
|
||||||
|
fromDateTime,
|
||||||
|
toDateTime
|
||||||
|
});
|
||||||
|
|
||||||
|
const sdk = await this.getClientForAddress(account);
|
||||||
|
|
||||||
|
const positions = await getSpotPositionHistoryImpl(
|
||||||
|
sdk,
|
||||||
|
pageIndex ?? 0,
|
||||||
|
pageSize ?? 20,
|
||||||
|
ticker,
|
||||||
|
fromDateTime,
|
||||||
|
toDateTime
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
positions,
|
||||||
|
pageIndex: pageIndex ?? 0,
|
||||||
|
pageSize: pageSize ?? 20,
|
||||||
|
count: positions.length
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return handleError(this, reply, error, 'gmx/spot-position-history');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper to pre-populate and refresh the markets cache
|
// Helper to pre-populate and refresh the markets cache
|
||||||
async function getMarketsData() {
|
async function getMarketsData() {
|
||||||
// Use a dummy zero address for the account
|
// Use a dummy zero address for the account
|
||||||
@@ -1849,6 +2033,7 @@ export default fp(async (fastify) => {
|
|||||||
fastify.decorateRequest('getGmxTrade', getGmxTrade)
|
fastify.decorateRequest('getGmxTrade', getGmxTrade)
|
||||||
fastify.decorateRequest('getGmxPositions', getGmxPositions)
|
fastify.decorateRequest('getGmxPositions', getGmxPositions)
|
||||||
fastify.decorateRequest('getPositionHistory', getPositionHistory)
|
fastify.decorateRequest('getPositionHistory', getPositionHistory)
|
||||||
|
fastify.decorateRequest('getSpotPositionHistory', getSpotPositionHistory)
|
||||||
fastify.decorateRequest('getGmxRebateStats', getGmxRebateStats)
|
fastify.decorateRequest('getGmxRebateStats', getGmxRebateStats)
|
||||||
fastify.decorateRequest('getClaimableFundingFees', getClaimableFundingFees)
|
fastify.decorateRequest('getClaimableFundingFees', getClaimableFundingFees)
|
||||||
fastify.decorateRequest('claimGmxFundingFees', claimGmxFundingFees)
|
fastify.decorateRequest('claimGmxFundingFees', claimGmxFundingFees)
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import {test} from 'node:test'
|
||||||
|
import assert from 'node:assert'
|
||||||
|
import {getClientForAddress, getSpotPositionHistoryImpl} from '../../src/plugins/custom/gmx.js'
|
||||||
|
import {Ticker} from '../../src/generated/ManagingApiTypes.js'
|
||||||
|
|
||||||
|
test('GMX get spot position history - Market swaps', async (t) => {
|
||||||
|
await t.test('should get spot swap executions', async () => {
|
||||||
|
const sdk = await getClientForAddress('0x932167388dD9aad41149b3cA23eBD489E2E2DD78')
|
||||||
|
|
||||||
|
const result = await getSpotPositionHistoryImpl(
|
||||||
|
sdk,
|
||||||
|
0, // pageIndex
|
||||||
|
100, // pageSize
|
||||||
|
Ticker.BTC, // ticker
|
||||||
|
'2025-12-04T00:00:00.000Z', // fromDateTime
|
||||||
|
'2025-12-07T00:00:00.000Z' // toDateTime
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log('\n📊 Spot Swap History Summary:')
|
||||||
|
console.log(`Total swaps: ${result.length}`)
|
||||||
|
console.log(`📊 Result:`, result);
|
||||||
|
|
||||||
|
assert.ok(result, 'Spot position history result should be defined')
|
||||||
|
assert.ok(Array.isArray(result), 'Spot position history should be an array')
|
||||||
|
})
|
||||||
|
|
||||||
|
await t.test('should get spot swaps within date range', async () => {
|
||||||
|
const sdk = await getClientForAddress('0x932167388dD9aad41149b3cA23eBD489E2E2DD78')
|
||||||
|
|
||||||
|
const toDate = new Date()
|
||||||
|
const fromDate = new Date(toDate.getTime() - (60 * 60 * 1000)) // last 1 hour
|
||||||
|
|
||||||
|
const fromDateTime = fromDate.toISOString()
|
||||||
|
const toDateTime = toDate.toISOString()
|
||||||
|
|
||||||
|
const result = await getSpotPositionHistoryImpl(
|
||||||
|
sdk,
|
||||||
|
0,
|
||||||
|
50,
|
||||||
|
Ticker.BTC,
|
||||||
|
fromDateTime,
|
||||||
|
toDateTime
|
||||||
|
)
|
||||||
|
|
||||||
|
console.log(`\n📅 Spot swaps in last 1 hour: ${result.length}`)
|
||||||
|
console.log(`From: ${fromDateTime}`)
|
||||||
|
console.log(`To: ${toDateTime}`)
|
||||||
|
|
||||||
|
assert.ok(result, 'Spot position history result should be defined')
|
||||||
|
assert.ok(Array.isArray(result), 'Spot position history should be an array')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
@@ -5216,6 +5216,7 @@ export interface TradingBotConfigRequest {
|
|||||||
useForPositionSizing?: boolean;
|
useForPositionSizing?: boolean;
|
||||||
useForSignalFiltering?: boolean;
|
useForSignalFiltering?: boolean;
|
||||||
useForDynamicStopLoss?: boolean;
|
useForDynamicStopLoss?: boolean;
|
||||||
|
tradingType?: TradingType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScenarioRequest {
|
export interface ScenarioRequest {
|
||||||
|
|||||||
@@ -721,6 +721,7 @@ export interface TradingBotConfigRequest {
|
|||||||
useForPositionSizing?: boolean;
|
useForPositionSizing?: boolean;
|
||||||
useForSignalFiltering?: boolean;
|
useForSignalFiltering?: boolean;
|
||||||
useForDynamicStopLoss?: boolean;
|
useForDynamicStopLoss?: boolean;
|
||||||
|
tradingType?: TradingType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScenarioRequest {
|
export interface ScenarioRequest {
|
||||||
|
|||||||
Reference in New Issue
Block a user