-
Notifications
You must be signed in to change notification settings - Fork 33
Feat/add liquidable positions example script #395
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughA 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~15 minutes Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: .coderabbit.yaml 📒 Files selected for processing (1)
✨ Finishing Touches
🧪 Generate unit tests
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this 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
| # print(f"\n\n\n") | ||
| # print(json.dumps(liquidable_positions, indent=4)) | ||
|
|
Copilot
AI
Aug 1, 2025
There was a problem hiding this comment.
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.
| # print(f"\n\n\n") | |
| # print(json.dumps(liquidable_positions, indent=4)) |
| liquidable_positions.append(position) | ||
|
|
||
| # print(f"\n\n\n") | ||
| # print(json.dumps(liquidable_positions, indent=4)) |
Copilot
AI
Aug 1, 2025
There was a problem hiding this comment.
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.
| # 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)) |
| # print(f"\n\n\n") | ||
| # print(json.dumps(liquidable_positions, indent=4)) |
Copilot
AI
Aug 1, 2025
There was a problem hiding this comment.
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.
| # print(f"\n\n\n") | |
| # print(json.dumps(liquidable_positions, indent=4)) | |
| print(f"\n\n\n") | |
| print(json.dumps(liquidable_positions, indent=4)) |
There was a problem hiding this 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
📒 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.
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- Multiple line length violations (lines 41, 42, 50, 53)
- Missing error handling for division by zero when quantity is zero
- 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.
| 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.
…le positions example script
…le positions example script
Summary by CodeRabbit