Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions examples/chain_client/10_SearchLiquidablePositions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import asyncio
from decimal import Decimal

from pyinjective.async_client_v2 import AsyncClient
from pyinjective.core.network import Network

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.


async def main() -> None:
# select network: local, testnet, mainnet
network = Network.mainnet()

# initialize grpc client
client = AsyncClient(network)

positions_per_market = dict()

positions_dict = await client.fetch_chain_positions()
liquidable_positions = []

for position in positions_dict["state"]:
if position["marketId"] not in positions_per_market:
positions_per_market[position["marketId"]] = []
positions_per_market[position["marketId"]].append(position)

derivative_markets = await client.fetch_chain_derivative_markets(
status="Active",
market_ids=list(positions_per_market.keys()),
)

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.

# 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 +71 to +72
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.

Comment on lines +71 to +73
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.
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(main())
Loading