outcometick 1.4.0__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.
- outcometick-1.4.0/LICENSE +21 -0
- outcometick-1.4.0/PKG-INFO +59 -0
- outcometick-1.4.0/README.md +48 -0
- outcometick-1.4.0/outcometick/__init__.py +67 -0
- outcometick-1.4.0/outcometick/py.typed +0 -0
- outcometick-1.4.0/outcometick.egg-info/PKG-INFO +59 -0
- outcometick-1.4.0/outcometick.egg-info/SOURCES.txt +10 -0
- outcometick-1.4.0/outcometick.egg-info/dependency_links.txt +1 -0
- outcometick-1.4.0/outcometick.egg-info/top_level.txt +1 -0
- outcometick-1.4.0/pyproject.toml +20 -0
- outcometick-1.4.0/setup.cfg +4 -0
- outcometick-1.4.0/tests/test_sdk.py +71 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ligengxin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: outcometick
|
|
3
|
+
Version: 1.4.0
|
|
4
|
+
Summary: Strategy SDK for outcometick prediction-market backtests
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Documentation, https://outcometick.com/docs/sdk
|
|
7
|
+
Requires-Python: >=3.14
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
<!--
|
|
13
|
+
GENERATED — do not edit this repository directly.
|
|
14
|
+
|
|
15
|
+
Every file here is built from the outcometick monorepo by
|
|
16
|
+
scripts/publish-sdk-repos.mjs and overwritten wholesale on each publish.
|
|
17
|
+
An edit made here survives until the next publish and then disappears.
|
|
18
|
+
|
|
19
|
+
Generated from monorepo revision 27a03218dafadd78d846fb906e64e2981aa6f51c.
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
# outcometick
|
|
23
|
+
|
|
24
|
+
The Python strategy SDK for [outcometick.com](https://outcometick.com).
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from outcometick import Strategy, Order
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MeanReversion(Strategy):
|
|
31
|
+
def on_market_open(self, ctx, market):
|
|
32
|
+
self.entered = False
|
|
33
|
+
|
|
34
|
+
def on_tick(self, ctx, tick):
|
|
35
|
+
z = ctx.zscore(tick.value, window=180)
|
|
36
|
+
if self.entered or abs(z) < ctx.p.entry_z:
|
|
37
|
+
return None
|
|
38
|
+
side = "DOWN" if z > 0 else "UP"
|
|
39
|
+
book = ctx.book()
|
|
40
|
+
self.entered = True
|
|
41
|
+
return Order(side=side, size=ctx.p.size, limit=book.best(side))
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This package is the SDK surface: the `Strategy` base class and the `Order`
|
|
45
|
+
value object, so your editor and type checker know the API and your own tests
|
|
46
|
+
can import it.
|
|
47
|
+
|
|
48
|
+
**The `ot` command line is distributed via npm**, not here:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
npm i -g outcometick
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
It runs Python strategies by spawning your local `python3`. Shipping one CLI
|
|
55
|
+
rather than two is deliberate — `ot check` must be the same validator the queue
|
|
56
|
+
runs, and a second implementation in another language would be the first thing
|
|
57
|
+
to drift.
|
|
58
|
+
|
|
59
|
+
Full reference: https://outcometick.com/docs/sdk
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
GENERATED — do not edit this repository directly.
|
|
3
|
+
|
|
4
|
+
Every file here is built from the outcometick monorepo by
|
|
5
|
+
scripts/publish-sdk-repos.mjs and overwritten wholesale on each publish.
|
|
6
|
+
An edit made here survives until the next publish and then disappears.
|
|
7
|
+
|
|
8
|
+
Generated from monorepo revision 27a03218dafadd78d846fb906e64e2981aa6f51c.
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
# outcometick
|
|
12
|
+
|
|
13
|
+
The Python strategy SDK for [outcometick.com](https://outcometick.com).
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from outcometick import Strategy, Order
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MeanReversion(Strategy):
|
|
20
|
+
def on_market_open(self, ctx, market):
|
|
21
|
+
self.entered = False
|
|
22
|
+
|
|
23
|
+
def on_tick(self, ctx, tick):
|
|
24
|
+
z = ctx.zscore(tick.value, window=180)
|
|
25
|
+
if self.entered or abs(z) < ctx.p.entry_z:
|
|
26
|
+
return None
|
|
27
|
+
side = "DOWN" if z > 0 else "UP"
|
|
28
|
+
book = ctx.book()
|
|
29
|
+
self.entered = True
|
|
30
|
+
return Order(side=side, size=ctx.p.size, limit=book.best(side))
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This package is the SDK surface: the `Strategy` base class and the `Order`
|
|
34
|
+
value object, so your editor and type checker know the API and your own tests
|
|
35
|
+
can import it.
|
|
36
|
+
|
|
37
|
+
**The `ot` command line is distributed via npm**, not here:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
npm i -g outcometick
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
It runs Python strategies by spawning your local `python3`. Shipping one CLI
|
|
44
|
+
rather than two is deliberate — `ot check` must be the same validator the queue
|
|
45
|
+
runs, and a second implementation in another language would be the first thing
|
|
46
|
+
to drift.
|
|
47
|
+
|
|
48
|
+
Full reference: https://outcometick.com/docs/sdk
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""The SDK surface a submitted Python strategy imports.
|
|
2
|
+
|
|
3
|
+
Deliberately tiny. `Strategy` is a base class that exists so `entry` can be
|
|
4
|
+
checked against something, and `Order` is a value object. Everything a strategy
|
|
5
|
+
can actually DO arrives through `ctx`, which the runner constructs — there is no
|
|
6
|
+
way to reach the outside from here, because there is nothing here to reach it
|
|
7
|
+
with.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
# Named explicitly so `from __future__ import annotations` does not leak
|
|
13
|
+
# `annotations` into the package's public surface — this module is also
|
|
14
|
+
# published to PyPI, where dir(outcometick) is what a user reads as the API.
|
|
15
|
+
__all__ = ("Strategy", "Order", "SIDES")
|
|
16
|
+
|
|
17
|
+
SIDES = ("UP", "DOWN")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Strategy:
|
|
21
|
+
"""Base class for a submitted strategy.
|
|
22
|
+
|
|
23
|
+
The hooks are not defined here on purpose. A default no-op `on_tick` would
|
|
24
|
+
turn "you declared a hook you did not implement" — a rejection the submitter
|
|
25
|
+
can fix in seconds — into a run that quietly never trades and bills for an
|
|
26
|
+
empty equity curve.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
#: Params from the manifest, injected by the runner before the first hook.
|
|
30
|
+
p: dict = {}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Order:
|
|
34
|
+
"""An order a hook returns. Never sent — returned, and matched by the runner
|
|
35
|
+
against the depth that was actually resting at that millisecond.
|
|
36
|
+
|
|
37
|
+
`limit` is a bound in whichever direction protects you: a ceiling when
|
|
38
|
+
opening, a floor when reducing.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
__slots__ = ("side", "size", "limit", "hold_s", "reduce_only", "tif", "tag")
|
|
42
|
+
|
|
43
|
+
def __init__(self, side, size, limit=None, hold_s=None, reduce_only=False,
|
|
44
|
+
tif="ioc", tag=None):
|
|
45
|
+
if side not in SIDES:
|
|
46
|
+
raise ValueError(f'side must be "UP" or "DOWN", got {side!r}')
|
|
47
|
+
if not (isinstance(size, (int, float)) and size > 0):
|
|
48
|
+
raise ValueError(f"size must be a positive number, got {size!r}")
|
|
49
|
+
if limit is not None and not (0 <= float(limit) <= 1):
|
|
50
|
+
# A binary outcome token trades between 0 and 1. A limit outside
|
|
51
|
+
# that is not a price, and silently clamping it would fill an order
|
|
52
|
+
# the strategy never asked for.
|
|
53
|
+
raise ValueError(f"limit must be between 0 and 1, got {limit!r}")
|
|
54
|
+
if tif != "ioc":
|
|
55
|
+
# Not modelled, so not accepted. See "Not supported yet" in the docs.
|
|
56
|
+
raise ValueError(f'tif must be "ioc"; {tif!r} is not supported yet')
|
|
57
|
+
self.side = side
|
|
58
|
+
self.size = float(size)
|
|
59
|
+
self.limit = None if limit is None else float(limit)
|
|
60
|
+
self.hold_s = None if hold_s is None else int(hold_s)
|
|
61
|
+
self.reduce_only = bool(reduce_only)
|
|
62
|
+
self.tif = tif
|
|
63
|
+
self.tag = tag
|
|
64
|
+
|
|
65
|
+
def __repr__(self) -> str:
|
|
66
|
+
return (f"Order(side={self.side!r}, size={self.size}, limit={self.limit}, "
|
|
67
|
+
f"reduce_only={self.reduce_only})")
|
|
File without changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: outcometick
|
|
3
|
+
Version: 1.4.0
|
|
4
|
+
Summary: Strategy SDK for outcometick prediction-market backtests
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Documentation, https://outcometick.com/docs/sdk
|
|
7
|
+
Requires-Python: >=3.14
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Dynamic: license-file
|
|
11
|
+
|
|
12
|
+
<!--
|
|
13
|
+
GENERATED — do not edit this repository directly.
|
|
14
|
+
|
|
15
|
+
Every file here is built from the outcometick monorepo by
|
|
16
|
+
scripts/publish-sdk-repos.mjs and overwritten wholesale on each publish.
|
|
17
|
+
An edit made here survives until the next publish and then disappears.
|
|
18
|
+
|
|
19
|
+
Generated from monorepo revision 27a03218dafadd78d846fb906e64e2981aa6f51c.
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
# outcometick
|
|
23
|
+
|
|
24
|
+
The Python strategy SDK for [outcometick.com](https://outcometick.com).
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from outcometick import Strategy, Order
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MeanReversion(Strategy):
|
|
31
|
+
def on_market_open(self, ctx, market):
|
|
32
|
+
self.entered = False
|
|
33
|
+
|
|
34
|
+
def on_tick(self, ctx, tick):
|
|
35
|
+
z = ctx.zscore(tick.value, window=180)
|
|
36
|
+
if self.entered or abs(z) < ctx.p.entry_z:
|
|
37
|
+
return None
|
|
38
|
+
side = "DOWN" if z > 0 else "UP"
|
|
39
|
+
book = ctx.book()
|
|
40
|
+
self.entered = True
|
|
41
|
+
return Order(side=side, size=ctx.p.size, limit=book.best(side))
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This package is the SDK surface: the `Strategy` base class and the `Order`
|
|
45
|
+
value object, so your editor and type checker know the API and your own tests
|
|
46
|
+
can import it.
|
|
47
|
+
|
|
48
|
+
**The `ot` command line is distributed via npm**, not here:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
npm i -g outcometick
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
It runs Python strategies by spawning your local `python3`. Shipping one CLI
|
|
55
|
+
rather than two is deliberate — `ot check` must be the same validator the queue
|
|
56
|
+
runs, and a second implementation in another language would be the first thing
|
|
57
|
+
to drift.
|
|
58
|
+
|
|
59
|
+
Full reference: https://outcometick.com/docs/sdk
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
outcometick
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "outcometick"
|
|
3
|
+
version = "1.4.0"
|
|
4
|
+
description = "Strategy SDK for outcometick prediction-market backtests"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
|
|
9
|
+
[project.urls]
|
|
10
|
+
Documentation = "https://outcometick.com/docs/sdk"
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["setuptools>=68"]
|
|
14
|
+
build-backend = "setuptools.build_meta"
|
|
15
|
+
|
|
16
|
+
[tool.setuptools]
|
|
17
|
+
packages = ["outcometick"]
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.package-data]
|
|
20
|
+
outcometick = ["py.typed"]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Tests for the public Python SDK surface.
|
|
2
|
+
|
|
3
|
+
Small on purpose: this package is the SDK surface and nothing else, so these
|
|
4
|
+
assert the contract a strategy author actually depends on — the names that
|
|
5
|
+
exist, and the argument validation that rejects a bad order at construction
|
|
6
|
+
rather than silently at fill time.
|
|
7
|
+
|
|
8
|
+
They ship with the published package so the mirror is a repository someone can
|
|
9
|
+
clone and verify rather than take on faith.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
|
|
14
|
+
from outcometick import Order, Strategy, SIDES
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestSurface(unittest.TestCase):
|
|
18
|
+
def test_exports_are_exactly_what_is_documented(self):
|
|
19
|
+
import outcometick
|
|
20
|
+
|
|
21
|
+
self.assertEqual(sorted(outcometick.__all__), ["Order", "SIDES", "Strategy"])
|
|
22
|
+
# __all__ also keeps `annotations` from `from __future__ import` out of
|
|
23
|
+
# the public namespace, which is what a reader sees in dir().
|
|
24
|
+
self.assertNotIn("annotations", outcometick.__all__)
|
|
25
|
+
|
|
26
|
+
def test_sides_are_the_two_outcome_tokens(self):
|
|
27
|
+
self.assertEqual(tuple(SIDES), ("UP", "DOWN"))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestOrder(unittest.TestCase):
|
|
31
|
+
def test_accepts_a_well_formed_order(self):
|
|
32
|
+
o = Order(side="UP", size=100, limit=0.55)
|
|
33
|
+
self.assertEqual(o.side, "UP")
|
|
34
|
+
self.assertEqual(o.size, 100.0)
|
|
35
|
+
self.assertEqual(o.limit, 0.55)
|
|
36
|
+
self.assertFalse(o.reduce_only)
|
|
37
|
+
|
|
38
|
+
def test_limit_is_optional(self):
|
|
39
|
+
self.assertIsNone(Order(side="DOWN", size=1).limit)
|
|
40
|
+
|
|
41
|
+
def test_rejects_a_side_that_is_not_an_outcome_token(self):
|
|
42
|
+
with self.assertRaises(ValueError):
|
|
43
|
+
Order(side="SIDEWAYS", size=100)
|
|
44
|
+
|
|
45
|
+
def test_rejects_a_non_positive_size(self):
|
|
46
|
+
for bad in (0, -1):
|
|
47
|
+
with self.assertRaises(ValueError):
|
|
48
|
+
Order(side="UP", size=bad)
|
|
49
|
+
|
|
50
|
+
def test_rejects_a_limit_outside_zero_to_one(self):
|
|
51
|
+
# A binary outcome token trades between 0 and 1. Anything else is not a
|
|
52
|
+
# price, and clamping it would fill an order nobody asked for.
|
|
53
|
+
for bad in (-0.01, 1.5, 100):
|
|
54
|
+
with self.assertRaises(ValueError):
|
|
55
|
+
Order(side="UP", size=1, limit=bad)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TestStrategy(unittest.TestCase):
|
|
59
|
+
def test_params_default_to_empty_so_a_hook_can_read_them(self):
|
|
60
|
+
self.assertEqual(Strategy().p, {})
|
|
61
|
+
|
|
62
|
+
def test_hooks_are_not_defined_by_the_base_class(self):
|
|
63
|
+
# Deliberate: a default no-op on_tick would turn "you declared a hook you
|
|
64
|
+
# did not implement" — fixable in seconds — into a run that quietly never
|
|
65
|
+
# trades and bills for an empty equity curve.
|
|
66
|
+
for hook in ("on_tick", "on_market_open", "on_book", "on_trade", "on_settle"):
|
|
67
|
+
self.assertFalse(hasattr(Strategy, hook), hook)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == "__main__":
|
|
71
|
+
unittest.main()
|