From 95f9b211e56372d3c17a2bb8942ae8133da64cdd Mon Sep 17 00:00:00 2001 From: Abel Armoa <30988000+aarmoa@users.noreply.github.com> Date: Fri, 1 Aug 2025 15:30:52 -0300 Subject: [PATCH 1/5] feat: added a new example script to search for liquidable positions --- .../10_SearchLiquidablePositions.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 examples/chain_client/10_SearchLiquidablePositions.py diff --git a/examples/chain_client/10_SearchLiquidablePositions.py b/examples/chain_client/10_SearchLiquidablePositions.py new file mode 100644 index 00000000..4345c014 --- /dev/null +++ b/examples/chain_client/10_SearchLiquidablePositions.py @@ -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 + +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) + + # print(f"\n\n\n") + # print(json.dumps(liquidable_positions, indent=4)) + +if __name__ == "__main__": + asyncio.get_event_loop().run_until_complete(main()) From 65f04268e2d6457ae833725d62b13f6e10819d15 Mon Sep 17 00:00:00 2001 From: Abel Armoa <30988000+aarmoa@users.noreply.github.com> Date: Fri, 1 Aug 2025 16:13:05 -0300 Subject: [PATCH 2/5] fix: fix liquidation price calculation in the liquidable positions example script --- examples/chain_client/10_SearchLiquidablePositions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/chain_client/10_SearchLiquidablePositions.py b/examples/chain_client/10_SearchLiquidablePositions.py index 4345c014..0f250a60 100644 --- a/examples/chain_client/10_SearchLiquidablePositions.py +++ b/examples/chain_client/10_SearchLiquidablePositions.py @@ -42,14 +42,15 @@ async def main() -> None: 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) - + 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"Test {(Decimal(1) + maintenance_margin_ratio)} || market mantainance margin ratio: {client_market.maintenance_margin_ratio}") 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) From a7d26e624b72146bb96b634f38c8117fddff135d Mon Sep 17 00:00:00 2001 From: Abel Armoa <30988000+aarmoa@users.noreply.github.com> Date: Fri, 1 Aug 2025 17:01:08 -0300 Subject: [PATCH 3/5] fix: fixed the position liquidation price calculation in the liquidable positions example script --- examples/chain_client/10_SearchLiquidablePositions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/chain_client/10_SearchLiquidablePositions.py b/examples/chain_client/10_SearchLiquidablePositions.py index 0f250a60..0d820ef3 100644 --- a/examples/chain_client/10_SearchLiquidablePositions.py +++ b/examples/chain_client/10_SearchLiquidablePositions.py @@ -5,7 +5,7 @@ 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) + unrealized_funding_payment = (cumulative_funding - cumulative_funding_entry) * quantity * (-1 if is_long else 1) return margin + unrealized_funding_payment async def main() -> None: From 987e82b7be84e280809513393d8c9723d77e2560 Mon Sep 17 00:00:00 2001 From: Abel Armoa <30988000+aarmoa@users.noreply.github.com> Date: Fri, 1 Aug 2025 17:02:23 -0300 Subject: [PATCH 4/5] fix: fixed the position liquidation price calculation in the liquidable positions example script --- examples/chain_client/10_SearchLiquidablePositions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/chain_client/10_SearchLiquidablePositions.py b/examples/chain_client/10_SearchLiquidablePositions.py index 0d820ef3..dc414c43 100644 --- a/examples/chain_client/10_SearchLiquidablePositions.py +++ b/examples/chain_client/10_SearchLiquidablePositions.py @@ -50,7 +50,6 @@ async def main() -> None: 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"Test {(Decimal(1) + maintenance_margin_ratio)} || market mantainance margin ratio: {client_market.maintenance_margin_ratio}") 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) From 1c7fb5520a0d2c5bc541fc9aa3b3802f34b59500 Mon Sep 17 00:00:00 2001 From: Abel Armoa <30988000+aarmoa@users.noreply.github.com> Date: Fri, 1 Aug 2025 17:08:00 -0300 Subject: [PATCH 5/5] fix: solved pre-commit issues --- .../10_SearchLiquidablePositions.py | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/examples/chain_client/10_SearchLiquidablePositions.py b/examples/chain_client/10_SearchLiquidablePositions.py index dc414c43..899ccc46 100644 --- a/examples/chain_client/10_SearchLiquidablePositions.py +++ b/examples/chain_client/10_SearchLiquidablePositions.py @@ -4,10 +4,14 @@ 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: + +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 + async def main() -> None: # select network: local, testnet, mainnet network = Network.mainnet() @@ -38,8 +42,12 @@ async def main() -> None: 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) adjusted_unit_margin = (adj_margin / quantity) * (-1 if is_long else 1) @@ -47,14 +55,22 @@ async def main() -> None: 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_side = "Long" if is_long else "Short" + print( + f"{position_side} position for market {client_market.id} and subaccount " + f"{position['subaccountId']} should be liquidated (liquidation price: " + f"{liquidation_price.normalize()} / mark price: {market_mark_price.normalize()})" + ) liquidable_positions.append(position) # print(f"\n\n\n") # print(json.dumps(liquidable_positions, indent=4)) + if __name__ == "__main__": asyncio.get_event_loop().run_until_complete(main())