Update logs and cache marketinfo
This commit is contained in:
@@ -553,7 +553,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await LogWarning(
|
await LogInformation(
|
||||||
$"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped.");
|
$"A position is already open for signal {previousSignal.Identifier}. Position flipping is currently not enable, the position will not be flipped.");
|
||||||
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
SetSignalStatus(signal.Identifier, SignalStatus.Expired);
|
||||||
}
|
}
|
||||||
@@ -876,8 +876,7 @@ public class TradingBot : Bot, ITradingBot
|
|||||||
|
|
||||||
private async Task LogWarning(string message)
|
private async Task LogWarning(string message)
|
||||||
{
|
{
|
||||||
message = $"[{Name}][{Identifier}] {message}";
|
message = $"[{Identifier}] {message}";
|
||||||
Logger.LogWarning(message);
|
|
||||||
SentrySdk.CaptureException(new Exception(message));
|
SentrySdk.CaptureException(new Exception(message));
|
||||||
await SendTradeMessage(message, true);
|
await SendTradeMessage(message, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,49 @@ import {formatUsd} from '../../generated/gmxsdk/utils/numbers/formatting.js';
|
|||||||
import {calculateDisplayDecimals} from '../../generated/gmxsdk/utils/numbers/index.js';
|
import {calculateDisplayDecimals} from '../../generated/gmxsdk/utils/numbers/index.js';
|
||||||
import {handleError} from '../../utils/errorHandler.js';
|
import {handleError} from '../../utils/errorHandler.js';
|
||||||
|
|
||||||
|
// Cache implementation for markets info data
|
||||||
|
interface CacheEntry {
|
||||||
|
data: {
|
||||||
|
marketsInfoData?: MarketsInfoData;
|
||||||
|
tokensData?: TokensData;
|
||||||
|
};
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CACHE_TTL = 30 * 60 * 1000; // 30 minutes in milliseconds
|
||||||
|
const marketsCache = new Map<string, CacheEntry>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets markets info data from cache or fetches it from GMX SDK
|
||||||
|
* @param sdk The GMX SDK client
|
||||||
|
* @returns Markets info data and tokens data
|
||||||
|
*/
|
||||||
|
async function getMarketsInfoWithCache(sdk: GmxSdk): Promise<{ marketsInfoData: MarketsInfoData; tokensData: TokensData }> {
|
||||||
|
const cacheKey = `markets_${sdk.chainId}`;
|
||||||
|
const now = Date.now();
|
||||||
|
const cached = marketsCache.get(cacheKey);
|
||||||
|
|
||||||
|
if (cached && (now - cached.timestamp) < CACHE_TTL) {
|
||||||
|
if (!cached.data.marketsInfoData || !cached.data.tokensData) {
|
||||||
|
throw new Error("Invalid cached data: missing markets or tokens info");
|
||||||
|
}
|
||||||
|
return cached.data as { marketsInfoData: MarketsInfoData; tokensData: TokensData };
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await sdk.markets.getMarketsInfo();
|
||||||
|
|
||||||
|
if (!data.marketsInfoData || !data.tokensData) {
|
||||||
|
throw new Error("Invalid response from GMX: missing markets or tokens info");
|
||||||
|
}
|
||||||
|
|
||||||
|
marketsCache.set(cacheKey, {
|
||||||
|
data: data as { marketsInfoData: MarketsInfoData; tokensData: TokensData },
|
||||||
|
timestamp: now
|
||||||
|
});
|
||||||
|
|
||||||
|
return data as { marketsInfoData: MarketsInfoData; tokensData: TokensData };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GMX Plugin
|
* GMX Plugin
|
||||||
*
|
*
|
||||||
@@ -99,6 +142,14 @@ export async function getClientForAddress(
|
|||||||
subgraphUrl: "https://subgraph.satsuma-prod.com/3b2ced13c8d9/gmx/synthetics-arbitrum-stats/api",
|
subgraphUrl: "https://subgraph.satsuma-prod.com/3b2ced13c8d9/gmx/synthetics-arbitrum-stats/api",
|
||||||
settings: {
|
settings: {
|
||||||
uiFeeReceiverAccount: "0xF9f04a745Db54B25bB8B345a1da74D4E3c38c8aB"
|
uiFeeReceiverAccount: "0xF9f04a745Db54B25bB8B345a1da74D4E3c38c8aB"
|
||||||
|
},
|
||||||
|
markets: {
|
||||||
|
"0x4D3Eb91efd36C2b74181F34B111bc1E91a0d0cb4": {
|
||||||
|
isListed: false,
|
||||||
|
},
|
||||||
|
"0xdf034cd3df9a80eABFA0556232a91E03Ca67D5Cb": {
|
||||||
|
isListed: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -136,8 +187,8 @@ export const openGmxPositionImpl = async (
|
|||||||
takeProfitPrice?: number
|
takeProfitPrice?: number
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
try {
|
try {
|
||||||
// Get markets and tokens data from GMX SDK
|
// Get markets and tokens data from GMX SDK with cache
|
||||||
const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo();
|
const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk);
|
||||||
|
|
||||||
if (!marketsInfoData || !tokensData) {
|
if (!marketsInfoData || !tokensData) {
|
||||||
throw new Error("No markets or tokens info data");
|
throw new Error("No markets or tokens info data");
|
||||||
@@ -149,7 +200,6 @@ export const openGmxPositionImpl = async (
|
|||||||
|
|
||||||
// Calculate the collateral amount in USDC (quantity * price)
|
// Calculate the collateral amount in USDC (quantity * price)
|
||||||
const collateralAmount = BigInt(Math.floor((quantity || 0) * (price || 0) * 1e6)); // USDC has 6 decimals
|
const collateralAmount = BigInt(Math.floor((quantity || 0) * (price || 0) * 1e6)); // USDC has 6 decimals
|
||||||
console.log('collateralAmount', collateralAmount);
|
|
||||||
|
|
||||||
// Calculate leverage in basis points (1x = 10000)
|
// Calculate leverage in basis points (1x = 10000)
|
||||||
const leverageBps = BigInt((leverage || 1) * 10000);
|
const leverageBps = BigInt((leverage || 1) * 10000);
|
||||||
@@ -168,8 +218,6 @@ export const openGmxPositionImpl = async (
|
|||||||
takeProfitPrice: takeProfitPrice ? numberToBigint(takeProfitPrice, 30) : undefined
|
takeProfitPrice: takeProfitPrice ? numberToBigint(takeProfitPrice, 30) : undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('params', params)
|
|
||||||
|
|
||||||
if (direction === TradeDirection.Long) {
|
if (direction === TradeDirection.Long) {
|
||||||
await sdk.orders.long(params);
|
await sdk.orders.long(params);
|
||||||
} else {
|
} else {
|
||||||
@@ -391,8 +439,8 @@ export const closeGmxPositionImpl = async (
|
|||||||
direction: TradeDirection
|
direction: TradeDirection
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
try {
|
try {
|
||||||
// Get markets and tokens data from GMX SDK
|
// Get markets and tokens data from GMX SDK with cache
|
||||||
const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo();
|
const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk);
|
||||||
|
|
||||||
if (!marketsInfoData || !tokensData) {
|
if (!marketsInfoData || !tokensData) {
|
||||||
throw new Error("No markets or tokens info data");
|
throw new Error("No markets or tokens info data");
|
||||||
@@ -525,7 +573,7 @@ export const getGmxTradeImpl = async (
|
|||||||
ticker: string
|
ticker: string
|
||||||
): Promise<Trade[]> => {
|
): Promise<Trade[]> => {
|
||||||
|
|
||||||
const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo();
|
const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk);
|
||||||
|
|
||||||
const orders = await sdk.orders.getOrders({
|
const orders = await sdk.orders.getOrders({
|
||||||
account: sdk.account,
|
account: sdk.account,
|
||||||
@@ -566,7 +614,6 @@ export const getGmxTradeImpl = async (
|
|||||||
|
|
||||||
if (collateral > 0) {
|
if (collateral > 0) {
|
||||||
leverage = Math.round(size / collateral);
|
leverage = Math.round(size / collateral);
|
||||||
console.log('Calculated leverage:', leverage, 'from size:', size, 'collateral:', collateral);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -641,7 +688,7 @@ export async function getGmxTrade(
|
|||||||
export const getGmxPositionsImpl = async (
|
export const getGmxPositionsImpl = async (
|
||||||
sdk: GmxSdk
|
sdk: GmxSdk
|
||||||
): Promise<Position[]> => {
|
): Promise<Position[]> => {
|
||||||
const {marketsInfoData, tokensData} = await sdk.markets.getMarketsInfo();
|
const {marketsInfoData, tokensData} = await getMarketsInfoWithCache(sdk);
|
||||||
|
|
||||||
const positionsInfo = await sdk.positions.getPositionsInfo({
|
const positionsInfo = await sdk.positions.getPositionsInfo({
|
||||||
marketsInfoData,
|
marketsInfoData,
|
||||||
@@ -651,7 +698,7 @@ export const getGmxPositionsImpl = async (
|
|||||||
|
|
||||||
const positions = Object.values(positionsInfo).map(async (pos) => {
|
const positions = Object.values(positionsInfo).map(async (pos) => {
|
||||||
// Fix leverage calculation to avoid exponential notation issues
|
// Fix leverage calculation to avoid exponential notation issues
|
||||||
let leverage = 2; // Default to 2x leverage
|
let leverage = 1; // Default to 1x leverage
|
||||||
|
|
||||||
if (pos.collateralAmount > 0n) {
|
if (pos.collateralAmount > 0n) {
|
||||||
// Manual calculation of leverage from raw values
|
// Manual calculation of leverage from raw values
|
||||||
@@ -783,6 +830,24 @@ export async function getGmxPositions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to pre-populate and refresh the markets cache
|
||||||
|
async function getMarketsData() {
|
||||||
|
// Use a dummy zero address for the account
|
||||||
|
const account = "0x0000000000000000000000000000000000000000";
|
||||||
|
const sdk = await getClientForAddress(account);
|
||||||
|
await getMarketsInfoWithCache(sdk);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupCacheRefresh() {
|
||||||
|
setInterval(async () => {
|
||||||
|
try {
|
||||||
|
await getMarketsData();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Cache refresh failed:', error);
|
||||||
|
}
|
||||||
|
}, CACHE_TTL);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The use of fastify-plugin is required to be able
|
* The use of fastify-plugin is required to be able
|
||||||
* to export the decorators to the outer scope
|
* to export the decorators to the outer scope
|
||||||
@@ -797,5 +862,16 @@ export default fp(async (fastify) => {
|
|||||||
fastify.decorateRequest('closeGmxPosition', closeGmxPosition)
|
fastify.decorateRequest('closeGmxPosition', closeGmxPosition)
|
||||||
fastify.decorateRequest('getGmxTrade', getGmxTrade)
|
fastify.decorateRequest('getGmxTrade', getGmxTrade)
|
||||||
fastify.decorateRequest('getGmxPositions', getGmxPositions)
|
fastify.decorateRequest('getGmxPositions', getGmxPositions)
|
||||||
|
|
||||||
|
// Pre-populate and refresh the markets cache on startup
|
||||||
|
fastify.addHook('onReady', async () => {
|
||||||
|
try {
|
||||||
|
await getMarketsData();
|
||||||
|
setupCacheRefresh();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Initial cache population failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -126,67 +126,16 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
|
|||||||
// Use the existing getClientForAddress function to get a proper GMX SDK instance
|
// Use the existing getClientForAddress function to get a proper GMX SDK instance
|
||||||
const sdk = await getClientForAddress(account);
|
const sdk = await getClientForAddress(account);
|
||||||
|
|
||||||
// Get markets info data
|
// Get the uiFeeFactor - this is a lightweight call
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
const marketsInfo = await sdk.markets.getMarketsInfo();
|
|
||||||
const responseTime = Date.now() - startTime;
|
|
||||||
|
|
||||||
// Get the uiFeeFactor
|
|
||||||
const uiFeeFactor = await sdk.utils.getUiFeeFactor();
|
const uiFeeFactor = await sdk.utils.getUiFeeFactor();
|
||||||
|
const responseTime = Date.now() - startTime;
|
||||||
if (!marketsInfo.marketsInfoData || Object.keys(marketsInfo.marketsInfoData).length === 0) {
|
|
||||||
return {
|
|
||||||
status: 'degraded',
|
|
||||||
message: 'GMX SDK returned empty markets info data',
|
|
||||||
data: {
|
|
||||||
responseTimeMs: responseTime,
|
|
||||||
uiFeeFactor: uiFeeFactor.toString()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check market data for ETH-USD
|
|
||||||
let foundEthMarket = false;
|
|
||||||
let marketInfoDetails = [];
|
|
||||||
|
|
||||||
// Collect information about all available markets
|
|
||||||
for (const [marketAddress, marketInfo] of Object.entries(marketsInfo.marketsInfoData)) {
|
|
||||||
const marketDetails = {
|
|
||||||
marketAddress,
|
|
||||||
indexToken: marketInfo.indexToken?.symbol,
|
|
||||||
longToken: marketInfo.longToken?.symbol,
|
|
||||||
shortToken: marketInfo.shortToken?.symbol
|
|
||||||
};
|
|
||||||
|
|
||||||
marketInfoDetails.push(marketDetails);
|
|
||||||
|
|
||||||
// Check if this is the ETH market
|
|
||||||
if (marketInfo.indexToken?.symbol === 'ETH' ||
|
|
||||||
marketInfo.indexToken?.symbol === 'WETH' ||
|
|
||||||
marketInfo.indexToken?.name?.includes('Ethereum')) {
|
|
||||||
foundEthMarket = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundEthMarket) {
|
|
||||||
return {
|
|
||||||
status: 'degraded',
|
|
||||||
message: 'ETH market not found in GMX markets data',
|
|
||||||
data: {
|
|
||||||
availableMarkets: marketInfoDetails,
|
|
||||||
responseTimeMs: responseTime,
|
|
||||||
uiFeeFactor: uiFeeFactor.toString()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 'healthy',
|
status: 'healthy',
|
||||||
message: `GMX SDK successfully retrieved markets data (${Object.keys(marketsInfo.marketsInfoData).length} markets)`,
|
message: 'GMX SDK successfully initialized',
|
||||||
data: {
|
data: {
|
||||||
marketCount: Object.keys(marketsInfo.marketsInfoData).length,
|
|
||||||
responseTimeMs: responseTime,
|
responseTimeMs: responseTime,
|
||||||
sampleMarkets: marketInfoDetails.slice(0, 3), // Just include first 3 markets for brevity
|
|
||||||
uiFeeFactor: uiFeeFactor.toString()
|
uiFeeFactor: uiFeeFactor.toString()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user