pybinbot 2.1.0__tar.gz → 2.1.1__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.
- {pybinbot-2.1.0/pybinbot.egg-info → pybinbot-2.1.1}/PKG-INFO +2 -2
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/__init__.py +2 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/models/bot_base.py +31 -1
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/enums.py +5 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/streaming/binance/socket_manager.py +3 -3
- {pybinbot-2.1.0 → pybinbot-2.1.1/pybinbot.egg-info}/PKG-INFO +2 -2
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot.egg-info/SOURCES.txt +1 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot.egg-info/requires.txt +1 -1
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pyproject.toml +2 -2
- pybinbot-2.1.1/tests/test_conditional_entry.py +45 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/LICENSE +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/MANIFEST.in +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/README.md +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/apis/binance/base.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/apis/binance/exceptions.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/apis/binbot/base.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/apis/binbot/exceptions.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/apis/coingecko.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/apis/kucoin/base.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/apis/kucoin/exceptions.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/apis/kucoin/futures.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/apis/kucoin/market.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/apis/kucoin/orders.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/apis/kucoin/rest.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/models/__init__.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/models/autotrade_settings.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/models/bot.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/models/deal.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/models/derivatives.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/models/grid_ladder.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/models/order.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/models/routes.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/models/signals.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/models/symbol.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/py.typed +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/__init__.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/beta.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/breadth.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/cache.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/candles.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/handlers.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/heikin_ashi.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/indicators.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/logging_config.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/maths.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/price_crossings.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/telegram_delivery.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/timestamps.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/types.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/shared/utils.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/streaming/binance/async_socket_client.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/streaming/binance/socket_client.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot/streaming/kucoin/kucoin_async_client.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot.egg-info/dependency_links.txt +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/pybinbot.egg-info/top_level.txt +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/setup.cfg +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_beta.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_binbot_api.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_bot_futures_leverage.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_breadth.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_candles.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_coingecko.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_conditional_futures_orders.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_deal_model.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_derivatives_api.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_futures_place_order.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_grid_ladder.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_handlers.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_heikin_ashi.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_indicators.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_kucoin_api.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_logging_config.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_matching_engine.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_maths.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_price_crossings.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_symbol_model.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_telegram_delivery.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_timestamps.py +0 -0
- {pybinbot-2.1.0 → pybinbot-2.1.1}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pybinbot
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: Utility functions for the binbot project.
|
|
5
5
|
Author-email: Carlos Wu <carkodw@gmail.com>
|
|
6
6
|
Requires-Python: >=3.15
|
|
@@ -11,7 +11,7 @@ Requires-Dist: numpy==2.5.4
|
|
|
11
11
|
Requires-Dist: pandas==3.1.1
|
|
12
12
|
Requires-Dist: pandas-stubs==3.1.0.261029
|
|
13
13
|
Requires-Dist: requests>=2.32.5
|
|
14
|
-
Requires-Dist: kucoin-universal-sdk>=1.3.
|
|
14
|
+
Requires-Dist: kucoin-universal-sdk>=1.3.2
|
|
15
15
|
Requires-Dist: aiohttp<3.15,>=3.14.1
|
|
16
16
|
Requires-Dist: websocket-client>=1.9.0
|
|
17
17
|
Requires-Dist: python-dotenv>=1.2.1
|
|
@@ -38,6 +38,7 @@ from pybinbot.shared.enums import (
|
|
|
38
38
|
BinanceOrderModel,
|
|
39
39
|
CloseConditions,
|
|
40
40
|
DealType,
|
|
41
|
+
EntryMode,
|
|
41
42
|
ExchangeId,
|
|
42
43
|
KucoinKlineIntervals,
|
|
43
44
|
MarketDominance,
|
|
@@ -282,6 +283,7 @@ __all__ = [
|
|
|
282
283
|
# enums
|
|
283
284
|
"CloseConditions",
|
|
284
285
|
"DealType",
|
|
286
|
+
"EntryMode",
|
|
285
287
|
"BinanceOrderModel",
|
|
286
288
|
"Status",
|
|
287
289
|
"Position",
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from typing import Literal
|
|
2
2
|
from uuid import UUID
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
4
|
+
from pydantic import BaseModel, Field, ValidationInfo, field_validator, model_validator
|
|
5
5
|
|
|
6
6
|
from pybinbot.shared.enums import (
|
|
7
7
|
BinanceKlineIntervals,
|
|
8
8
|
CloseConditions,
|
|
9
|
+
EntryMode,
|
|
9
10
|
MarketType,
|
|
10
11
|
Position,
|
|
11
12
|
QuoteAssets,
|
|
@@ -46,6 +47,14 @@ class BotBase(BaseModel):
|
|
|
46
47
|
)
|
|
47
48
|
created_at: float = Field(default_factory=timestamp)
|
|
48
49
|
updated_at: float = Field(default_factory=timestamp)
|
|
50
|
+
entry_mode: EntryMode = Field(default=EntryMode.immediate, validate_default=True)
|
|
51
|
+
entry_price: float | None = Field(default=None, ge=0, allow_inf_nan=False)
|
|
52
|
+
entry_expires_at: float = Field(
|
|
53
|
+
default_factory=lambda data: data["created_at"] + 6 * 60 * 60 * 1000,
|
|
54
|
+
gt=0,
|
|
55
|
+
allow_inf_nan=False,
|
|
56
|
+
description="Absolute entry deadline in Unix milliseconds; defaults to creation + 6 hours",
|
|
57
|
+
)
|
|
49
58
|
dynamic_trailing: bool = Field(default=False)
|
|
50
59
|
dynamic_position_sizing: bool = Field(default=False)
|
|
51
60
|
logs: list = Field(default=[])
|
|
@@ -104,6 +113,27 @@ class BotBase(BaseModel):
|
|
|
104
113
|
},
|
|
105
114
|
}
|
|
106
115
|
|
|
116
|
+
@field_validator("entry_mode", mode="before")
|
|
117
|
+
@classmethod
|
|
118
|
+
def default_entry_mode(cls, value):
|
|
119
|
+
return EntryMode.immediate if value is None else value
|
|
120
|
+
|
|
121
|
+
@field_validator("entry_expires_at", mode="before")
|
|
122
|
+
@classmethod
|
|
123
|
+
def default_entry_expiry(cls, value, info: ValidationInfo):
|
|
124
|
+
return info.data["created_at"] + 6 * 60 * 60 * 1000 if value is None else value
|
|
125
|
+
|
|
126
|
+
@model_validator(mode="after")
|
|
127
|
+
def validate_conditional_entry(self):
|
|
128
|
+
if self.entry_mode == EntryMode.conditional_limit:
|
|
129
|
+
if self.entry_price is None or self.entry_price <= 0:
|
|
130
|
+
raise ValueError(
|
|
131
|
+
"Conditional limit entry requires a positive entry_price"
|
|
132
|
+
)
|
|
133
|
+
if self.market_type != MarketType.FUTURES:
|
|
134
|
+
raise ValueError("Conditional limit entry currently requires futures")
|
|
135
|
+
return self
|
|
136
|
+
|
|
107
137
|
@model_validator(mode="after")
|
|
108
138
|
def validate_dynamic_position_sizing(self):
|
|
109
139
|
if self.dynamic_position_sizing:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
-
import threading
|
|
4
3
|
import time
|
|
4
|
+
from threading import Thread
|
|
5
5
|
|
|
6
6
|
from websocket import (
|
|
7
7
|
ABNF,
|
|
@@ -11,7 +11,7 @@ from websocket import (
|
|
|
11
11
|
)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class BinanceSocketManager(
|
|
14
|
+
class BinanceSocketManager(Thread):
|
|
15
15
|
def __init__(
|
|
16
16
|
self,
|
|
17
17
|
stream_url,
|
|
@@ -23,7 +23,7 @@ class BinanceSocketManager(threading.Thread):
|
|
|
23
23
|
on_pong=None,
|
|
24
24
|
logger=None,
|
|
25
25
|
):
|
|
26
|
-
|
|
26
|
+
Thread.__init__(self)
|
|
27
27
|
if not logger:
|
|
28
28
|
logger = logging.getLogger(__name__)
|
|
29
29
|
self.logger = logger
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pybinbot
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.1
|
|
4
4
|
Summary: Utility functions for the binbot project.
|
|
5
5
|
Author-email: Carlos Wu <carkodw@gmail.com>
|
|
6
6
|
Requires-Python: >=3.15
|
|
@@ -11,7 +11,7 @@ Requires-Dist: numpy==2.5.4
|
|
|
11
11
|
Requires-Dist: pandas==3.1.1
|
|
12
12
|
Requires-Dist: pandas-stubs==3.1.0.261029
|
|
13
13
|
Requires-Dist: requests>=2.32.5
|
|
14
|
-
Requires-Dist: kucoin-universal-sdk>=1.3.
|
|
14
|
+
Requires-Dist: kucoin-universal-sdk>=1.3.2
|
|
15
15
|
Requires-Dist: aiohttp<3.15,>=3.14.1
|
|
16
16
|
Requires-Dist: websocket-client>=1.9.0
|
|
17
17
|
Requires-Dist: python-dotenv>=1.2.1
|
|
@@ -22,7 +22,7 @@ build-backend = "setuptools.build_meta"
|
|
|
22
22
|
|
|
23
23
|
[project]
|
|
24
24
|
name = "pybinbot"
|
|
25
|
-
version = "2.1.
|
|
25
|
+
version = "2.1.1"
|
|
26
26
|
description = "Utility functions for the binbot project."
|
|
27
27
|
authors = [
|
|
28
28
|
{ name="Carlos Wu", email="carkodw@gmail.com" }
|
|
@@ -35,7 +35,7 @@ dependencies = [
|
|
|
35
35
|
"pandas==3.1.1",
|
|
36
36
|
"pandas-stubs==3.1.0.261029",
|
|
37
37
|
"requests>=2.32.5",
|
|
38
|
-
"kucoin-universal-sdk>=1.3.
|
|
38
|
+
"kucoin-universal-sdk>=1.3.2",
|
|
39
39
|
"aiohttp>=3.14.1,<3.15",
|
|
40
40
|
"websocket-client>=1.9.0",
|
|
41
41
|
"python-dotenv>=1.2.1",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from pydantic import ValidationError
|
|
3
|
+
|
|
4
|
+
from pybinbot import BotBase, EntryMode, MarketType
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.parametrize(
|
|
8
|
+
"overrides",
|
|
9
|
+
[{}, {"entry_mode": None, "entry_expires_at": None, "entry_price": None}],
|
|
10
|
+
)
|
|
11
|
+
def test_omitted_or_null_entry_fields_default_to_immediate_and_six_hours(overrides):
|
|
12
|
+
bot = BotBase(pair="XBTUSDTM", created_at=1000, **overrides)
|
|
13
|
+
assert bot.entry_mode == EntryMode.immediate
|
|
14
|
+
assert bot.entry_expires_at == 21_601_000
|
|
15
|
+
assert bot.entry_price is None
|
|
16
|
+
assert bot.model_dump(mode="json")["entry_price"] is None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_conditional_entry_serializes_strings_and_preserves_deadline():
|
|
20
|
+
bot = BotBase(
|
|
21
|
+
pair="XBTUSDTM",
|
|
22
|
+
market_type=MarketType.FUTURES,
|
|
23
|
+
entry_mode="conditional_limit",
|
|
24
|
+
entry_price=100,
|
|
25
|
+
entry_expires_at=9000,
|
|
26
|
+
)
|
|
27
|
+
assert bot.model_dump(mode="json")["entry_mode"] == "conditional_limit"
|
|
28
|
+
assert bot.entry_expires_at == 9000
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
"values",
|
|
33
|
+
[
|
|
34
|
+
{"entry_mode": "unknown"},
|
|
35
|
+
{"entry_mode": "conditional_limit"},
|
|
36
|
+
{"entry_mode": "conditional_limit", "entry_price": None},
|
|
37
|
+
{"entry_mode": "conditional_limit", "entry_price": 0},
|
|
38
|
+
{"entry_mode": "conditional_limit", "entry_price": -1},
|
|
39
|
+
{"entry_mode": "conditional_limit", "entry_price": float("inf")},
|
|
40
|
+
{"entry_expires_at": float("nan")},
|
|
41
|
+
],
|
|
42
|
+
)
|
|
43
|
+
def test_invalid_entry_configuration_is_rejected(values):
|
|
44
|
+
with pytest.raises(ValidationError):
|
|
45
|
+
BotBase(pair="XBTUSDTM", market_type=MarketType.FUTURES, **values)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|