Skip to content

Conversation

@aarmoa
Copy link
Collaborator

@aarmoa aarmoa commented Aug 1, 2025

  • added a new example script to search for liquidable positions

Summary by CodeRabbit

  • New Features
    • Added a script to identify and display liquidable derivative positions on the Injective Protocol mainnet, including detailed information such as position direction, market ID, subaccount ID, liquidation price, and mark price.

@aarmoa aarmoa requested a review from Copilot August 1, 2025 18:37
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 1, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

A new Python script has been added to the examples, demonstrating how to search for liquidable derivative positions on the Injective Protocol mainnet. The script uses an asynchronous client to fetch positions, group them by market, compute adjusted margins and liquidation prices, and print out positions that are currently liquidable.

Changes

Cohort / File(s) Change Summary
New Liquidable Positions Example
examples/chain_client/10_SearchLiquidablePositions.py
Added a script that asynchronously identifies and prints liquidable derivative positions on Injective mainnet, including utility functions and a main coroutine.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Script
    participant AsyncClient
    participant InjectiveChain

    User->>Script: Run script
    Script->>AsyncClient: Connect to Injective mainnet
    Script->>AsyncClient: Fetch all positions
    Script->>AsyncClient: Fetch active markets
    loop For each market
        Script->>AsyncClient: Fetch mark price
        loop For each position in market
            Script->>Script: Calculate adjusted margin and liquidation price
            Script->>Script: Check if position is liquidable
            alt If liquidable
                Script->>User: Print position details
            end
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15 minutes

Poem

A bunny hopped through code today,
Sniffing out positions that might go astray.
With margin math and prices checked,
Liquidation risks were deftly detected.
On Injective’s chain, it found the way—
To keep those traders safe, hooray!
🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 987e82b and 1c7fb55.

