Skip to content

Feat/perps mirror hedge#14

Draft
tae9898 wants to merge 8 commits into
Minara-AI:mainfrom
tae9898:feat/perps-mirror-hedge
Draft

Feat/perps mirror hedge#14
tae9898 wants to merge 8 commits into
Minara-AI:mainfrom
tae9898:feat/perps-mirror-hedge

Conversation

@tae9898

@tae9898 tae9898 commented Jun 26, 2026

Copy link
Copy Markdown

No description provided.

@tae9898 tae9898 marked this pull request as draft June 26, 2026 15:59

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new mirror (hedge) command to monitor source wallets and automatically open opposite positions, implements a 30-second cache TTL for asset metadata to prevent IOC failures, updates order execution types to use Immediate-or-Cancel (IOC) limits, and refactors output formatting to support arrays and nested objects. Key feedback points include: implementing defensive checks in getUserPositions to prevent potential TypeErrors, verifying the success of updateLeverage before caching leverage status, hoisting the getAssetMeta call to the top of the polling loop to avoid redundant API requests, and removing a redundant conditional branch in formatValue where both paths are identical.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/api/perps.ts
Comment on lines +355 to +360
return (data.assetPositions ?? []).map((ap) => ({
coin: ap.position.coin,
szi: parseFloat(ap.position.szi),
entryPx: parseFloat(ap.position.entryPx ?? '0'),
unrealizedPnl: parseFloat(ap.position.unrealizedPnl ?? '0'),
}));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If ap.position is undefined or null, accessing ap.position.coin will throw a TypeError, causing the entire function to fail and return []. Enforce defensive programming by safely checking if ap.position exists before mapping.

    return (data.assetPositions ?? [])
      .map((ap) => {
        const pos = ap?.position;
        if (!pos) return null;
        return {
          coin: pos.coin,
          szi: parseFloat(pos.szi),
          entryPx: parseFloat(pos.entryPx ?? '0'),
          unrealizedPnl: parseFloat(pos.unrealizedPnl ?? '0'),
        };
      })
      .filter((p): p is HlPosition => p !== null);

Comment thread src/commands/perps.ts Outdated
Comment on lines +1336 to +1348
if (!leverageSet.has(coin)) {
try {
await perpsApi.updateLeverage(creds.accessToken, {
symbol: coin,
isCross: true,
leverage: targetLeverage,
subAccountId: walletId,
});
leverageSet.add(coin);
} catch (e) {
warn(` Could not set leverage for ${coin}: ${String(e).slice(0, 80)}`);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

updateLeverage returns an ApiResponse<void> and does not throw on API-level failures (e.g., invalid leverage, unauthorized). We should check levRes.success before adding the coin to leverageSet to ensure we don't skip setting leverage on subsequent polls if it failed.

Suggested change
if (!leverageSet.has(coin)) {
try {
await perpsApi.updateLeverage(creds.accessToken, {
symbol: coin,
isCross: true,
leverage: targetLeverage,
subAccountId: walletId,
});
leverageSet.add(coin);
} catch (e) {
warn(` Could not set leverage for ${coin}: ${String(e).slice(0, 80)}`);
}
}
if (!leverageSet.has(coin)) {
try {
const levRes = await perpsApi.updateLeverage(creds.accessToken, {
symbol: coin,
isCross: true,
leverage: targetLeverage,
subAccountId: walletId,
});
if (levRes.success) {
leverageSet.add(coin);
} else {
warn(' Could not set leverage for ' + coin + ': ' + (levRes.error?.message ?? 'Unknown error'));
}
} catch (e) {
warn(' Could not set leverage for ' + coin + ': ' + String(e).slice(0, 80));
}
}

Comment thread src/commands/perps.ts Outdated
Comment on lines +1292 to +1295
while (running) {
try {
for (const source of sources) {
const current = await fetchSourcePositions(source);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To avoid redundant API calls and cache checks inside the nested loops, fetch the asset metadata once per poll interval at the top of the while (running) loop.

Suggested change
while (running) {
try {
for (const source of sources) {
const current = await fetchSourcePositions(source);
while (running) {
try {
const assets = await perpsApi.getAssetMeta();
for (const source of sources) {

Comment thread src/commands/perps.ts Outdated
Comment on lines +1315 to +1316
const assets = await perpsApi.getAssetMeta();
const meta = assets.find((a) => a.name.toUpperCase() === coin.toUpperCase());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since assets is now fetched once per poll interval at the top of the loop, you can remove the redundant call here.

Suggested change
const assets = await perpsApi.getAssetMeta();
const meta = assets.find((a) => a.name.toUpperCase() === coin.toUpperCase());
const meta = assets.find((a) => a.name.toUpperCase() === coin.toUpperCase());

Comment thread src/formatters.ts
Comment on lines 142 to +145
if (entries.length <= 3) {
return entries.map(([k, v]) => `${k}=${typeof v === 'string' ? v : JSON.stringify(v)}`).join(' ');
return entries.map(([k, v]) => `${k}=${typeof v === 'string' ? v : formatValue(v, k)}`).join(' ');
}
return chalk.dim(JSON.stringify(value));
return entries.map(([k, v]) => `${k}=${typeof v === 'string' ? v : formatValue(v, k)}`).join(' ');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Both branches of the if statement are identical. You can simplify this by removing the redundant condition and directly returning the mapped entries.

    return entries.map(([k, v]) => k + '=' + (typeof v === 'string' ? v : formatValue(v, k))).join('  ');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant