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:
@@ -11,6 +11,7 @@
|
||||
|
||||
|
||||
export interface Account {
|
||||
id?: number;
|
||||
name: string;
|
||||
exchange: TradingExchanges;
|
||||
type: AccountType;
|
||||
@@ -45,6 +46,9 @@ export interface User {
|
||||
agentName?: string | null;
|
||||
avatarUrl?: string | null;
|
||||
telegramChannel?: string | null;
|
||||
ownerWalletAddress?: string | null;
|
||||
isAdmin?: boolean;
|
||||
lastConnectionDate?: Date | null;
|
||||
}
|
||||
|
||||
export interface Balance {
|
||||
@@ -219,9 +223,100 @@ export interface SendTokenRequest {
|
||||
chainId?: number | null;
|
||||
}
|
||||
|
||||
export interface ExchangeApprovalStatus {
|
||||
export interface ExchangeInitializedStatus {
|
||||
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 {
|
||||
@@ -233,17 +328,18 @@ export interface Backtest {
|
||||
config: TradingBotConfig;
|
||||
positions: { [key: string]: Position; };
|
||||
signals: { [key: string]: LightSignal; };
|
||||
candles: Candle[];
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
statistics: PerformanceMetrics;
|
||||
fees: number;
|
||||
walletBalances: KeyValuePairOfDateTimeAndDecimal[];
|
||||
user: User;
|
||||
score: number;
|
||||
requestId?: string;
|
||||
metadata?: any | null;
|
||||
scoreMessage?: string;
|
||||
initialBalance: number;
|
||||
netPnl: number;
|
||||
positionCount: number;
|
||||
}
|
||||
|
||||
export interface TradingBotConfig {
|
||||
@@ -253,7 +349,7 @@ export interface TradingBotConfig {
|
||||
timeframe: Timeframe;
|
||||
isForWatchingOnly: boolean;
|
||||
botTradingBalance: number;
|
||||
isForBacktest: boolean;
|
||||
tradingType: TradingType;
|
||||
cooldownPeriod: number;
|
||||
maxLossStreak: number;
|
||||
flipPosition: boolean;
|
||||
@@ -268,6 +364,9 @@ export interface TradingBotConfig {
|
||||
useForPositionSizing?: boolean;
|
||||
useForSignalFiltering?: boolean;
|
||||
useForDynamicStopLoss?: boolean;
|
||||
isForCopyTrading?: boolean;
|
||||
masterBotIdentifier?: string | null;
|
||||
masterBotUserId?: number | null;
|
||||
}
|
||||
|
||||
export interface LightMoneyManagement {
|
||||
@@ -288,6 +387,13 @@ export enum Timeframe {
|
||||
OneMinute = "OneMinute",
|
||||
}
|
||||
|
||||
export enum TradingType {
|
||||
Futures = "Futures",
|
||||
BacktestFutures = "BacktestFutures",
|
||||
BacktestSpot = "BacktestSpot",
|
||||
Spot = "Spot",
|
||||
}
|
||||
|
||||
export interface RiskManagement {
|
||||
adverseProbabilityThreshold: number;
|
||||
favorableProbabilityThreshold: number;
|
||||
@@ -327,9 +433,18 @@ export interface LightIndicator {
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
stDev?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: 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 {
|
||||
@@ -343,11 +458,15 @@ export enum IndicatorType {
|
||||
EmaTrend = "EmaTrend",
|
||||
Composite = "Composite",
|
||||
StochRsiTrend = "StochRsiTrend",
|
||||
StochasticCross = "StochasticCross",
|
||||
Stc = "Stc",
|
||||
StDev = "StDev",
|
||||
LaggingStc = "LaggingStc",
|
||||
SuperTrendCrossEma = "SuperTrendCrossEma",
|
||||
DualEmaCross = "DualEmaCross",
|
||||
BollingerBandsPercentBMomentumBreakout = "BollingerBandsPercentBMomentumBreakout",
|
||||
BollingerBandsVolatilityProtection = "BollingerBandsVolatilityProtection",
|
||||
IchimokuKumoTrend = "IchimokuKumoTrend",
|
||||
}
|
||||
|
||||
export enum SignalType {
|
||||
@@ -357,8 +476,8 @@ export enum SignalType {
|
||||
}
|
||||
|
||||
export interface Position {
|
||||
accountName: string;
|
||||
date: Date;
|
||||
accountId: number;
|
||||
originDirection: TradeDirection;
|
||||
ticker: Ticker;
|
||||
moneyManagement: LightMoneyManagement;
|
||||
@@ -367,12 +486,16 @@ export interface Position {
|
||||
TakeProfit1: Trade;
|
||||
TakeProfit2?: Trade | null;
|
||||
ProfitAndLoss?: ProfitAndLoss | null;
|
||||
uiFees?: number;
|
||||
gasFees?: number;
|
||||
status: PositionStatus;
|
||||
signalIdentifier?: string | null;
|
||||
identifier: string;
|
||||
initiator: PositionInitiator;
|
||||
user: User;
|
||||
initiatorIdentifier: string;
|
||||
recoveryAttempted?: boolean;
|
||||
tradingType?: TradingType;
|
||||
}
|
||||
|
||||
export enum TradeDirection {
|
||||
@@ -430,7 +553,6 @@ export enum PositionStatus {
|
||||
Canceled = "Canceled",
|
||||
Rejected = "Rejected",
|
||||
Updating = "Updating",
|
||||
PartiallyFilled = "PartiallyFilled",
|
||||
Filled = "Filled",
|
||||
Flipped = "Flipped",
|
||||
Finished = "Finished",
|
||||
@@ -495,25 +617,10 @@ export interface PerformanceMetrics {
|
||||
totalPnL?: number;
|
||||
}
|
||||
|
||||
export interface KeyValuePairOfDateTimeAndDecimal {
|
||||
key?: Date;
|
||||
value?: number;
|
||||
}
|
||||
|
||||
export interface DeleteBacktestsRequest {
|
||||
backtestIds: string[];
|
||||
}
|
||||
|
||||
export interface PaginatedBacktestsResponse {
|
||||
backtests?: LightBacktestResponse[] | null;
|
||||
totalCount?: number;
|
||||
currentPage?: number;
|
||||
pageSize?: number;
|
||||
totalPages?: number;
|
||||
hasNextPage?: boolean;
|
||||
hasPreviousPage?: boolean;
|
||||
}
|
||||
|
||||
export interface LightBacktestResponse {
|
||||
id: string;
|
||||
config: TradingBotConfig;
|
||||
@@ -528,6 +635,36 @@ export interface LightBacktestResponse {
|
||||
sharpeRatio: number;
|
||||
score: number;
|
||||
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 {
|
||||
@@ -544,18 +681,27 @@ export interface LightBacktest {
|
||||
sharpeRatio?: number | null;
|
||||
score?: number;
|
||||
scoreMessage?: string | null;
|
||||
metadata?: any | null;
|
||||
ticker?: string | null;
|
||||
initialBalance?: number;
|
||||
netPnl?: number;
|
||||
positionCount?: number;
|
||||
}
|
||||
|
||||
export interface RunBacktestRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
balance?: number;
|
||||
watchOnly?: boolean;
|
||||
save?: boolean;
|
||||
withCandles?: boolean;
|
||||
moneyManagementName?: string | null;
|
||||
moneyManagement?: MoneyManagement | null;
|
||||
}
|
||||
|
||||
export interface TradingBotConfigRequest {
|
||||
accountName: string;
|
||||
accountName?: string | null;
|
||||
ticker: Ticker;
|
||||
timeframe: Timeframe;
|
||||
isForWatchingOnly: boolean;
|
||||
@@ -575,6 +721,7 @@ export interface TradingBotConfigRequest {
|
||||
useForPositionSizing?: boolean;
|
||||
useForSignalFiltering?: boolean;
|
||||
useForDynamicStopLoss?: boolean;
|
||||
tradingType?: TradingType;
|
||||
}
|
||||
|
||||
export interface ScenarioRequest {
|
||||
@@ -593,9 +740,18 @@ export interface IndicatorRequest {
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
stDev?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: 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 {
|
||||
@@ -606,6 +762,10 @@ export interface MoneyManagementRequest {
|
||||
leverage: number;
|
||||
}
|
||||
|
||||
export interface MoneyManagement extends LightMoneyManagement {
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface BundleBacktestRequest {
|
||||
requestId: string;
|
||||
user: User;
|
||||
@@ -613,8 +773,76 @@ export interface BundleBacktestRequest {
|
||||
completedAt?: Date | null;
|
||||
status: BundleBacktestRequestStatus;
|
||||
name: string;
|
||||
backtestRequestsJson: string;
|
||||
results?: string[] | null;
|
||||
version: number;
|
||||
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;
|
||||
completedBacktests: number;
|
||||
failedBacktests: number;
|
||||
@@ -625,17 +853,18 @@ export interface BundleBacktestRequest {
|
||||
estimatedTimeRemainingSeconds?: number | null;
|
||||
}
|
||||
|
||||
export enum BundleBacktestRequestStatus {
|
||||
Pending = "Pending",
|
||||
Running = "Running",
|
||||
Completed = "Completed",
|
||||
Failed = "Failed",
|
||||
Cancelled = "Cancelled",
|
||||
}
|
||||
|
||||
export interface RunBundleBacktestRequest {
|
||||
name: string;
|
||||
requests: RunBacktestRequest[];
|
||||
export interface BundleBacktestStatusResponse {
|
||||
bundleRequestId?: string;
|
||||
status?: string | null;
|
||||
totalJobs?: number;
|
||||
completedJobs?: number;
|
||||
failedJobs?: number;
|
||||
runningJobs?: number;
|
||||
pendingJobs?: number;
|
||||
progressPercentage?: number;
|
||||
createdAt?: Date;
|
||||
completedAt?: Date | null;
|
||||
errorMessage?: string | null;
|
||||
}
|
||||
|
||||
export interface GeneticRequest {
|
||||
@@ -726,14 +955,15 @@ export interface RunGeneticRequest {
|
||||
eligibleIndicators?: IndicatorType[] | null;
|
||||
}
|
||||
|
||||
export interface MoneyManagement extends LightMoneyManagement {
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
export interface StartBotRequest {
|
||||
config?: TradingBotConfigRequest | null;
|
||||
}
|
||||
|
||||
export interface StartCopyTradingRequest {
|
||||
masterBotIdentifier?: string;
|
||||
botTradingBalance?: number;
|
||||
}
|
||||
|
||||
export interface SaveBotRequest extends StartBotRequest {
|
||||
}
|
||||
|
||||
@@ -750,12 +980,14 @@ export interface TradingBotResponse {
|
||||
candles: Candle[];
|
||||
winRate: number;
|
||||
profitAndLoss: number;
|
||||
roi: number;
|
||||
identifier: string;
|
||||
agentName: string;
|
||||
createDate: Date;
|
||||
startupTime: Date;
|
||||
name: string;
|
||||
ticker: Ticker;
|
||||
masterAgentName?: string | null;
|
||||
}
|
||||
|
||||
export interface PaginatedResponseOfTradingBotResponse {
|
||||
@@ -768,7 +1000,19 @@ export interface PaginatedResponseOfTradingBotResponse {
|
||||
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;
|
||||
direction?: TradeDirection;
|
||||
}
|
||||
@@ -788,6 +1032,7 @@ export interface UpdateBotConfigRequest {
|
||||
export interface TickerInfos {
|
||||
ticker?: Ticker;
|
||||
imageUrl?: string | null;
|
||||
name?: string | null;
|
||||
}
|
||||
|
||||
export interface SpotlightOverview {
|
||||
@@ -819,9 +1064,18 @@ export interface IndicatorBase {
|
||||
slowPeriods?: number | null;
|
||||
signalPeriods?: number | null;
|
||||
multiplier?: number | null;
|
||||
stDev?: number | null;
|
||||
smoothPeriods?: number | null;
|
||||
stochPeriods?: 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;
|
||||
}
|
||||
|
||||
@@ -853,6 +1107,7 @@ export interface IndicatorsResultBase {
|
||||
stdDev?: StdDevResult[] | null;
|
||||
superTrend?: SuperTrendResult[] | null;
|
||||
chandelierLong?: ChandelierResult[] | null;
|
||||
ichimoku?: IchimokuResult[] | null;
|
||||
}
|
||||
|
||||
export interface ResultBase {
|
||||
@@ -929,6 +1184,14 @@ export interface SuperTrendResult extends ResultBase {
|
||||
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 {
|
||||
ticker?: Ticker;
|
||||
startDate?: Date;
|
||||
@@ -949,6 +1212,7 @@ export interface TopStrategiesViewModel {
|
||||
export interface StrategyPerformance {
|
||||
strategyName?: string | null;
|
||||
pnL?: number;
|
||||
netPnL?: number;
|
||||
agentName?: string | null;
|
||||
}
|
||||
|
||||
@@ -960,65 +1224,75 @@ export interface StrategyRoiPerformance {
|
||||
strategyName?: string | null;
|
||||
roi?: number;
|
||||
pnL?: number;
|
||||
netPnL?: 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 {
|
||||
name?: string | null;
|
||||
state?: BotStatus;
|
||||
pnL?: number;
|
||||
netPnL?: number;
|
||||
roiPercentage?: number;
|
||||
roiLast24H?: number;
|
||||
runtime?: Date;
|
||||
runtime?: Date | null;
|
||||
totalRuntimeSeconds?: number;
|
||||
lastStartTime?: Date | null;
|
||||
lastStopTime?: Date | null;
|
||||
accumulatedRunTimeSeconds?: number;
|
||||
winRate?: number;
|
||||
totalVolumeTraded?: number;
|
||||
volumeLast24H?: number;
|
||||
wins?: number;
|
||||
losses?: number;
|
||||
positions?: Position[] | null;
|
||||
positions?: PositionViewModel[] | null;
|
||||
identifier?: string;
|
||||
walletBalances?: { [key: string]: number; } | null;
|
||||
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 {
|
||||
lastUpdated?: Date;
|
||||
lastSnapshot?: Date;
|
||||
hasPendingChanges?: boolean;
|
||||
totalAgents?: number;
|
||||
totalActiveStrategies?: number;
|
||||
totalPlatformPnL?: number;
|
||||
totalPlatformVolume?: number;
|
||||
totalPlatformVolumeLast24h?: number;
|
||||
totalOpenInterest?: number;
|
||||
openInterest?: number;
|
||||
totalPositionCount?: number;
|
||||
agentsChange24h?: number;
|
||||
strategiesChange24h?: number;
|
||||
pnLChange24h?: number;
|
||||
volumeChange24h?: number;
|
||||
openInterestChange24h?: number;
|
||||
positionCountChange24h?: number;
|
||||
totalPlatformFees?: number;
|
||||
dailySnapshots?: DailySnapshot[] | null;
|
||||
volumeByAsset?: { [key in keyof typeof Ticker]?: number; } | null;
|
||||
positionCountByAsset?: { [key in keyof typeof Ticker]?: 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;
|
||||
volume?: number;
|
||||
totalAgents?: number;
|
||||
totalStrategies?: number;
|
||||
totalVolume?: number;
|
||||
totalPnL?: number;
|
||||
netPnL?: number;
|
||||
totalOpenInterest?: number;
|
||||
totalPositionCount?: number;
|
||||
}
|
||||
|
||||
export interface PaginatedAgentIndexResponse {
|
||||
@@ -1038,16 +1312,19 @@ export interface PaginatedAgentIndexResponse {
|
||||
export interface AgentSummaryViewModel {
|
||||
agentName?: string | null;
|
||||
totalPnL?: number;
|
||||
netPnL?: number;
|
||||
totalROI?: number;
|
||||
wins?: number;
|
||||
losses?: number;
|
||||
activeStrategiesCount?: number;
|
||||
totalVolume?: number;
|
||||
totalBalance?: number;
|
||||
totalFees?: number;
|
||||
backtestCount?: number;
|
||||
}
|
||||
|
||||
export enum SortableFields {
|
||||
TotalPnL = "TotalPnL",
|
||||
NetPnL = "NetPnL",
|
||||
TotalROI = "TotalROI",
|
||||
Wins = "Wins",
|
||||
Losses = "Losses",
|
||||
@@ -1059,25 +1336,82 @@ export enum SortableFields {
|
||||
}
|
||||
|
||||
export interface AgentBalanceHistory {
|
||||
userId?: number;
|
||||
agentName?: string | null;
|
||||
agentBalances?: AgentBalance[] | null;
|
||||
}
|
||||
|
||||
export interface AgentBalance {
|
||||
agentName?: string | null;
|
||||
totalValue?: number;
|
||||
totalAccountUsdValue?: number;
|
||||
userId?: number;
|
||||
totalBalanceValue?: number;
|
||||
usdcWalletValue?: number;
|
||||
usdcInPositionsValue?: number;
|
||||
botsAllocationUsdValue?: number;
|
||||
pnL?: number;
|
||||
time?: Date;
|
||||
}
|
||||
|
||||
export interface BestAgentsResponse {
|
||||
agents?: AgentBalanceHistory[] | null;
|
||||
export interface JobStatusResponse {
|
||||
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;
|
||||
currentPage?: number;
|
||||
pageSize?: 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 {
|
||||
@@ -1120,11 +1454,69 @@ export interface PrivyInitAddressResponse {
|
||||
isAlreadyInitialized?: boolean;
|
||||
}
|
||||
|
||||
export interface IndicatorRequestDto {
|
||||
indicatorName: string;
|
||||
strategyDescription: string;
|
||||
documentationUrl?: string | null;
|
||||
imageUrl?: string | null;
|
||||
requesterName: string;
|
||||
}
|
||||
|
||||
export interface LoginRequest {
|
||||
name: string;
|
||||
address: string;
|
||||
signature: 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 {
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
TradeStatus,
|
||||
TradeType
|
||||
} from '../../generated/ManagingApiTypes.js';
|
||||
import {TradeActionType} from '../../generated/gmxsdk/types/tradeHistory.js';
|
||||
|
||||
// Cache implementation for markets info data
|
||||
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
|
||||
* @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
|
||||
async function getMarketsData() {
|
||||
// Use a dummy zero address for the account
|
||||
@@ -1849,6 +2033,7 @@ export default fp(async (fastify) => {
|
||||
fastify.decorateRequest('getGmxTrade', getGmxTrade)
|
||||
fastify.decorateRequest('getGmxPositions', getGmxPositions)
|
||||
fastify.decorateRequest('getPositionHistory', getPositionHistory)
|
||||
fastify.decorateRequest('getSpotPositionHistory', getSpotPositionHistory)
|
||||
fastify.decorateRequest('getGmxRebateStats', getGmxRebateStats)
|
||||
fastify.decorateRequest('getClaimableFundingFees', getClaimableFundingFees)
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user