poly-position-watcher 0.3.1__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {poly_position_watcher-0.3.1/poly_position_watcher.egg-info → poly_position_watcher-0.3.2}/PKG-INFO +47 -2
  2. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/README.md +46 -1
  3. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/_version.py +1 -1
  4. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/position_service.py +8 -5
  5. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/schema/position_model.py +14 -0
  6. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/trade_calculator.py +3 -6
  7. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2/poly_position_watcher.egg-info}/PKG-INFO +47 -2
  8. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher.egg-info/SOURCES.txt +1 -0
  9. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/pyproject.toml +1 -1
  10. poly_position_watcher-0.3.2/tests/test_position_model.py +59 -0
  11. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/tests/test_trade_calculator.py +6 -6
  12. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/LICENSE +0 -0
  13. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/MANIFEST.in +0 -0
  14. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/__init__.py +0 -0
  15. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/api_worker.py +0 -0
  16. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/common/__init__.py +0 -0
  17. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/common/enums.py +0 -0
  18. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/common/logger.py +0 -0
  19. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/schema/__init__.py +0 -0
  20. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/schema/base.py +0 -0
  21. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/schema/common_model.py +0 -0
  22. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher/wss_worker.py +0 -0
  23. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher.egg-info/dependency_links.txt +0 -0
  24. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher.egg-info/requires.txt +0 -0
  25. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/poly_position_watcher.egg-info/top_level.txt +0 -0
  26. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/setup.cfg +0 -0
  27. {poly_position_watcher-0.3.1 → poly_position_watcher-0.3.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: poly-position-watcher
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: polymarket proxy wallet redeem
5
5
  Home-page: https://github.com/tosmart01/polymarket-position-watcher
6
6
  Author: pinbar
@@ -100,6 +100,50 @@ Important:
100
100
  - `get_position()` does not fetch `/markets` automatically.
101
101
  - If a market is missing `feeSchedule`, fee calculation is skipped for that market and a warning is logged once.
102
102
 
103
+ Where does `feeSchedule` come from:
104
+ - Fetch a market or event from the Gamma API, then read the market object's `feeSchedule`.
105
+ - Your trade payload uses `trade.market` as the market `conditionId`, so register fee metadata with `conditionId` as the key.
106
+ - Official docs:
107
+ [Fees](https://docs.polymarket.com/trading/fees),
108
+ [Get event by id](https://docs.polymarket.com/api-reference/events/get-event-by-id),
109
+ [List markets](https://docs.polymarket.com/api-reference/markets/list-markets),
110
+ [Get market by slug](https://docs.polymarket.com/api-reference/markets/get-market-by-slug)
111
+
112
+ Example: fetch an event and register all nested market fee schedules
113
+
114
+ ```python
115
+ import requests
116
+
117
+ event = requests.get(
118
+ "https://gamma-api.polymarket.com/events/<event_id>",
119
+ timeout=10,
120
+ ).json()
121
+
122
+ fee_schedule_map = {
123
+ market["conditionId"]: market.get("feeSchedule")
124
+ for market in event.get("markets", [])
125
+ if market.get("feeSchedule")
126
+ }
127
+
128
+ service.set_market_fee_schedules(fee_schedule_map)
129
+ ```
130
+
131
+ Example: fetch a single market and register its fee schedule
132
+
133
+ ```python
134
+ import requests
135
+
136
+ market = requests.get(
137
+ "https://gamma-api.polymarket.com/markets/slug/<market-slug>",
138
+ timeout=10,
139
+ ).json()
140
+
141
+ service.set_market_fee_schedule(
142
+ market["conditionId"],
143
+ market.get("feeSchedule"),
144
+ )
145
+ ```
146
+
103
147
 
104
148
 
105
149
  Example output:
@@ -161,13 +205,14 @@ Some Polymarket markets enable taker fee / maker rebate. This library supports f
161
205
  - Enable with `enable_fee_calc=True`
162
206
  - Register `condition_id -> feeSchedule` through `service.set_market_fee_schedule(...)` or `service.set_market_fee_schedules(...)`
163
207
  - This registration step is required if you want fee-aware positions; the watcher does not auto-fetch `/markets`
208
+ - In practice, use the Gamma market/event response's `market.get("feeSchedule")`
164
209
  - Optionally override the fee handler with `fee_calc_fn`
165
210
  - Disable (default) if you prefer pre-fee positions
166
211
  - Returned position fields:
167
212
  `size` = post-fee net size, `original_size` = pre-fee net size, `fee_amount` = accumulated fee amount
168
213
 
169
214
  Default fee formula (when `fee_calc_fn` is not provided):
170
- `fee = size * price * rate * (price * (1 - price)) ** exponent`.
215
+ `fee = size * rate * price * (1 - price)`.
171
216
 
172
217
  On taker buys, the fee is deducted in shares, so `size` is reduced by `fee / price`.
173
218
  On taker sells, the fee is charged in USDC, so position size is unchanged and only `fee_amount` increases.
@@ -81,6 +81,50 @@ Important:
81
81
  - `get_position()` does not fetch `/markets` automatically.
82
82
  - If a market is missing `feeSchedule`, fee calculation is skipped for that market and a warning is logged once.
83
83
 
84
+ Where does `feeSchedule` come from:
85
+ - Fetch a market or event from the Gamma API, then read the market object's `feeSchedule`.
86
+ - Your trade payload uses `trade.market` as the market `conditionId`, so register fee metadata with `conditionId` as the key.
87
+ - Official docs:
88
+ [Fees](https://docs.polymarket.com/trading/fees),
89
+ [Get event by id](https://docs.polymarket.com/api-reference/events/get-event-by-id),
90
+ [List markets](https://docs.polymarket.com/api-reference/markets/list-markets),
91
+ [Get market by slug](https://docs.polymarket.com/api-reference/markets/get-market-by-slug)
92
+
93
+ Example: fetch an event and register all nested market fee schedules
94
+
95
+ ```python
96
+ import requests
97
+
98
+ event = requests.get(
99
+ "https://gamma-api.polymarket.com/events/<event_id>",
100
+ timeout=10,
101
+ ).json()
102
+
103
+ fee_schedule_map = {
104
+ market["conditionId"]: market.get("feeSchedule")
105
+ for market in event.get("markets", [])
106
+ if market.get("feeSchedule")
107
+ }
108
+
109
+ service.set_market_fee_schedules(fee_schedule_map)
110
+ ```
111
+
112
+ Example: fetch a single market and register its fee schedule
113
+
114
+ ```python
115
+ import requests
116
+
117
+ market = requests.get(
118
+ "https://gamma-api.polymarket.com/markets/slug/<market-slug>",
119
+ timeout=10,
120
+ ).json()
121
+
122
+ service.set_market_fee_schedule(
123
+ market["conditionId"],
124
+ market.get("feeSchedule"),
125
+ )
126
+ ```
127
+
84
128
 
85
129
 
86
130
  Example output:
@@ -142,13 +186,14 @@ Some Polymarket markets enable taker fee / maker rebate. This library supports f
142
186
  - Enable with `enable_fee_calc=True`
143
187
  - Register `condition_id -> feeSchedule` through `service.set_market_fee_schedule(...)` or `service.set_market_fee_schedules(...)`
144
188
  - This registration step is required if you want fee-aware positions; the watcher does not auto-fetch `/markets`
189
+ - In practice, use the Gamma market/event response's `market.get("feeSchedule")`
145
190
  - Optionally override the fee handler with `fee_calc_fn`
146
191
  - Disable (default) if you prefer pre-fee positions
147
192
  - Returned position fields:
148
193
  `size` = post-fee net size, `original_size` = pre-fee net size, `fee_amount` = accumulated fee amount
149
194
 
150
195
  Default fee formula (when `fee_calc_fn` is not provided):
151
- `fee = size * price * rate * (price * (1 - price)) ** exponent`.
196
+ `fee = size * rate * price * (1 - price)`.
152
197
 
153
198
  On taker buys, the fee is deducted in shares, so `size` is reduced by `fee / price`.
154
199
  On taker sells, the fee is charged in USDC, so position size is unchanged and only `fee_amount` increases.
@@ -1,3 +1,3 @@
1
1
  __all__ = ["__version__"]
2
2
 
3
- __version__ = "0.3.1"
3
+ __version__ = "0.3.2"
@@ -195,16 +195,19 @@ class PositionStore:
195
195
  def _status_is(trade: TradeMessage, status: TradeStatus) -> bool:
196
196
  return trade.status == status or trade.status == status.value
197
197
 
198
- filled_trades = [i for i in trades if _status_is(i, FAILED_TRADE)]
198
+ failed_trades = [i for i in trades if _status_is(i, FAILED_TRADE)]
199
199
  success_trades = [i for i in trades if not _status_is(i, FAILED_TRADE)]
200
200
  confirmed_trades = [
201
201
  i for i in success_trades if _status_is(i, TradeStatus.CONFIRMED)
202
202
  ]
203
- has_failed = bool(len(filled_trades))
203
+ has_failed = bool(len(failed_trades))
204
204
  if has_failed:
205
- filled_size = sum([i.size for i in filled_trades])
205
+ failed_size = sum(i.size for i in failed_trades)
206
+ failed_trade_ids = [trade.id for trade in failed_trades]
206
207
  logger.warning(
207
- f"found error trades, total size: {filled_size}: {filled_trades}"
208
+ "Found failed trades, total size: {}, ids: {}",
209
+ failed_size,
210
+ failed_trade_ids,
208
211
  )
209
212
  market_slug = next(
210
213
  (trade.market_slug for trade in trades if trade.market_slug), ""
@@ -240,7 +243,7 @@ class PositionStore:
240
243
  created_at=datetime.fromtimestamp(position_result.last_update),
241
244
  has_failed=has_failed,
242
245
  market_slug=market_slug,
243
- failed_trades=filled_trades,
246
+ failed_trades=failed_trades,
244
247
  )
245
248
  return current
246
249
  # if exists_pos := self.positions.get(token_id):
@@ -139,6 +139,20 @@ class UserPosition(PrettyPrintBaseModel):
139
139
  def failed_size(self) -> float:
140
140
  return sum(i.size for i in self.failed_trades)
141
141
 
142
+ @property
143
+ def failed_trade_ids(self) -> list[str]:
144
+ return [trade.id for trade in self.failed_trades]
145
+
146
+ def __str__(self):
147
+ lines = []
148
+ for name, value in self.__dict__.items():
149
+ if name == "failed_trades":
150
+ value = self.failed_trade_ids
151
+ lines.append(f"{name}: {value!r}")
152
+ return f"{self.__class__.__name__}(\n " + ",\n ".join(lines) + "\n)"
153
+
154
+ __repr__ = __str__
155
+
142
156
 
143
157
  class PositionDetails(BaseModel):
144
158
  """仓位详细信息"""
@@ -25,18 +25,15 @@ def _default_fee_calc(
25
25
  fee_schedule: Mapping[str, Any],
26
26
  ) -> tuple[float, float]:
27
27
  rate = float(fee_schedule.get("rate") or 0.0)
28
- exponent = float(fee_schedule.get("exponent") or 0.0)
29
28
  if size <= 0 or price <= 0 or rate <= 0:
30
29
  return size, 0.0
31
30
 
32
- fee_amount = round(
33
- max(size * price * rate * ((price * (1 - price)) ** exponent), 0.0),
34
- 4,
35
- )
31
+ fee_amount = round(max(size * rate * price * (1 - price), 0.0), 5)
36
32
  if fee_amount <= 0:
37
33
  return size, 0.0
38
34
 
39
- if side == Side.BUY or side == Side.BUY.value:
35
+ side_value = getattr(side, "value", side)
36
+ if str(side_value).upper() == "BUY":
40
37
  fee_size = fee_amount / price
41
38
  return max(size - fee_size, 0.0), fee_amount
42
39
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: poly-position-watcher
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: polymarket proxy wallet redeem
5
5
  Home-page: https://github.com/tosmart01/polymarket-position-watcher
6
6
  Author: pinbar
@@ -100,6 +100,50 @@ Important:
100
100
  - `get_position()` does not fetch `/markets` automatically.
101
101
  - If a market is missing `feeSchedule`, fee calculation is skipped for that market and a warning is logged once.
102
102
 
103
+ Where does `feeSchedule` come from:
104
+ - Fetch a market or event from the Gamma API, then read the market object's `feeSchedule`.
105
+ - Your trade payload uses `trade.market` as the market `conditionId`, so register fee metadata with `conditionId` as the key.
106
+ - Official docs:
107
+ [Fees](https://docs.polymarket.com/trading/fees),
108
+ [Get event by id](https://docs.polymarket.com/api-reference/events/get-event-by-id),
109
+ [List markets](https://docs.polymarket.com/api-reference/markets/list-markets),
110
+ [Get market by slug](https://docs.polymarket.com/api-reference/markets/get-market-by-slug)
111
+
112
+ Example: fetch an event and register all nested market fee schedules
113
+
114
+ ```python
115
+ import requests
116
+
117
+ event = requests.get(
118
+ "https://gamma-api.polymarket.com/events/<event_id>",
119
+ timeout=10,
120
+ ).json()
121
+
122
+ fee_schedule_map = {
123
+ market["conditionId"]: market.get("feeSchedule")
124
+ for market in event.get("markets", [])
125
+ if market.get("feeSchedule")
126
+ }
127
+
128
+ service.set_market_fee_schedules(fee_schedule_map)
129
+ ```
130
+
131
+ Example: fetch a single market and register its fee schedule
132
+
133
+ ```python
134
+ import requests
135
+
136
+ market = requests.get(
137
+ "https://gamma-api.polymarket.com/markets/slug/<market-slug>",
138
+ timeout=10,
139
+ ).json()
140
+
141
+ service.set_market_fee_schedule(
142
+ market["conditionId"],
143
+ market.get("feeSchedule"),
144
+ )
145
+ ```
146
+
103
147
 
104
148
 
105
149
  Example output:
@@ -161,13 +205,14 @@ Some Polymarket markets enable taker fee / maker rebate. This library supports f
161
205
  - Enable with `enable_fee_calc=True`
162
206
  - Register `condition_id -> feeSchedule` through `service.set_market_fee_schedule(...)` or `service.set_market_fee_schedules(...)`
163
207
  - This registration step is required if you want fee-aware positions; the watcher does not auto-fetch `/markets`
208
+ - In practice, use the Gamma market/event response's `market.get("feeSchedule")`
164
209
  - Optionally override the fee handler with `fee_calc_fn`
165
210
  - Disable (default) if you prefer pre-fee positions
166
211
  - Returned position fields:
167
212
  `size` = post-fee net size, `original_size` = pre-fee net size, `fee_amount` = accumulated fee amount
168
213
 
169
214
  Default fee formula (when `fee_calc_fn` is not provided):
170
- `fee = size * price * rate * (price * (1 - price)) ** exponent`.
215
+ `fee = size * rate * price * (1 - price)`.
171
216
 
172
217
  On taker buys, the fee is deducted in shares, so `size` is reduced by `fee / price`.
173
218
  On taker sells, the fee is charged in USDC, so position size is unchanged and only `fee_amount` increases.
@@ -21,4 +21,5 @@ poly_position_watcher/schema/__init__.py
21
21
  poly_position_watcher/schema/base.py
22
22
  poly_position_watcher/schema/common_model.py
23
23
  poly_position_watcher/schema/position_model.py
24
+ tests/test_position_model.py
24
25
  tests/test_trade_calculator.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "poly-position-watcher"
3
- version = "0.3.1"
3
+ version = "0.3.2"
4
4
  description = "polymarket proxy wallet redeem"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ import unittest
4
+
5
+ from poly_position_watcher.schema.position_model import TradeMessage, UserPosition
6
+
7
+
8
+ def build_failed_trade(trade_id: str) -> TradeMessage:
9
+ return TradeMessage(
10
+ type="TRADE",
11
+ event_type="trade",
12
+ asset_id="0xtoken",
13
+ id=trade_id,
14
+ maker_orders=[],
15
+ transaction_hash=f"0xhash-{trade_id}",
16
+ market="0xmarket",
17
+ maker_address="0xuser",
18
+ outcome="YES",
19
+ owner="0xuser",
20
+ price=0.25,
21
+ side="BUY",
22
+ size=10.0,
23
+ status="FAILED",
24
+ taker_order_id=f"0xorder-{trade_id}",
25
+ timestamp=1,
26
+ match_time=1,
27
+ last_update=1,
28
+ trade_owner="0xuser",
29
+ trader_side="TAKER",
30
+ fee_rate_bps=0,
31
+ market_slug="test-market",
32
+ )
33
+
34
+
35
+ class UserPositionTests(unittest.TestCase):
36
+ def test_failed_trade_ids_are_exposed_in_string_output(self) -> None:
37
+ position = UserPosition(
38
+ price=0.25,
39
+ size=10.0,
40
+ original_size=10.0,
41
+ volume=2.5,
42
+ fee_amount=0.0,
43
+ sellable_size=10.0,
44
+ token_id="0xtoken",
45
+ last_update=1,
46
+ market_id="0xmarket",
47
+ outcome="YES",
48
+ has_failed=True,
49
+ failed_trades=[build_failed_trade("failed-1"), build_failed_trade("failed-2")],
50
+ )
51
+
52
+ self.assertEqual(position.failed_trade_ids, ["failed-1", "failed-2"])
53
+ rendered = str(position)
54
+ self.assertIn("failed_trades: ['failed-1', 'failed-2']", rendered)
55
+ self.assertNotIn("transaction_hash", rendered)
56
+
57
+
58
+ if __name__ == "__main__":
59
+ unittest.main()
@@ -53,8 +53,8 @@ def build_taker_trade(
53
53
  )
54
54
 
55
55
 
56
- def expected_fee(size: float, price: float, rate: float, exponent: float) -> float:
57
- return round(size * price * rate * ((price * (1 - price)) ** exponent), 4)
56
+ def expected_fee(size: float, price: float, rate: float) -> float:
57
+ return round(size * rate * price * (1 - price), 5)
58
58
 
59
59
 
60
60
  class TradeCalculatorFeeTests(unittest.TestCase):
@@ -139,7 +139,7 @@ class TradeCalculatorFeeTests(unittest.TestCase):
139
139
  fee_schedule_by_market={MARKET_ID: FEE_SCHEDULE},
140
140
  )
141
141
 
142
- fee_amount = expected_fee(100.0, 0.25, 0.0175, 1)
142
+ fee_amount = expected_fee(100.0, 0.25, 0.0175)
143
143
  expected_size = 100.0 - fee_amount / 0.25
144
144
 
145
145
  self.assertTrue(math.isclose(result.fee_amount, fee_amount, rel_tol=0, abs_tol=1e-9))
@@ -166,7 +166,7 @@ class TradeCalculatorFeeTests(unittest.TestCase):
166
166
  fee_schedule_by_market={MARKET_ID: FEE_SCHEDULE},
167
167
  )
168
168
 
169
- fee_amount = expected_fee(100.0, 0.25, 0.0175, 1)
169
+ fee_amount = expected_fee(100.0, 0.25, 0.0175)
170
170
  net_proceeds = 100.0 * 0.25 - fee_amount
171
171
 
172
172
  self.assertTrue(math.isclose(result.fee_amount, fee_amount, rel_tol=0, abs_tol=1e-9))
@@ -183,7 +183,7 @@ class TradeCalculatorFeeTests(unittest.TestCase):
183
183
  price=0.25,
184
184
  match_time=1,
185
185
  )
186
- buy_fee = expected_fee(100.0, 0.25, 0.0175, 1)
186
+ buy_fee = expected_fee(100.0, 0.25, 0.0175)
187
187
  net_buy_size = 100.0 - buy_fee / 0.25
188
188
 
189
189
  sell_trade = build_taker_trade(
@@ -201,7 +201,7 @@ class TradeCalculatorFeeTests(unittest.TestCase):
201
201
  fee_schedule_by_market={MARKET_ID: FEE_SCHEDULE},
202
202
  )
203
203
 
204
- sell_fee = expected_fee(net_buy_size, 0.4, 0.0175, 1)
204
+ sell_fee = expected_fee(net_buy_size, 0.4, 0.0175)
205
205
  expected_realized_pnl = (net_buy_size * 0.4 - sell_fee) - 25.0
206
206
 
207
207
  self.assertTrue(math.isclose(result.size, 0.0, rel_tol=0, abs_tol=1e-9))