📒 Files selected for processing (1)
  • examples/chain_client/10_SearchLiquidablePositions.py (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add_liquidable_positions_example_script

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds a new example script to demonstrate how to search for liquidable positions in derivative markets. The script implements liquidation logic by calculating adjusted margins and liquidation prices for active positions.

  • Adds liquidation calculation logic including funding payment adjustments
  • Implements position analysis across all active derivative markets
  • Provides console output for positions that should be liquidated

Comment on lines +56 to +58
# print(f"\n\n\n")
# print(json.dumps(liquidable_positions, indent=4))

Copy link

Copilot AI Aug 1, 2025

Choose a reason for hiding this comment

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

These commented-out debug print statements should be removed from the final code to keep the example clean and production-ready.

Suggested change
# print(f"\n\n\n")
# print(json.dumps(liquidable_positions, indent=4))

Copilot uses AI. Check for mistakes.
liquidable_positions.append(position)

# print(f"\n\n\n")
# print(json.dumps(liquidable_positions, indent=4))
Copy link

Copilot AI Aug 1, 2025

Choose a reason for hiding this comment

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

This commented-out JSON dump should be removed or converted to proper logging if needed for debugging purposes.

Suggested change
# print(json.dumps(liquidable_positions, indent=4))
# To see the full list of liquidable positions, enable debug logging.
logging.debug(json.dumps(liquidable_positions, indent=4))

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +57
# print(f"\n\n\n")
# print(json.dumps(liquidable_positions, indent=4))
Copy link

Copilot AI Aug 1, 2025

Choose a reason for hiding this comment

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

The variable liquidable_positions is populated but never used except in the commented-out code. Consider removing it or demonstrating its usage in the example.

Suggested change
# print(f"\n\n\n")
# print(json.dumps(liquidable_positions, indent=4))
print(f"\n\n\n")
print(json.dumps(liquidable_positions, indent=4))

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
examples/chain_client/10_SearchLiquidablePositions.py (1)

56-60: Update asyncio execution pattern.

The commented debug code is fine to keep for reference, but consider updating the asyncio execution to use the modern pattern.

Apply this diff to use the modern asyncio pattern:

 if __name__ == "__main__":
-    asyncio.get_event_loop().run_until_complete(main())
+    asyncio.run(main())

This is more concise and handles event loop management automatically.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c6b3f5 and 95f9b21.

📒 Files selected for processing (1)
  • examples/chain_client/10_SearchLiquidablePositions.py (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: pre-commit
examples/chain_client/10_SearchLiquidablePositions.py

[error] 7-7: flakeheaven (pycodestyle E302): expected 2 blank lines, found 1


[error] 11-11: flakeheaven (pycodestyle E302): expected 2 blank lines, found 1


[error] 7-121: flakeheaven (pycodestyle E501): line too long (146 > 120 characters)


[error] 41-121: flakeheaven (pycodestyle E501): line too long (137 > 120 characters)


[error] 42-121: flakeheaven (pycodestyle E501): line too long (151 > 120 characters)


[error] 50-121: flakeheaven (pycodestyle E501): line too long (147 > 120 characters)


[error] 53-121: flakeheaven (pycodestyle E501): line too long (259 > 120 characters)


[error] isort: import sorting issues fixed automatically


[error] black: file reformatted automatically to comply with code style

🔇 Additional comments (2)
examples/chain_client/10_SearchLiquidablePositions.py (2)

1-6: LGTM! Imports are appropriate for the functionality.

The imports are well-chosen for this liquidation position search script. The pipeline automatically fixed import sorting issues.


18-31: LGTM! Efficient position grouping and market fetching.

The logic efficiently groups positions by market ID and fetches only the relevant derivative markets, avoiding unnecessary API calls.

Comment on lines 7 to 9
def adjusted_margin(quantity: Decimal, margin: Decimal, is_long: bool, cumulative_funding_entry: Decimal, cumulative_funding: Decimal) -> Decimal:
unrealized_funding_payment = (cumulative_funding - cumulative_funding_entry) * quantity * (1 if is_long else -1)
return margin + unrealized_funding_payment
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix formatting issues while logic looks correct.

The adjusted margin calculation logic is sound - it properly accounts for unrealized funding payments based on position direction. However, there are formatting violations that need to be addressed:

  • Missing blank lines before function definition (E302)
  • Line 7 exceeds 120 character limit (E501)

Apply this diff to fix the formatting issues:

+
+
 def adjusted_margin(
-    quantity: Decimal, margin: Decimal, is_long: bool, cumulative_funding_entry: Decimal, cumulative_funding: Decimal) -> Decimal:
-    unrealized_funding_payment = (cumulative_funding - cumulative_funding_entry) * quantity * (1 if is_long else -1)
+    quantity: Decimal,
+    margin: Decimal,
+    is_long: bool,
+    cumulative_funding_entry: Decimal,
+    cumulative_funding: Decimal,
+) -> Decimal:
+    direction_multiplier = 1 if is_long else -1
+    unrealized_funding_payment = (
+        (cumulative_funding - cumulative_funding_entry) * quantity * direction_multiplier
+    )
     return margin + unrealized_funding_payment
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def adjusted_margin(quantity: Decimal, margin: Decimal, is_long: bool, cumulative_funding_entry: Decimal, cumulative_funding: Decimal) -> Decimal:
unrealized_funding_payment = (cumulative_funding - cumulative_funding_entry) * quantity * (1 if is_long else -1)
return margin + unrealized_funding_payment
def adjusted_margin(
quantity: Decimal,
margin: Decimal,
is_long: bool,
cumulative_funding_entry: Decimal,
cumulative_funding: Decimal,
) -> Decimal:
direction_multiplier = 1 if is_long else -1
unrealized_funding_payment = (
(cumulative_funding - cumulative_funding_entry) * quantity * direction_multiplier
)
return margin + unrealized_funding_payment
🧰 Tools
🪛 GitHub Actions: pre-commit

[error] 7-7: flakeheaven (pycodestyle E302): expected 2 blank lines, found 1


[error] 7-121: flakeheaven (pycodestyle E501): line too long (146 > 120 characters)


[error] isort: import sorting issues fixed automatically


[error] black: file reformatted automatically to comply with code style

🤖 Prompt for AI Agents
In examples/chain_client/10_SearchLiquidablePositions.py around lines 7 to 9,
fix formatting issues by adding a blank line before the function definition to
comply with PEP8 (E302) and break the long line 7 into multiple lines or use
line continuation to ensure it does not exceed 120 characters (E501). Keep the
logic unchanged while adjusting the formatting.

Comment on lines 33 to 55
for market in derivative_markets["markets"]:
client_market = (await client.all_derivative_markets())[market["market"]["marketId"]]
market_mark_price = client_market._from_extended_chain_format(Decimal(market["markPrice"]))
for position in positions_per_market[client_market.id]:
is_long = position["position"]["isLong"]
quantity = client_market._from_extended_chain_format(Decimal(position["position"]["quantity"]))
entry_price = client_market._from_extended_chain_format(Decimal(position["position"]["entryPrice"]))
margin = client_market._from_extended_chain_format(Decimal(position["position"]["margin"]))
cumulative_funding_entry = client_market._from_extended_chain_format(Decimal(position["position"]["cumulativeFundingEntry"]))
market_cumulative_funding = client_market._from_extended_chain_format(Decimal(market["perpetualInfo"]["fundingInfo"]["cumulativeFunding"]))

adj_margin = adjusted_margin(quantity, margin, is_long, cumulative_funding_entry, market_cumulative_funding)
adjusted_unit_margin = (adj_margin / quantity) * (1 if is_long else -1)

maintenance_margin_ratio = client_market.maintenance_margin_ratio * (-1 if is_long else 1)
liquidation_price = (entry_price + adjusted_unit_margin) / (Decimal(1) + maintenance_margin_ratio)

should_be_liquidated = (is_long and market_mark_price <= liquidation_price) or (not is_long and market_mark_price >= liquidation_price)

if should_be_liquidated:
print(f"{'Long' if is_long else 'Short'} position for market {client_market.id} and subaccount {position['subaccountId']} should be liquidated (liquidation price: {liquidation_price.normalize()} / mark price: {market_mark_price.normalize()})")
liquidable_positions.append(position)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix line length violations and add error handling.

The liquidation detection logic is mathematically sound and follows the correct approach for identifying positions that should be liquidated. However, there are several formatting violations and missing error handling.

Critical issues to address:

  1. Multiple line length violations (lines 41, 42, 50, 53)
  2. Missing error handling for division by zero when quantity is zero
  3. No validation for invalid position data

Apply this diff to fix the line length issues:

         for position in positions_per_market[client_market.id]:
             is_long = position["position"]["isLong"]
-            quantity = client_market._from_extended_chain_format(Decimal(position["position"]["quantity"]))
+            quantity = client_market._from_extended_chain_format(
+                Decimal(position["position"]["quantity"])
+            )
             entry_price = client_market._from_extended_chain_format(Decimal(position["position"]["entryPrice"]))
             margin = client_market._from_extended_chain_format(Decimal(position["position"]["margin"]))
-            cumulative_funding_entry = client_market._from_extended_chain_format(Decimal(position["position"]["cumulativeFundingEntry"]))
-            market_cumulative_funding = client_market._from_extended_chain_format(Decimal(market["perpetualInfo"]["fundingInfo"]["cumulativeFunding"]))
+            cumulative_funding_entry = client_market._from_extended_chain_format(
+                Decimal(position["position"]["cumulativeFundingEntry"])
+            )
+            market_cumulative_funding = client_market._from_extended_chain_format(
+                Decimal(market["perpetualInfo"]["fundingInfo"]["cumulativeFunding"])
+            )

-            adj_margin = adjusted_margin(quantity, margin, is_long, cumulative_funding_entry, market_cumulative_funding)
+            if quantity == 0:
+                continue  # Skip positions with zero quantity
+                
+            adj_margin = adjusted_margin(
+                quantity, margin, is_long, cumulative_funding_entry, market_cumulative_funding
+            )
             adjusted_unit_margin = (adj_margin / quantity) * (1 if is_long else -1)

-            maintenance_margin_ratio = client_market.maintenance_margin_ratio * (-1 if is_long else 1)
-            liquidation_price = (entry_price + adjusted_unit_margin) / (Decimal(1) + maintenance_margin_ratio)
+            maintenance_margin_ratio = client_market.maintenance_margin_ratio * (
+                -1 if is_long else 1
+            )
+            liquidation_price = (entry_price + adjusted_unit_margin) / (
+                Decimal(1) + maintenance_margin_ratio
+            )

-            should_be_liquidated = (is_long and market_mark_price <= liquidation_price) or (not is_long and market_mark_price >= liquidation_price)
+            should_be_liquidated = (
+                (is_long and market_mark_price <= liquidation_price) or 
+                (not is_long and market_mark_price >= liquidation_price)
+            )

             if should_be_liquidated:
-                print(f"{'Long' if is_long else 'Short'} position for market {client_market.id} and subaccount {position['subaccountId']} should be liquidated (liquidation price: {liquidation_price.normalize()} / mark price: {market_mark_price.normalize()})")
+                position_type = 'Long' if is_long else 'Short'
+                print(
+                    f"{position_type} position for market {client_market.id} "
+                    f"and subaccount {position['subaccountId']} should be liquidated "
+                    f"(liquidation price: {liquidation_price.normalize()} / "
+                    f"mark price: {market_mark_price.normalize()})"
+                )
                 liquidable_positions.append(position)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for market in derivative_markets["markets"]:
client_market = (await client.all_derivative_markets())[market["market"]["marketId"]]
market_mark_price = client_market._from_extended_chain_format(Decimal(market["markPrice"]))
for position in positions_per_market[client_market.id]:
is_long = position["position"]["isLong"]
quantity = client_market._from_extended_chain_format(Decimal(position["position"]["quantity"]))
entry_price = client_market._from_extended_chain_format(Decimal(position["position"]["entryPrice"]))
margin = client_market._from_extended_chain_format(Decimal(position["position"]["margin"]))
cumulative_funding_entry = client_market._from_extended_chain_format(Decimal(position["position"]["cumulativeFundingEntry"]))
market_cumulative_funding = client_market._from_extended_chain_format(Decimal(market["perpetualInfo"]["fundingInfo"]["cumulativeFunding"]))
adj_margin = adjusted_margin(quantity, margin, is_long, cumulative_funding_entry, market_cumulative_funding)
adjusted_unit_margin = (adj_margin / quantity) * (1 if is_long else -1)
maintenance_margin_ratio = client_market.maintenance_margin_ratio * (-1 if is_long else 1)
liquidation_price = (entry_price + adjusted_unit_margin) / (Decimal(1) + maintenance_margin_ratio)
should_be_liquidated = (is_long and market_mark_price <= liquidation_price) or (not is_long and market_mark_price >= liquidation_price)
if should_be_liquidated:
print(f"{'Long' if is_long else 'Short'} position for market {client_market.id} and subaccount {position['subaccountId']} should be liquidated (liquidation price: {liquidation_price.normalize()} / mark price: {market_mark_price.normalize()})")
liquidable_positions.append(position)
for market in derivative_markets["markets"]:
client_market = (await client.all_derivative_markets())[market["market"]["marketId"]]
market_mark_price = client_market._from_extended_chain_format(Decimal(market["markPrice"]))
for position in positions_per_market[client_market.id]:
is_long = position["position"]["isLong"]
quantity = client_market._from_extended_chain_format(
Decimal(position["position"]["quantity"])
)
entry_price = client_market._from_extended_chain_format(Decimal(position["position"]["entryPrice"]))
margin = client_market._from_extended_chain_format(Decimal(position["position"]["margin"]))
cumulative_funding_entry = client_market._from_extended_chain_format(
Decimal(position["position"]["cumulativeFundingEntry"])
)
market_cumulative_funding = client_market._from_extended_chain_format(
Decimal(market["perpetualInfo"]["fundingInfo"]["cumulativeFunding"])
)
if quantity == 0:
continue # Skip positions with zero quantity
adj_margin = adjusted_margin(
quantity, margin, is_long, cumulative_funding_entry, market_cumulative_funding
)
adjusted_unit_margin = (adj_margin / quantity) * (1 if is_long else -1)
maintenance_margin_ratio = client_market.maintenance_margin_ratio * (
-1 if is_long else 1
)
liquidation_price = (entry_price + adjusted_unit_margin) / (
Decimal(1) + maintenance_margin_ratio
)
should_be_liquidated = (
(is_long and market_mark_price <= liquidation_price)
or (not is_long and market_mark_price >= liquidation_price)
)
if should_be_liquidated:
position_type = "Long" if is_long else "Short"
print(
f"{position_type} position for market {client_market.id} "
f"and subaccount {position['subaccountId']} should be liquidated "
f"(liquidation price: {liquidation_price.normalize()} / "
f"mark price: {market_mark_price.normalize()})"
)
liquidable_positions.append(position)
🧰 Tools
🪛 GitHub Actions: pre-commit

[error] isort: import sorting issues fixed automatically


[error] black: file reformatted automatically to comply with code style

🤖 Prompt for AI Agents
In examples/chain_client/10_SearchLiquidablePositions.py between lines 33 and
55, fix multiple line length violations by breaking long expressions into
shorter lines for better readability. Add error handling to check if quantity is
zero before performing division to avoid division by zero errors. Also, validate
position data fields before using them to ensure they contain valid values, and
handle or skip invalid data gracefully to prevent runtime exceptions.

@aarmoa aarmoa merged commit 26b9e06 into dev Aug 1, 2025
9 of 10 checks passed
@aarmoa aarmoa deleted the feat/add_liquidable_positions_example_script branch August 1, 2025 20:10
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.

2 participants