nautilus_polymarket/python/sort.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3// https://nautechsystems.io
4//
5// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6// You may not use this file except in compliance with the License.
7// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Python bindings for Polymarket trade parsing.
17
18use pyo3::{prelude::*, types::PyDict};
19
20use crate::http::data_api::build_polymarket_trade_id;
21
22/// Returns a deterministic sort key tuple for a Polymarket Data API v2 trade
23/// dict.
24///
25/// The Polymarket Data API timestamps are second-resolution and the public
26/// endpoint does not guarantee a stable order across pages. Pass this as the
27/// `key` argument to `list.sort` so concatenated pages produce a deterministic
28/// chronological stream.
29///
30/// The returned tuple is `(timestamp, transaction_hash, token_id, side, price, size)`,
31/// with all string-typed fields stringified to match Python's behavior for
32/// dictionaries with mixed-type values. Missing keys default to empty strings,
33/// matching `dict.get(key, "")`.
34#[pyfunction]
35#[pyo3(name = "polymarket_trade_sort_key")]
36pub fn py_polymarket_trade_sort_key(
37 trade: &Bound<'_, PyDict>,
38) -> PyResult<(i64, String, String, String, String, String)> {
39 fn extract_string(trade: &Bound<'_, PyDict>, key: &str) -> PyResult<String> {
40 match trade.get_item(key)? {
41 Some(value) => Ok(value.str()?.extract::<String>()?),
42 None => Ok(String::new()),
43 }
44 }
45
46 let timestamp: i64 = match trade.get_item("timestamp")? {
47 Some(value) => value.extract()?,
48 None => 0,
49 };
50 let transaction_hash = extract_string(trade, "transaction_hash")?;
51 let asset = extract_string(trade, "token_id")?;
52 let side = extract_string(trade, "side")?;
53 let price = extract_string(trade, "price")?;
54 let size = extract_string(trade, "size")?;
55
56 Ok((timestamp, transaction_hash, asset, side, price, size))
57}
58
59/// Returns the composite Polymarket TradeId for a fill.
60///
61/// Polygon transactions can settle multiple fills sharing the same
62/// `transaction_hash`. Using only the last 36 chars collapses them to a single
63/// TradeId and downstream catalog readers silently drop duplicates. The id
64/// composes a hash suffix, an asset suffix, and a per-(tx, asset) sequence so
65/// every fill is preserved.
66#[pyfunction]
67#[pyo3(name = "polymarket_trade_id")]
68pub fn py_polymarket_trade_id(transaction_hash: &str, asset: &str, seq: u32) -> String {
69 build_polymarket_trade_id(transaction_hash, asset, seq)
70}
71
72#[cfg(test)]
73mod tests {
74 use rstest::rstest;
75
76 use super::*;
77
78 #[rstest]
79 fn test_trade_sort_key_reads_v2_fields() {
80 Python::initialize();
81 Python::attach(|py| {
82 let trade = PyDict::new(py);
83 trade.set_item("timestamp", 1_710_000_000).unwrap();
84 trade.set_item("transaction_hash", "0xabc").unwrap();
85 trade.set_item("token_id", "1234token").unwrap();
86 trade.set_item("side", "BUY").unwrap();
87 trade.set_item("price", "0.55").unwrap();
88 trade.set_item("size", "10").unwrap();
89
90 let key = py_polymarket_trade_sort_key(&trade).unwrap();
91
92 assert_eq!(
93 key,
94 (
95 1_710_000_000,
96 "0xabc".to_string(),
97 "1234token".to_string(),
98 "BUY".to_string(),
99 "0.55".to_string(),
100 "10".to_string(),
101 )
102 );
103 });
104 }
105
106 #[rstest]
107 fn test_trade_sort_key_ignores_v1_field_names() {
108 // v1 camelCase keys must no longer contribute sort components.
109 Python::initialize();
110 Python::attach(|py| {
111 let trade = PyDict::new(py);
112 trade.set_item("timestamp", 1).unwrap();
113 trade.set_item("transactionHash", "0xabc").unwrap();
114 trade.set_item("asset", "1234token").unwrap();
115
116 let key = py_polymarket_trade_sort_key(&trade).unwrap();
117
118 assert_eq!(key.1, String::new());
119 assert_eq!(key.2, String::new());
120 });
121 }
122}