pybinbot 0.4.1__py3-none-any.whl
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/__init__.py +160 -0
- pybinbot/models/__init__.py +0 -0
- pybinbot/models/bot_base.py +99 -0
- pybinbot/models/deal.py +78 -0
- pybinbot/models/order.py +112 -0
- pybinbot/models/routes.py +6 -0
- pybinbot/models/signals.py +46 -0
- pybinbot/py.typed +0 -0
- pybinbot/shared/__init__.py +0 -0
- pybinbot/shared/cache.py +32 -0
- pybinbot/shared/enums.py +283 -0
- pybinbot/shared/handlers.py +89 -0
- pybinbot/shared/heikin_ashi.py +198 -0
- pybinbot/shared/indicators.py +271 -0
- pybinbot/shared/logging_config.py +40 -0
- pybinbot/shared/maths.py +123 -0
- pybinbot/shared/timestamps.py +98 -0
- pybinbot/shared/types.py +11 -0
- pybinbot-0.4.1.dist-info/METADATA +77 -0
- pybinbot-0.4.1.dist-info/RECORD +23 -0
- pybinbot-0.4.1.dist-info/WHEEL +5 -0
- pybinbot-0.4.1.dist-info/licenses/LICENSE +18 -0
- pybinbot-0.4.1.dist-info/top_level.txt +1 -0
pybinbot/__init__.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Public API for the ``pybinbot`` distribution.
|
|
2
|
+
|
|
3
|
+
This package exposes a flat, convenient API for common types, enums and
|
|
4
|
+
models, while still allowing access to the structured subpackages via
|
|
5
|
+
``pybinbot.shared`` and ``pybinbot.models``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pybinbot.shared.maths import (
|
|
9
|
+
format_ts,
|
|
10
|
+
interval_to_millisecs,
|
|
11
|
+
round_numbers,
|
|
12
|
+
round_numbers_ceiling,
|
|
13
|
+
round_numbers_floor,
|
|
14
|
+
supress_notation,
|
|
15
|
+
supress_trailling,
|
|
16
|
+
zero_remainder,
|
|
17
|
+
)
|
|
18
|
+
from pybinbot.shared.timestamps import (
|
|
19
|
+
ms_to_sec,
|
|
20
|
+
round_timestamp,
|
|
21
|
+
sec_to_ms,
|
|
22
|
+
timestamp,
|
|
23
|
+
timestamp_to_datetime,
|
|
24
|
+
ts_to_day,
|
|
25
|
+
ts_to_humandate,
|
|
26
|
+
)
|
|
27
|
+
from pybinbot.shared.enums import (
|
|
28
|
+
AutotradeSettingsDocument,
|
|
29
|
+
BinanceKlineIntervals,
|
|
30
|
+
BinanceOrderModel,
|
|
31
|
+
CloseConditions,
|
|
32
|
+
DealType,
|
|
33
|
+
ExchangeId,
|
|
34
|
+
KafkaTopics,
|
|
35
|
+
KucoinKlineIntervals,
|
|
36
|
+
MarketDominance,
|
|
37
|
+
OrderSide,
|
|
38
|
+
OrderStatus,
|
|
39
|
+
OrderType,
|
|
40
|
+
QuoteAssets,
|
|
41
|
+
Status,
|
|
42
|
+
Strategy,
|
|
43
|
+
TimeInForce,
|
|
44
|
+
TrendEnum,
|
|
45
|
+
UserRoles,
|
|
46
|
+
)
|
|
47
|
+
from pybinbot.shared.indicators import Indicators
|
|
48
|
+
from pybinbot.shared.heikin_ashi import HeikinAshi
|
|
49
|
+
from pybinbot.shared.logging_config import configure_logging
|
|
50
|
+
from pybinbot.shared.types import Amount
|
|
51
|
+
from pybinbot.shared.cache import cache
|
|
52
|
+
from pybinbot.shared.handlers import handle_binance_errors, aio_response_handler
|
|
53
|
+
from pybinbot.models.bot_base import BotBase
|
|
54
|
+
from pybinbot.models.deal import DealBase
|
|
55
|
+
from pybinbot.models.order import OrderBase
|
|
56
|
+
from pybinbot.models.signals import HABollinguerSpread, SignalsConsumer
|
|
57
|
+
from pybinbot.models.routes import StandardResponse
|
|
58
|
+
from pybinbot.apis.binance.base import BinanceApi
|
|
59
|
+
from pybinbot.apis.binbot.base import BinbotApi
|
|
60
|
+
from pybinbot.apis.binbot.exceptions import (
|
|
61
|
+
BinbotErrors,
|
|
62
|
+
QuantityTooLow,
|
|
63
|
+
IsolateBalanceError,
|
|
64
|
+
DealCreationError,
|
|
65
|
+
MarginShortError,
|
|
66
|
+
MarginLoanNotFound,
|
|
67
|
+
DeleteOrderError,
|
|
68
|
+
LowBalanceCleanupError,
|
|
69
|
+
SaveBotError,
|
|
70
|
+
InsufficientBalance,
|
|
71
|
+
)
|
|
72
|
+
from pybinbot.apis.kucoin.base import KucoinApi
|
|
73
|
+
from pybinbot.apis.kucoin.exceptions import KucoinErrors
|
|
74
|
+
from pybinbot.apis.binance.exceptions import (
|
|
75
|
+
BinanceErrors,
|
|
76
|
+
InvalidSymbol,
|
|
77
|
+
NotEnoughFunds,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
from . import models, shared
|
|
82
|
+
|
|
83
|
+
__all__ = [
|
|
84
|
+
# subpackages
|
|
85
|
+
"shared",
|
|
86
|
+
"models",
|
|
87
|
+
# models
|
|
88
|
+
"BotBase",
|
|
89
|
+
"OrderBase",
|
|
90
|
+
"DealBase",
|
|
91
|
+
"StandardResponse",
|
|
92
|
+
"HABollinguerSpread",
|
|
93
|
+
"SignalsConsumer",
|
|
94
|
+
# misc
|
|
95
|
+
"Amount",
|
|
96
|
+
"configure_logging",
|
|
97
|
+
"cache",
|
|
98
|
+
"handle_binance_errors",
|
|
99
|
+
"aio_response_handler",
|
|
100
|
+
# maths helpers
|
|
101
|
+
"supress_trailling",
|
|
102
|
+
"round_numbers",
|
|
103
|
+
"round_numbers_ceiling",
|
|
104
|
+
"round_numbers_floor",
|
|
105
|
+
"supress_notation",
|
|
106
|
+
"interval_to_millisecs",
|
|
107
|
+
"format_ts",
|
|
108
|
+
"zero_remainder",
|
|
109
|
+
# timestamp helpers
|
|
110
|
+
"timestamp",
|
|
111
|
+
"round_timestamp",
|
|
112
|
+
"ts_to_day",
|
|
113
|
+
"ms_to_sec",
|
|
114
|
+
"sec_to_ms",
|
|
115
|
+
"ts_to_humandate",
|
|
116
|
+
"timestamp_to_datetime",
|
|
117
|
+
# dataframes
|
|
118
|
+
"Indicators",
|
|
119
|
+
"HeikinAshi",
|
|
120
|
+
# enums
|
|
121
|
+
"CloseConditions",
|
|
122
|
+
"KafkaTopics",
|
|
123
|
+
"DealType",
|
|
124
|
+
"BinanceOrderModel",
|
|
125
|
+
"Status",
|
|
126
|
+
"Strategy",
|
|
127
|
+
"OrderType",
|
|
128
|
+
"TimeInForce",
|
|
129
|
+
"OrderSide",
|
|
130
|
+
"OrderStatus",
|
|
131
|
+
"TrendEnum",
|
|
132
|
+
"BinanceKlineIntervals",
|
|
133
|
+
"KucoinKlineIntervals",
|
|
134
|
+
"AutotradeSettingsDocument",
|
|
135
|
+
"UserRoles",
|
|
136
|
+
"QuoteAssets",
|
|
137
|
+
"ExchangeId",
|
|
138
|
+
"HABollinguerSpread",
|
|
139
|
+
"SignalsConsumer",
|
|
140
|
+
"MarketDominance",
|
|
141
|
+
# exchange apis
|
|
142
|
+
"BinbotApi",
|
|
143
|
+
"BinanceApi",
|
|
144
|
+
"KucoinApi",
|
|
145
|
+
"KucoinErrors",
|
|
146
|
+
# exceptions
|
|
147
|
+
"BinanceErrors",
|
|
148
|
+
"InvalidSymbol",
|
|
149
|
+
"NotEnoughFunds",
|
|
150
|
+
"BinbotErrors",
|
|
151
|
+
"QuantityTooLow",
|
|
152
|
+
"IsolateBalanceError",
|
|
153
|
+
"MarginShortError",
|
|
154
|
+
"MarginLoanNotFound",
|
|
155
|
+
"DeleteOrderError",
|
|
156
|
+
"LowBalanceCleanupError",
|
|
157
|
+
"DealCreationError",
|
|
158
|
+
"SaveBotError",
|
|
159
|
+
"InsufficientBalance",
|
|
160
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field, field_validator
|
|
2
|
+
|
|
3
|
+
from pybinbot.shared.enums import (
|
|
4
|
+
BinanceKlineIntervals,
|
|
5
|
+
CloseConditions,
|
|
6
|
+
QuoteAssets,
|
|
7
|
+
Status,
|
|
8
|
+
Strategy,
|
|
9
|
+
)
|
|
10
|
+
from pybinbot.shared.timestamps import timestamp, ts_to_humandate
|
|
11
|
+
from pybinbot.shared.types import Amount
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BotBase(BaseModel):
|
|
15
|
+
pair: str
|
|
16
|
+
fiat: str = Field(default="USDC")
|
|
17
|
+
quote_asset: QuoteAssets = Field(default=QuoteAssets.USDC)
|
|
18
|
+
fiat_order_size: Amount = Field(
|
|
19
|
+
default=0, ge=0, description="Min Binance 0.0001 BNB approx 15USD"
|
|
20
|
+
)
|
|
21
|
+
candlestick_interval: BinanceKlineIntervals = Field(
|
|
22
|
+
default=BinanceKlineIntervals.fifteen_minutes
|
|
23
|
+
)
|
|
24
|
+
close_condition: CloseConditions = Field(default=CloseConditions.dynamic_trailling)
|
|
25
|
+
cooldown: int = Field(
|
|
26
|
+
default=0,
|
|
27
|
+
ge=0,
|
|
28
|
+
description="cooldown period in minutes before opening next bot with same pair",
|
|
29
|
+
)
|
|
30
|
+
created_at: float = Field(default_factory=timestamp)
|
|
31
|
+
updated_at: float = Field(default_factory=timestamp)
|
|
32
|
+
dynamic_trailling: bool = Field(default=False)
|
|
33
|
+
logs: list = Field(default=[])
|
|
34
|
+
mode: str = Field(default="manual")
|
|
35
|
+
name: str = Field(
|
|
36
|
+
default="terminal",
|
|
37
|
+
description="Algorithm name or 'terminal' if executed from React app",
|
|
38
|
+
)
|
|
39
|
+
status: Status = Field(default=Status.inactive)
|
|
40
|
+
stop_loss: Amount = Field(
|
|
41
|
+
default=0, ge=-1, le=101, description="If stop_loss > 0, allow for reversal"
|
|
42
|
+
)
|
|
43
|
+
margin_short_reversal: bool = Field(
|
|
44
|
+
default=False,
|
|
45
|
+
description="Autoswitch from long to short or short to long strategy",
|
|
46
|
+
)
|
|
47
|
+
take_profit: Amount = Field(default=0, ge=-1, le=101)
|
|
48
|
+
trailling: bool = Field(default=False)
|
|
49
|
+
trailling_deviation: Amount = Field(
|
|
50
|
+
default=0,
|
|
51
|
+
ge=-1,
|
|
52
|
+
le=101,
|
|
53
|
+
description="Trailling activation (first take profit hit)",
|
|
54
|
+
)
|
|
55
|
+
trailling_profit: Amount = Field(default=0, ge=-1, le=101)
|
|
56
|
+
strategy: Strategy = Field(default=Strategy.long)
|
|
57
|
+
model_config = {
|
|
58
|
+
"from_attributes": True,
|
|
59
|
+
"use_enum_values": True,
|
|
60
|
+
"json_schema_extra": {
|
|
61
|
+
"description": "Most fields are optional. Deal and orders fields are generated internally and filled by Exchange",
|
|
62
|
+
"examples": [
|
|
63
|
+
{
|
|
64
|
+
"pair": "BNBUSDT",
|
|
65
|
+
"fiat": "USDC",
|
|
66
|
+
"quote_asset": "USDC",
|
|
67
|
+
"fiat_order_size": 15,
|
|
68
|
+
"candlestick_interval": "15m",
|
|
69
|
+
"close_condition": "dynamic_trailling",
|
|
70
|
+
"cooldown": 0,
|
|
71
|
+
"created_at": 1702999999.0,
|
|
72
|
+
"updated_at": 1702999999.0,
|
|
73
|
+
"dynamic_trailling": False,
|
|
74
|
+
"logs": [],
|
|
75
|
+
"mode": "manual",
|
|
76
|
+
"name": "Default bot",
|
|
77
|
+
"status": "inactive",
|
|
78
|
+
"stop_loss": 0,
|
|
79
|
+
"take_profit": 2.3,
|
|
80
|
+
"trailling": True,
|
|
81
|
+
"trailling_deviation": 0.63,
|
|
82
|
+
"trailling_profit": 2.3,
|
|
83
|
+
"margin_short_reversal": False,
|
|
84
|
+
"strategy": "long",
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@field_validator("pair")
|
|
91
|
+
@classmethod
|
|
92
|
+
def check_pair_not_empty(cls, v):
|
|
93
|
+
assert v != "", "Pair field must be filled."
|
|
94
|
+
return v
|
|
95
|
+
|
|
96
|
+
def add_log(self, message: str) -> str:
|
|
97
|
+
timestamped_message = f"[{ts_to_humandate(timestamp())}] {message}"
|
|
98
|
+
self.logs.append(timestamped_message)
|
|
99
|
+
return self.logs[-1]
|
pybinbot/models/deal.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field, field_validator
|
|
2
|
+
|
|
3
|
+
from pybinbot.shared.types import Amount
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DealBase(BaseModel):
|
|
7
|
+
"""Operational deal data model with numeric fields."""
|
|
8
|
+
|
|
9
|
+
base_order_size: Amount = Field(default=0, gt=-1)
|
|
10
|
+
current_price: Amount = Field(default=0)
|
|
11
|
+
take_profit_price: Amount = Field(default=0)
|
|
12
|
+
trailling_stop_loss_price: Amount = Field(
|
|
13
|
+
default=0,
|
|
14
|
+
description=(
|
|
15
|
+
"take_profit but for trailling, to avoid confusion, "
|
|
16
|
+
"trailling_profit_price always be > trailling_stop_loss_price"
|
|
17
|
+
),
|
|
18
|
+
)
|
|
19
|
+
trailling_profit_price: Amount = Field(default=0)
|
|
20
|
+
stop_loss_price: Amount = Field(default=0)
|
|
21
|
+
total_interests: float = Field(default=0, gt=-1)
|
|
22
|
+
total_commissions: float = Field(default=0, gt=-1)
|
|
23
|
+
margin_loan_id: int = Field(
|
|
24
|
+
default=0,
|
|
25
|
+
ge=0,
|
|
26
|
+
description=(
|
|
27
|
+
"Txid from Binance. This is used to check if there is a loan, "
|
|
28
|
+
"0 means no loan"
|
|
29
|
+
),
|
|
30
|
+
)
|
|
31
|
+
margin_repay_id: int = Field(
|
|
32
|
+
default=0, ge=0, description="= 0, it has not been repaid"
|
|
33
|
+
)
|
|
34
|
+
opening_price: Amount = Field(
|
|
35
|
+
default=0,
|
|
36
|
+
description=(
|
|
37
|
+
"replaces previous buy_price or short_sell_price/margin_short_sell_price"
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
opening_qty: Amount = Field(
|
|
41
|
+
default=0,
|
|
42
|
+
description=(
|
|
43
|
+
"replaces previous buy_total_qty or short_sell_qty/margin_short_sell_qty"
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
opening_timestamp: int = Field(default=0)
|
|
47
|
+
closing_price: Amount = Field(
|
|
48
|
+
default=0,
|
|
49
|
+
description=(
|
|
50
|
+
"replaces previous sell_price or short_sell_price/margin_short_sell_price"
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
closing_qty: Amount = Field(
|
|
54
|
+
default=0,
|
|
55
|
+
description=(
|
|
56
|
+
"replaces previous sell_qty or short_sell_qty/margin_short_sell_qty"
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
closing_timestamp: int = Field(
|
|
60
|
+
default=0,
|
|
61
|
+
description=("replaces previous buy_timestamp or margin/short_sell timestamps"),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@field_validator("margin_loan_id", mode="before")
|
|
65
|
+
@classmethod
|
|
66
|
+
def validate_margin_loan_id(cls, value):
|
|
67
|
+
if isinstance(value, float):
|
|
68
|
+
return int(value)
|
|
69
|
+
else:
|
|
70
|
+
return value
|
|
71
|
+
|
|
72
|
+
@field_validator("margin_loan_id", mode="after")
|
|
73
|
+
@classmethod
|
|
74
|
+
def cast_float(cls, value):
|
|
75
|
+
if isinstance(value, float):
|
|
76
|
+
return int(value)
|
|
77
|
+
else:
|
|
78
|
+
return value
|
pybinbot/models/order.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
from pybinbot.shared.enums import DealType, OrderStatus
|
|
4
|
+
from pybinbot.shared.types import Amount
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OrderBase(BaseModel):
|
|
8
|
+
order_type: str = Field(
|
|
9
|
+
description=(
|
|
10
|
+
"Because every exchange has different naming, we should keep it as a "
|
|
11
|
+
"str rather than OrderType enum"
|
|
12
|
+
)
|
|
13
|
+
)
|
|
14
|
+
time_in_force: str
|
|
15
|
+
timestamp: int = Field(default=0)
|
|
16
|
+
order_id: int | str = Field(
|
|
17
|
+
description=(
|
|
18
|
+
"Because every exchange has id type, we should keep it as loose as "
|
|
19
|
+
"possible. Int is for backwards compatibility"
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
order_side: str = Field(
|
|
23
|
+
description=(
|
|
24
|
+
"Because every exchange has different naming, we should keep it as a "
|
|
25
|
+
"str rather than OrderType enum"
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
pair: str
|
|
29
|
+
qty: float
|
|
30
|
+
status: OrderStatus
|
|
31
|
+
price: float
|
|
32
|
+
deal_type: DealType
|
|
33
|
+
model_config = {
|
|
34
|
+
"from_attributes": True,
|
|
35
|
+
"use_enum_values": True,
|
|
36
|
+
"json_schema_extra": {
|
|
37
|
+
"description": (
|
|
38
|
+
"Most fields are optional. Deal field is generated internally, "
|
|
39
|
+
"orders are filled up by Exchange"
|
|
40
|
+
),
|
|
41
|
+
"examples": [
|
|
42
|
+
{
|
|
43
|
+
"order_type": "LIMIT",
|
|
44
|
+
"time_in_force": "GTC",
|
|
45
|
+
"timestamp": 0,
|
|
46
|
+
"order_id": 0,
|
|
47
|
+
"order_side": "BUY",
|
|
48
|
+
"pair": "",
|
|
49
|
+
"qty": 0,
|
|
50
|
+
"status": "",
|
|
51
|
+
"price": 0,
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DealModel(BaseModel):
|
|
59
|
+
base_order_size: Amount = Field(default=0, gt=-1)
|
|
60
|
+
current_price: Amount = Field(default=0)
|
|
61
|
+
take_profit_price: Amount = Field(default=0)
|
|
62
|
+
trailling_stop_loss_price: Amount = Field(
|
|
63
|
+
default=0,
|
|
64
|
+
description=(
|
|
65
|
+
"take_profit but for trailling, to avoid confusion, "
|
|
66
|
+
"trailling_profit_price always be > trailling_stop_loss_price"
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
trailling_profit_price: Amount = Field(default=0)
|
|
70
|
+
stop_loss_price: Amount = Field(default=0)
|
|
71
|
+
total_interests: float = Field(default=0, gt=-1)
|
|
72
|
+
total_commissions: float = Field(default=0, gt=-1)
|
|
73
|
+
margin_loan_id: int = Field(
|
|
74
|
+
default=0,
|
|
75
|
+
ge=0,
|
|
76
|
+
description=(
|
|
77
|
+
"Txid from Binance. This is used to check if there is a loan, "
|
|
78
|
+
"0 means no loan"
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
margin_repay_id: int = Field(
|
|
82
|
+
default=0, ge=0, description="= 0, it has not been repaid"
|
|
83
|
+
)
|
|
84
|
+
opening_price: Amount = Field(
|
|
85
|
+
default=0,
|
|
86
|
+
description=(
|
|
87
|
+
"replaces previous buy_price or short_sell_price/margin_short_sell_price"
|
|
88
|
+
),
|
|
89
|
+
)
|
|
90
|
+
opening_qty: Amount = Field(
|
|
91
|
+
default=0,
|
|
92
|
+
description=(
|
|
93
|
+
"replaces previous buy_total_qty or short_sell_qty/margin_short_sell_qty"
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
opening_timestamp: int = Field(default=0)
|
|
97
|
+
closing_price: Amount = Field(
|
|
98
|
+
default=0,
|
|
99
|
+
description=(
|
|
100
|
+
"replaces previous sell_price or short_sell_price/margin_short_sell_price"
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
closing_qty: Amount = Field(
|
|
104
|
+
default=0,
|
|
105
|
+
description=(
|
|
106
|
+
"replaces previous sell_qty or short_sell_qty/margin_short_sell_qty"
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
closing_timestamp: int = Field(
|
|
110
|
+
default=0,
|
|
111
|
+
description=("replaces previous buy_timestamp or margin/short_sell timestamps"),
|
|
112
|
+
)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class HABollinguerSpread(BaseModel):
|
|
8
|
+
"""Pydantic model for the Bollinguer spread."""
|
|
9
|
+
|
|
10
|
+
bb_high: float
|
|
11
|
+
bb_mid: float
|
|
12
|
+
bb_low: float
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SignalsConsumer(BaseModel):
|
|
16
|
+
"""Pydantic model for the signals consumer."""
|
|
17
|
+
|
|
18
|
+
type: str = Field(default="signal")
|
|
19
|
+
date: str = Field(
|
|
20
|
+
default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
21
|
+
)
|
|
22
|
+
spread: Optional[float] = Field(default=0)
|
|
23
|
+
current_price: Optional[float] = Field(default=0)
|
|
24
|
+
msg: str
|
|
25
|
+
symbol: str
|
|
26
|
+
algo: str
|
|
27
|
+
bot_strategy: str = Field(default="long")
|
|
28
|
+
bb_spreads: Optional[HABollinguerSpread]
|
|
29
|
+
autotrade: bool = Field(default=True, description="If it is in testing mode, False")
|
|
30
|
+
|
|
31
|
+
model_config = ConfigDict(
|
|
32
|
+
extra="allow",
|
|
33
|
+
use_enum_values=True,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@field_validator("spread", "current_price")
|
|
37
|
+
@classmethod
|
|
38
|
+
def name_must_contain_space(cls, v):
|
|
39
|
+
if v is None:
|
|
40
|
+
return 0
|
|
41
|
+
elif isinstance(v, str):
|
|
42
|
+
return float(v)
|
|
43
|
+
elif isinstance(v, float):
|
|
44
|
+
return v
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError("must be a float or 0")
|
pybinbot/py.typed
ADDED
|
File without changes
|
|
File without changes
|
pybinbot/shared/cache.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from typing import Any, Callable, Dict, Tuple
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def cache(
|
|
7
|
+
ttl_seconds: int = 3600,
|
|
8
|
+
) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
|
|
9
|
+
"""Simple in-process TTL cache decorator (per process).
|
|
10
|
+
Caches function results by args/kwargs for ttl_seconds.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
14
|
+
store: Dict[
|
|
15
|
+
Tuple[Tuple[Any, ...], Tuple[Tuple[str, Any], ...]], Tuple[float, Any]
|
|
16
|
+
] = {}
|
|
17
|
+
|
|
18
|
+
@wraps(func)
|
|
19
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
20
|
+
key = (args, tuple(sorted(kwargs.items())))
|
|
21
|
+
now = time.monotonic()
|
|
22
|
+
if key in store:
|
|
23
|
+
expiry, value = store[key]
|
|
24
|
+
if now < expiry:
|
|
25
|
+
return value
|
|
26
|
+
value = func(*args, **kwargs)
|
|
27
|
+
store[key] = (now + max(0, int(ttl_seconds)), value)
|
|
28
|
+
return value
|
|
29
|
+
|
|
30
|
+
return wrapper
|
|
31
|
+
|
|
32
|
+
return decorator
|