Skip to main content

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}