Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
51 changes: 22 additions & 29 deletions sdks/python/pmxt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3397,38 +3397,31 @@ def get_execution_price_detailed(
Returns:
Detailed execution result
"""
try:
# Convert order_book to dict for API call
bids = [{"price": b.price, "size": b.size} for b in order_book.bids]
asks = [{"price": a.price, "size": a.size} for a in order_book.asks]
ob_dict = {"bids": bids, "asks": asks, "timestamp": order_book.timestamp}
if amount <= 0:
raise ValueError("Amount must be greater than 0")

body = {
"args": [ob_dict, side, amount]
}
levels = [level for level in (order_book.asks if side == "buy" else order_book.bids) if level.size > 0]
levels.sort(key=lambda level: level.price, reverse=(side == "sell"))

creds = self._get_credentials_dict()
if creds:
body["credentials"] = creds
if not levels:
return ExecutionPriceResult(price=0, filled_amount=0, fully_filled=False)

url = f"{self._resolve_sidecar_host()}/api/{self.exchange_name}/getExecutionPriceDetailed"
remaining_amount = amount
total_cost = 0.0
filled_amount = 0.0
epsilon = 0.00000001

headers = {"Content-Type": "application/json", "Accept": "application/json"}
headers.update(self._get_auth_headers())

response = self._fetch_with_retry(
lambda: self._api_client.call_api(
method="POST",
url=url,
body=body,
header_params=headers
)
)
for level in levels:
if remaining_amount <= epsilon:
break

response.read()
data_json = json.loads(response.data)
fill_size = min(remaining_amount, level.size)
total_cost += fill_size * level.price
filled_amount += fill_size
remaining_amount -= fill_size

data = self._handle_response(data_json)
return _convert_execution_result(data)
except Exception as e:
raise self._parse_api_exception(e) from None
return ExecutionPriceResult(
price=total_cost / filled_amount if filled_amount > epsilon else 0,
filled_amount=filled_amount,
fully_filled=remaining_amount <= epsilon,
)
31 changes: 31 additions & 0 deletions sdks/python/tests/test_execution_price_detailed_local.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from pmxt import Polymarket
from pmxt.models import OrderBook, OrderLevel


def test_get_execution_price_detailed_computes_locally_without_sidecar():
client = Polymarket(auto_start_server=False)
result = client.get_execution_price_detailed(
OrderBook(
bids=[OrderLevel(price=0.41, size=5), OrderLevel(price=0.43, size=10)],
asks=[OrderLevel(price=0.52, size=4), OrderLevel(price=0.5, size=6)],
),
"buy",
8,
)

assert abs(result.price - 0.505) < 1e-12
assert result.filled_amount == 8
assert result.fully_filled is True


def test_get_execution_price_detailed_reports_partial_fill():
client = Polymarket(auto_start_server=False)
result = client.get_execution_price_detailed(
OrderBook(bids=[OrderLevel(price=0.42, size=2)], asks=[]),
"sell",
5,
)

assert abs(result.price - 0.42) < 1e-12
assert result.filled_amount == 2
assert result.fully_filled is False
63 changes: 29 additions & 34 deletions sdks/typescript/pmxt/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2710,54 +2710,49 @@ export abstract class Exchange {
}

/**
* Calculate detailed execution price information.
* Uses the sidecar server for calculation to ensure consistency.
* Calculate detailed execution price information by walking the order book locally.
*
* @param orderBook - The current order book
* @param side - 'buy' or 'sell'
* @param amount - The amount to execute
* @returns Detailed execution result
*/
async getExecutionPriceDetailed(
getExecutionPriceDetailed(
orderBook: OrderBook,
side: 'buy' | 'sell',
amount: number
): Promise<ExecutionPriceResult> {
await this.initPromise;
try {
const body: any = {
args: [orderBook, side, amount]
};
const credentials = this.getCredentials();
if (credentials) {
body.credentials = credentials;
}
): ExecutionPriceResult {
if (amount <= 0) {
throw new Error('Amount must be greater than 0');
}

const url = `${this.resolveBaseUrl()}/api/${this.exchangeName}/getExecutionPriceDetailed`;
const levels = (side === 'buy' ? orderBook.asks : orderBook.bids)
.filter((level) => level.size > 0)
.sort((a, b) => side === 'buy' ? a.price - b.price : b.price - a.price);

const response = await this.fetchWithRetry(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...this.config.headers
},
body: JSON.stringify(body)
});
if (levels.length === 0) {
return { price: 0, filledAmount: 0, fullyFilled: false };
}

if (!response.ok) {
const body = await response.json().catch(() => ({}));
if (body.error && typeof body.error === "object") {
throw fromServerError(body.error);
}
throw new PmxtError(body.error?.message || response.statusText);
}
let remainingAmount = amount;
let totalCost = 0;
let filledAmount = 0;
const epsilon = 0.00000001;

const json = await response.json();
return this.handleResponse(json);
} catch (error) {
if (error instanceof PmxtError) throw error;
throw new PmxtError(`Failed to get execution price: ${error}`);
for (const level of levels) {
if (remainingAmount <= epsilon) break;

const fillSize = Math.min(remainingAmount, level.size);
totalCost += fillSize * level.price;
filledAmount += fillSize;
remainingAmount -= fillSize;
}

return {
price: filledAmount > epsilon ? totalCost / filledAmount : 0,
filledAmount,
fullyFilled: remainingAmount <= epsilon,
};
}

// ----------------------------------------------------------------------------
Expand Down
37 changes: 37 additions & 0 deletions sdks/typescript/tests/execution-price-detailed-local.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Polymarket } from '../pmxt/client';

describe('getExecutionPriceDetailed', () => {
it('computes detailed execution price locally without sidecar access', () => {
const client = new Polymarket({ autoStartServer: false });
const result = client.getExecutionPriceDetailed({
bids: [
{ price: 0.41, size: 5 },
{ price: 0.43, size: 10 },
],
asks: [
{ price: 0.52, size: 4 },
{ price: 0.5, size: 6 },
],
}, 'buy', 8);

expect(result).toEqual({
price: 0.505,
filledAmount: 8,
fullyFilled: true,
});
});

it('reports partial fills instead of requiring a network call', () => {
const client = new Polymarket({ autoStartServer: false });
const result = client.getExecutionPriceDetailed({
bids: [{ price: 0.42, size: 2 }],
asks: [],
}, 'sell', 5);

expect(result).toEqual({
price: 0.42,
filledAmount: 2,
fullyFilled: false,
});
});
});
Loading