crypticorn 2.16.0__py3-none-any.whl → 2.17.0__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.
- crypticorn/__init__.py +2 -2
- crypticorn/auth/client/api/admin_api.py +397 -13
- crypticorn/auth/client/api/auth_api.py +3610 -341
- crypticorn/auth/client/api/service_api.py +249 -7
- crypticorn/auth/client/api/user_api.py +2295 -179
- crypticorn/auth/client/api/wallet_api.py +1468 -81
- crypticorn/auth/client/configuration.py +2 -2
- crypticorn/auth/client/models/create_api_key_request.py +2 -1
- crypticorn/auth/client/models/get_api_keys200_response_inner.py +2 -1
- crypticorn/auth/client/rest.py +23 -4
- crypticorn/auth/main.py +8 -5
- crypticorn/cli/init.py +1 -1
- crypticorn/cli/templates/.env.docker.temp +3 -0
- crypticorn/cli/templates/.env.example.temp +4 -0
- crypticorn/cli/templates/Dockerfile +5 -2
- crypticorn/client.py +226 -59
- crypticorn/common/__init__.py +1 -0
- crypticorn/common/auth.py +45 -14
- crypticorn/common/decorators.py +1 -2
- crypticorn/common/enums.py +0 -2
- crypticorn/common/errors.py +10 -0
- crypticorn/common/metrics.py +30 -0
- crypticorn/common/middleware.py +94 -1
- crypticorn/common/pagination.py +252 -20
- crypticorn/common/router/admin_router.py +2 -2
- crypticorn/common/router/status_router.py +40 -2
- crypticorn/common/scopes.py +2 -2
- crypticorn/common/warnings.py +7 -0
- crypticorn/dex/__init__.py +6 -0
- crypticorn/dex/client/__init__.py +49 -0
- crypticorn/dex/client/api/__init__.py +6 -0
- crypticorn/dex/client/api/admin_api.py +2986 -0
- crypticorn/dex/client/api/signals_api.py +1798 -0
- crypticorn/dex/client/api/status_api.py +892 -0
- crypticorn/dex/client/api_client.py +758 -0
- crypticorn/dex/client/api_response.py +20 -0
- crypticorn/dex/client/configuration.py +620 -0
- crypticorn/dex/client/exceptions.py +220 -0
- crypticorn/dex/client/models/__init__.py +30 -0
- crypticorn/dex/client/models/api_error_identifier.py +121 -0
- crypticorn/dex/client/models/api_error_level.py +37 -0
- crypticorn/dex/client/models/api_error_type.py +37 -0
- crypticorn/dex/client/models/exception_detail.py +117 -0
- crypticorn/dex/client/models/log_level.py +38 -0
- crypticorn/dex/client/models/paginated_response_signal_with_token.py +134 -0
- crypticorn/dex/client/models/risk.py +86 -0
- crypticorn/dex/client/models/signal_overview_stats.py +158 -0
- crypticorn/dex/client/models/signal_volume.py +84 -0
- crypticorn/dex/client/models/signal_with_token.py +163 -0
- crypticorn/dex/client/models/token_data.py +127 -0
- crypticorn/dex/client/models/token_detail.py +116 -0
- crypticorn/dex/client/py.typed +0 -0
- crypticorn/dex/client/rest.py +217 -0
- crypticorn/dex/main.py +1 -0
- crypticorn/hive/client/api/admin_api.py +1173 -47
- crypticorn/hive/client/api/data_api.py +499 -17
- crypticorn/hive/client/api/models_api.py +1595 -87
- crypticorn/hive/client/api/status_api.py +397 -16
- crypticorn/hive/client/api_client.py +0 -5
- crypticorn/hive/client/models/api_error_identifier.py +1 -1
- crypticorn/hive/client/models/coin_info.py +1 -1
- crypticorn/hive/client/models/exception_detail.py +1 -1
- crypticorn/hive/client/models/target_info.py +1 -1
- crypticorn/hive/client/rest.py +23 -4
- crypticorn/hive/main.py +99 -25
- crypticorn/hive/utils.py +2 -2
- crypticorn/klines/client/api/admin_api.py +1173 -47
- crypticorn/klines/client/api/change_in_timeframe_api.py +269 -11
- crypticorn/klines/client/api/funding_rates_api.py +315 -11
- crypticorn/klines/client/api/ohlcv_data_api.py +390 -11
- crypticorn/klines/client/api/status_api.py +397 -16
- crypticorn/klines/client/api/symbols_api.py +216 -11
- crypticorn/klines/client/api/udf_api.py +1268 -51
- crypticorn/klines/client/api_client.py +0 -5
- crypticorn/klines/client/models/api_error_identifier.py +3 -1
- crypticorn/klines/client/models/exception_detail.py +1 -1
- crypticorn/klines/client/models/ohlcv.py +1 -1
- crypticorn/klines/client/models/symbol_group.py +1 -1
- crypticorn/klines/client/models/udf_config.py +1 -1
- crypticorn/klines/client/rest.py +23 -4
- crypticorn/klines/main.py +89 -12
- crypticorn/metrics/client/api/admin_api.py +1173 -47
- crypticorn/metrics/client/api/exchanges_api.py +1370 -145
- crypticorn/metrics/client/api/indicators_api.py +622 -17
- crypticorn/metrics/client/api/logs_api.py +296 -11
- crypticorn/metrics/client/api/marketcap_api.py +1207 -67
- crypticorn/metrics/client/api/markets_api.py +343 -11
- crypticorn/metrics/client/api/quote_currencies_api.py +228 -11
- crypticorn/metrics/client/api/status_api.py +397 -16
- crypticorn/metrics/client/api/tokens_api.py +382 -15
- crypticorn/metrics/client/api_client.py +0 -5
- crypticorn/metrics/client/configuration.py +4 -2
- crypticorn/metrics/client/models/exception_detail.py +1 -1
- crypticorn/metrics/client/models/exchange_mapping.py +1 -1
- crypticorn/metrics/client/models/marketcap_ranking.py +1 -1
- crypticorn/metrics/client/models/marketcap_symbol_ranking.py +1 -1
- crypticorn/metrics/client/models/ohlcv.py +1 -1
- crypticorn/metrics/client/rest.py +23 -4
- crypticorn/metrics/main.py +113 -19
- crypticorn/pay/client/api/admin_api.py +1585 -57
- crypticorn/pay/client/api/now_payments_api.py +961 -39
- crypticorn/pay/client/api/payments_api.py +562 -17
- crypticorn/pay/client/api/products_api.py +880 -30
- crypticorn/pay/client/api/status_api.py +397 -16
- crypticorn/pay/client/api_client.py +0 -5
- crypticorn/pay/client/configuration.py +2 -2
- crypticorn/pay/client/models/api_error_identifier.py +7 -7
- crypticorn/pay/client/models/exception_detail.py +1 -1
- crypticorn/pay/client/models/now_create_invoice_req.py +1 -1
- crypticorn/pay/client/models/now_create_invoice_res.py +1 -1
- crypticorn/pay/client/models/product.py +1 -1
- crypticorn/pay/client/models/product_create.py +1 -1
- crypticorn/pay/client/models/product_update.py +1 -1
- crypticorn/pay/client/models/scope.py +1 -0
- crypticorn/pay/client/rest.py +23 -4
- crypticorn/pay/main.py +10 -6
- crypticorn/trade/client/__init__.py +11 -1
- crypticorn/trade/client/api/__init__.py +0 -1
- crypticorn/trade/client/api/admin_api.py +1184 -55
- crypticorn/trade/client/api/api_keys_api.py +1678 -162
- crypticorn/trade/client/api/bots_api.py +7563 -187
- crypticorn/trade/client/api/exchanges_api.py +565 -19
- crypticorn/trade/client/api/notifications_api.py +1290 -116
- crypticorn/trade/client/api/orders_api.py +393 -55
- crypticorn/trade/client/api/status_api.py +397 -13
- crypticorn/trade/client/api/strategies_api.py +1133 -77
- crypticorn/trade/client/api/trading_actions_api.py +786 -65
- crypticorn/trade/client/models/__init__.py +11 -0
- crypticorn/trade/client/models/actions_count.py +88 -0
- crypticorn/trade/client/models/api_error_identifier.py +1 -0
- crypticorn/trade/client/models/bot.py +7 -18
- crypticorn/trade/client/models/bot_create.py +17 -1
- crypticorn/trade/client/models/bot_update.py +17 -1
- crypticorn/trade/client/models/exchange.py +6 -1
- crypticorn/trade/client/models/exchange_key.py +1 -1
- crypticorn/trade/client/models/exchange_key_balance.py +111 -0
- crypticorn/trade/client/models/exchange_key_create.py +17 -1
- crypticorn/trade/client/models/exchange_key_update.py +17 -1
- crypticorn/trade/client/models/execution_ids.py +1 -1
- crypticorn/trade/client/models/futures_balance.py +27 -25
- crypticorn/trade/client/models/notification.py +17 -1
- crypticorn/trade/client/models/notification_create.py +18 -2
- crypticorn/trade/client/models/notification_update.py +17 -1
- crypticorn/trade/client/models/orders_count.py +88 -0
- crypticorn/trade/client/models/paginated_response_futures_trading_action.py +134 -0
- crypticorn/trade/client/models/paginated_response_order.py +134 -0
- crypticorn/trade/client/models/pn_l.py +95 -0
- crypticorn/trade/client/models/post_futures_action.py +1 -1
- crypticorn/trade/client/models/spot_balance.py +109 -0
- crypticorn/trade/client/models/strategy.py +22 -4
- crypticorn/trade/client/models/strategy_create.py +23 -5
- crypticorn/trade/client/models/strategy_exchange_info.py +16 -4
- crypticorn/trade/client/models/strategy_update.py +19 -3
- crypticorn/trade/client/models/tpsl.py +4 -19
- crypticorn/trade/client/models/tpsl_create.py +6 -19
- crypticorn/trade/client/rest.py +23 -4
- crypticorn/trade/main.py +15 -12
- {crypticorn-2.16.0.dist-info → crypticorn-2.17.0.dist-info}/METADATA +65 -20
- {crypticorn-2.16.0.dist-info → crypticorn-2.17.0.dist-info}/RECORD +163 -128
- crypticorn/trade/client/api/futures_trading_panel_api.py +0 -1285
- {crypticorn-2.16.0.dist-info → crypticorn-2.17.0.dist-info}/WHEEL +0 -0
- {crypticorn-2.16.0.dist-info → crypticorn-2.17.0.dist-info}/entry_points.txt +0 -0
- {crypticorn-2.16.0.dist-info → crypticorn-2.17.0.dist-info}/licenses/LICENSE +0 -0
- {crypticorn-2.16.0.dist-info → crypticorn-2.17.0.dist-info}/top_level.txt +0 -0
@@ -50,15 +50,18 @@ class Strategy(BaseModel):
|
|
50
50
|
description="Whether the strategy is enabled. If false, no bots will be created or updated for this strategy, open trades will be rejected. Existing bots will be marked as stopping."
|
51
51
|
)
|
52
52
|
performance_fee: Union[
|
53
|
-
Annotated[float, Field(le=1.0, strict=True)],
|
54
|
-
Annotated[int, Field(le=1, strict=True)],
|
53
|
+
Annotated[float, Field(le=1.0, strict=True, ge=0.0)],
|
54
|
+
Annotated[int, Field(le=1, strict=True, ge=0)],
|
55
55
|
] = Field(description="Performance fee for the strategy")
|
56
56
|
identifier: StrictStr = Field(
|
57
57
|
description="Unique human readable identifier for the strategy e.g. 'daily_trend_momentum'"
|
58
58
|
)
|
59
59
|
margin_mode: Optional[MarginMode] = None
|
60
|
-
leverage:
|
60
|
+
leverage: Annotated[int, Field(strict=True, ge=1)] = Field(
|
61
|
+
description="Leverage for the strategy"
|
62
|
+
)
|
61
63
|
market_type: MarketType = Field(description="Market of operation of the strategy")
|
64
|
+
additional_properties: Dict[str, Any] = {}
|
62
65
|
__properties: ClassVar[List[str]] = [
|
63
66
|
"created_at",
|
64
67
|
"updated_at",
|
@@ -103,8 +106,13 @@ class Strategy(BaseModel):
|
|
103
106
|
* `None` is only added to the output dict for nullable fields that
|
104
107
|
were set at model initialization. Other fields with value `None`
|
105
108
|
are ignored.
|
109
|
+
* Fields in `self.additional_properties` are added to the output dict.
|
106
110
|
"""
|
107
|
-
excluded_fields: Set[str] = set(
|
111
|
+
excluded_fields: Set[str] = set(
|
112
|
+
[
|
113
|
+
"additional_properties",
|
114
|
+
]
|
115
|
+
)
|
108
116
|
|
109
117
|
_dict = self.model_dump(
|
110
118
|
by_alias=True,
|
@@ -118,6 +126,11 @@ class Strategy(BaseModel):
|
|
118
126
|
if _item_exchanges:
|
119
127
|
_items.append(_item_exchanges.to_dict())
|
120
128
|
_dict["exchanges"] = _items
|
129
|
+
# puts key-value pairs in additional_properties in the top level
|
130
|
+
if self.additional_properties is not None:
|
131
|
+
for _key, _value in self.additional_properties.items():
|
132
|
+
_dict[_key] = _value
|
133
|
+
|
121
134
|
# set to None if margin_mode (nullable) is None
|
122
135
|
# and model_fields_set contains the field
|
123
136
|
if self.margin_mode is None and "margin_mode" in self.model_fields_set:
|
@@ -157,4 +170,9 @@ class Strategy(BaseModel):
|
|
157
170
|
"market_type": obj.get("market_type"),
|
158
171
|
}
|
159
172
|
)
|
173
|
+
# store additional fields in additional_properties
|
174
|
+
for _key in obj.keys():
|
175
|
+
if _key not in cls.__properties:
|
176
|
+
_obj.additional_properties[_key] = obj.get(_key)
|
177
|
+
|
160
178
|
return _obj
|
@@ -17,7 +17,7 @@ import pprint
|
|
17
17
|
import re # noqa: F401
|
18
18
|
import json
|
19
19
|
|
20
|
-
from pydantic import BaseModel, ConfigDict, Field, StrictBool,
|
20
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr
|
21
21
|
from typing import Any, ClassVar, Dict, List, Optional, Union
|
22
22
|
from typing_extensions import Annotated
|
23
23
|
from crypticorn.trade.client.models.margin_mode import MarginMode
|
@@ -41,15 +41,18 @@ class StrategyCreate(BaseModel):
|
|
41
41
|
description="Whether the strategy is enabled. If false, no bots will be created or updated for this strategy, open trades will be rejected. Existing bots will be marked as stopping."
|
42
42
|
)
|
43
43
|
performance_fee: Union[
|
44
|
-
Annotated[float, Field(le=1.0, strict=True)],
|
45
|
-
Annotated[int, Field(le=1, strict=True)],
|
44
|
+
Annotated[float, Field(le=1.0, strict=True, ge=0.0)],
|
45
|
+
Annotated[int, Field(le=1, strict=True, ge=0)],
|
46
46
|
] = Field(description="Performance fee for the strategy")
|
47
47
|
identifier: StrictStr = Field(
|
48
48
|
description="Unique human readable identifier for the strategy e.g. 'daily_trend_momentum'"
|
49
49
|
)
|
50
50
|
margin_mode: Optional[MarginMode] = None
|
51
|
-
leverage:
|
51
|
+
leverage: Annotated[int, Field(strict=True, ge=1)] = Field(
|
52
|
+
description="Leverage for the strategy"
|
53
|
+
)
|
52
54
|
market_type: MarketType = Field(description="Market of operation of the strategy")
|
55
|
+
additional_properties: Dict[str, Any] = {}
|
53
56
|
__properties: ClassVar[List[str]] = [
|
54
57
|
"name",
|
55
58
|
"description",
|
@@ -91,8 +94,13 @@ class StrategyCreate(BaseModel):
|
|
91
94
|
* `None` is only added to the output dict for nullable fields that
|
92
95
|
were set at model initialization. Other fields with value `None`
|
93
96
|
are ignored.
|
97
|
+
* Fields in `self.additional_properties` are added to the output dict.
|
94
98
|
"""
|
95
|
-
excluded_fields: Set[str] = set(
|
99
|
+
excluded_fields: Set[str] = set(
|
100
|
+
[
|
101
|
+
"additional_properties",
|
102
|
+
]
|
103
|
+
)
|
96
104
|
|
97
105
|
_dict = self.model_dump(
|
98
106
|
by_alias=True,
|
@@ -106,6 +114,11 @@ class StrategyCreate(BaseModel):
|
|
106
114
|
if _item_exchanges:
|
107
115
|
_items.append(_item_exchanges.to_dict())
|
108
116
|
_dict["exchanges"] = _items
|
117
|
+
# puts key-value pairs in additional_properties in the top level
|
118
|
+
if self.additional_properties is not None:
|
119
|
+
for _key, _value in self.additional_properties.items():
|
120
|
+
_dict[_key] = _value
|
121
|
+
|
109
122
|
# set to None if margin_mode (nullable) is None
|
110
123
|
# and model_fields_set contains the field
|
111
124
|
if self.margin_mode is None and "margin_mode" in self.model_fields_set:
|
@@ -142,4 +155,9 @@ class StrategyCreate(BaseModel):
|
|
142
155
|
"market_type": obj.get("market_type"),
|
143
156
|
}
|
144
157
|
)
|
158
|
+
# store additional fields in additional_properties
|
159
|
+
for _key in obj.keys():
|
160
|
+
if _key not in cls.__properties:
|
161
|
+
_obj.additional_properties[_key] = obj.get(_key)
|
162
|
+
|
145
163
|
return _obj
|
@@ -17,8 +17,8 @@ import pprint
|
|
17
17
|
import re # noqa: F401
|
18
18
|
import json
|
19
19
|
|
20
|
-
from pydantic import BaseModel, ConfigDict, Field, StrictInt
|
21
|
-
from typing import Any, ClassVar, Dict, List
|
20
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr
|
21
|
+
from typing import Any, ClassVar, Dict, List, Optional
|
22
22
|
from crypticorn.trade.client.models.exchange import Exchange
|
23
23
|
from typing import Optional, Set
|
24
24
|
from typing_extensions import Self
|
@@ -30,10 +30,14 @@ class StrategyExchangeInfo(BaseModel):
|
|
30
30
|
""" # noqa: E501
|
31
31
|
|
32
32
|
exchange: Exchange = Field(description="Exchange name. Of type Exchange")
|
33
|
+
base_asset: Optional[StrictStr] = Field(
|
34
|
+
default="USDT",
|
35
|
+
description="Base asset for the strategy. This is the asset that will be used to trade with. Default is USDT.",
|
36
|
+
)
|
33
37
|
min_amount: StrictInt = Field(
|
34
38
|
description="Minimum amount for the strategy on the exchange"
|
35
39
|
)
|
36
|
-
__properties: ClassVar[List[str]] = ["exchange", "min_amount"]
|
40
|
+
__properties: ClassVar[List[str]] = ["exchange", "base_asset", "min_amount"]
|
37
41
|
|
38
42
|
model_config = ConfigDict(
|
39
43
|
populate_by_name=True,
|
@@ -84,6 +88,14 @@ class StrategyExchangeInfo(BaseModel):
|
|
84
88
|
return cls.model_validate(obj)
|
85
89
|
|
86
90
|
_obj = cls.model_validate(
|
87
|
-
{
|
91
|
+
{
|
92
|
+
"exchange": obj.get("exchange"),
|
93
|
+
"base_asset": (
|
94
|
+
obj.get("base_asset")
|
95
|
+
if obj.get("base_asset") is not None
|
96
|
+
else "USDT"
|
97
|
+
),
|
98
|
+
"min_amount": obj.get("min_amount"),
|
99
|
+
}
|
88
100
|
)
|
89
101
|
return _obj
|
@@ -36,10 +36,11 @@ class StrategyUpdate(BaseModel):
|
|
36
36
|
enabled: Optional[StrictBool] = None
|
37
37
|
performance_fee: Optional[
|
38
38
|
Union[
|
39
|
-
Annotated[float, Field(le=1.0, strict=True)],
|
40
|
-
Annotated[int, Field(le=1, strict=True)],
|
39
|
+
Annotated[float, Field(le=1.0, strict=True, ge=0.0)],
|
40
|
+
Annotated[int, Field(le=1, strict=True, ge=0)],
|
41
41
|
]
|
42
42
|
] = None
|
43
|
+
additional_properties: Dict[str, Any] = {}
|
43
44
|
__properties: ClassVar[List[str]] = [
|
44
45
|
"name",
|
45
46
|
"description",
|
@@ -77,8 +78,13 @@ class StrategyUpdate(BaseModel):
|
|
77
78
|
* `None` is only added to the output dict for nullable fields that
|
78
79
|
were set at model initialization. Other fields with value `None`
|
79
80
|
are ignored.
|
81
|
+
* Fields in `self.additional_properties` are added to the output dict.
|
80
82
|
"""
|
81
|
-
excluded_fields: Set[str] = set(
|
83
|
+
excluded_fields: Set[str] = set(
|
84
|
+
[
|
85
|
+
"additional_properties",
|
86
|
+
]
|
87
|
+
)
|
82
88
|
|
83
89
|
_dict = self.model_dump(
|
84
90
|
by_alias=True,
|
@@ -92,6 +98,11 @@ class StrategyUpdate(BaseModel):
|
|
92
98
|
if _item_exchanges:
|
93
99
|
_items.append(_item_exchanges.to_dict())
|
94
100
|
_dict["exchanges"] = _items
|
101
|
+
# puts key-value pairs in additional_properties in the top level
|
102
|
+
if self.additional_properties is not None:
|
103
|
+
for _key, _value in self.additional_properties.items():
|
104
|
+
_dict[_key] = _value
|
105
|
+
|
95
106
|
# set to None if name (nullable) is None
|
96
107
|
# and model_fields_set contains the field
|
97
108
|
if self.name is None and "name" in self.model_fields_set:
|
@@ -144,4 +155,9 @@ class StrategyUpdate(BaseModel):
|
|
144
155
|
"performance_fee": obj.get("performance_fee"),
|
145
156
|
}
|
146
157
|
)
|
158
|
+
# store additional fields in additional_properties
|
159
|
+
for _key in obj.keys():
|
160
|
+
if _key not in cls.__properties:
|
161
|
+
_obj.additional_properties[_key] = obj.get(_key)
|
162
|
+
|
147
163
|
return _obj
|
@@ -28,18 +28,14 @@ class TPSL(BaseModel):
|
|
28
28
|
Runtime fields for take profit and stop loss
|
29
29
|
""" # noqa: E501
|
30
30
|
|
31
|
-
price_delta:
|
32
|
-
|
31
|
+
price_delta: StrictStr = Field(
|
32
|
+
description="The price delta to calculate the limit price from the current market price, e.g. 1.01 for a TP of 1% on long"
|
33
|
+
)
|
33
34
|
allocation: StrictStr = Field(
|
34
35
|
description="Percentage of the open order to sell. All allocations must sum up to 1. Use this allocation again when closing the order."
|
35
36
|
)
|
36
37
|
execution_id: Optional[StrictStr] = None
|
37
|
-
__properties: ClassVar[List[str]] = [
|
38
|
-
"price_delta",
|
39
|
-
"price",
|
40
|
-
"allocation",
|
41
|
-
"execution_id",
|
42
|
-
]
|
38
|
+
__properties: ClassVar[List[str]] = ["price_delta", "allocation", "execution_id"]
|
43
39
|
|
44
40
|
model_config = ConfigDict(
|
45
41
|
populate_by_name=True,
|
@@ -78,16 +74,6 @@ class TPSL(BaseModel):
|
|
78
74
|
exclude=excluded_fields,
|
79
75
|
exclude_none=True,
|
80
76
|
)
|
81
|
-
# set to None if price_delta (nullable) is None
|
82
|
-
# and model_fields_set contains the field
|
83
|
-
if self.price_delta is None and "price_delta" in self.model_fields_set:
|
84
|
-
_dict["price_delta"] = None
|
85
|
-
|
86
|
-
# set to None if price (nullable) is None
|
87
|
-
# and model_fields_set contains the field
|
88
|
-
if self.price is None and "price" in self.model_fields_set:
|
89
|
-
_dict["price"] = None
|
90
|
-
|
91
77
|
# set to None if execution_id (nullable) is None
|
92
78
|
# and model_fields_set contains the field
|
93
79
|
if self.execution_id is None and "execution_id" in self.model_fields_set:
|
@@ -107,7 +93,6 @@ class TPSL(BaseModel):
|
|
107
93
|
_obj = cls.model_validate(
|
108
94
|
{
|
109
95
|
"price_delta": obj.get("price_delta"),
|
110
|
-
"price": obj.get("price"),
|
111
96
|
"allocation": obj.get("allocation"),
|
112
97
|
"execution_id": obj.get("execution_id"),
|
113
98
|
}
|
@@ -18,7 +18,7 @@ import re # noqa: F401
|
|
18
18
|
import json
|
19
19
|
|
20
20
|
from pydantic import BaseModel, ConfigDict, Field, StrictStr
|
21
|
-
from typing import Any, ClassVar, Dict, List
|
21
|
+
from typing import Any, ClassVar, Dict, List
|
22
22
|
from typing import Optional, Set
|
23
23
|
from typing_extensions import Self
|
24
24
|
|
@@ -28,12 +28,13 @@ class TPSLCreate(BaseModel):
|
|
28
28
|
Model for take profit and stop loss
|
29
29
|
""" # noqa: E501
|
30
30
|
|
31
|
-
price_delta:
|
32
|
-
|
31
|
+
price_delta: StrictStr = Field(
|
32
|
+
description="The price delta to calculate the limit price from the current market price, e.g. 1.01 for a TP of 1% on long"
|
33
|
+
)
|
33
34
|
allocation: StrictStr = Field(
|
34
35
|
description="Percentage of the open order to sell. All allocations must sum up to 1. Use this allocation again when closing the order."
|
35
36
|
)
|
36
|
-
__properties: ClassVar[List[str]] = ["price_delta", "
|
37
|
+
__properties: ClassVar[List[str]] = ["price_delta", "allocation"]
|
37
38
|
|
38
39
|
model_config = ConfigDict(
|
39
40
|
populate_by_name=True,
|
@@ -72,16 +73,6 @@ class TPSLCreate(BaseModel):
|
|
72
73
|
exclude=excluded_fields,
|
73
74
|
exclude_none=True,
|
74
75
|
)
|
75
|
-
# set to None if price_delta (nullable) is None
|
76
|
-
# and model_fields_set contains the field
|
77
|
-
if self.price_delta is None and "price_delta" in self.model_fields_set:
|
78
|
-
_dict["price_delta"] = None
|
79
|
-
|
80
|
-
# set to None if price (nullable) is None
|
81
|
-
# and model_fields_set contains the field
|
82
|
-
if self.price is None and "price" in self.model_fields_set:
|
83
|
-
_dict["price"] = None
|
84
|
-
|
85
76
|
return _dict
|
86
77
|
|
87
78
|
@classmethod
|
@@ -94,10 +85,6 @@ class TPSLCreate(BaseModel):
|
|
94
85
|
return cls.model_validate(obj)
|
95
86
|
|
96
87
|
_obj = cls.model_validate(
|
97
|
-
{
|
98
|
-
"price_delta": obj.get("price_delta"),
|
99
|
-
"price": obj.get("price"),
|
100
|
-
"allocation": obj.get("allocation"),
|
101
|
-
}
|
88
|
+
{"price_delta": obj.get("price_delta"), "allocation": obj.get("allocation")}
|
102
89
|
)
|
103
90
|
return _obj
|
crypticorn/trade/client/rest.py
CHANGED
@@ -77,6 +77,7 @@ class RESTClientObject:
|
|
77
77
|
|
78
78
|
self.pool_manager: Optional[aiohttp.ClientSession] = None
|
79
79
|
self.retry_client: Optional[aiohttp_retry.RetryClient] = None
|
80
|
+
self.is_sync: bool = False # Track whether this is sync or async mode
|
80
81
|
|
81
82
|
async def close(self) -> None:
|
82
83
|
if self.pool_manager:
|
@@ -170,7 +171,9 @@ class RESTClientObject:
|
|
170
171
|
|
171
172
|
pool_manager: Union[aiohttp.ClientSession, aiohttp_retry.RetryClient]
|
172
173
|
|
173
|
-
#
|
174
|
+
# For sync operations, always use a fresh session
|
175
|
+
should_close_session = False
|
176
|
+
|
174
177
|
if self.pool_manager is None:
|
175
178
|
self.pool_manager = aiohttp.ClientSession(
|
176
179
|
connector=aiohttp.TCPConnector(
|
@@ -178,6 +181,9 @@ class RESTClientObject:
|
|
178
181
|
),
|
179
182
|
trust_env=True,
|
180
183
|
)
|
184
|
+
# Only close session automatically in sync mode
|
185
|
+
should_close_session = self.is_sync
|
186
|
+
|
181
187
|
pool_manager = self.pool_manager
|
182
188
|
|
183
189
|
if self.retries is not None and method in ALLOW_RETRY_METHODS:
|
@@ -193,6 +199,19 @@ class RESTClientObject:
|
|
193
199
|
)
|
194
200
|
pool_manager = self.retry_client
|
195
201
|
|
196
|
-
|
197
|
-
|
198
|
-
|
202
|
+
try:
|
203
|
+
r = await pool_manager.request(**args)
|
204
|
+
# For sessions we're about to close, read the data immediately
|
205
|
+
if should_close_session:
|
206
|
+
response = RESTResponse(r)
|
207
|
+
await response.read() # Read data before closing session
|
208
|
+
return response
|
209
|
+
else:
|
210
|
+
return RESTResponse(r)
|
211
|
+
finally:
|
212
|
+
if should_close_session:
|
213
|
+
if self.retry_client is not None:
|
214
|
+
await self.retry_client.close()
|
215
|
+
self.retry_client = None
|
216
|
+
await self.pool_manager.close()
|
217
|
+
self.pool_manager = None
|
crypticorn/trade/main.py
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from typing import TYPE_CHECKING, Optional
|
3
|
-
from crypticorn.trade import (
|
3
|
+
from crypticorn.trade.client import (
|
4
4
|
ApiClient,
|
5
5
|
APIKeysApi,
|
6
6
|
BotsApi,
|
7
7
|
Configuration,
|
8
8
|
ExchangesApi,
|
9
|
-
FuturesTradingPanelApi,
|
10
9
|
NotificationsApi,
|
11
10
|
OrdersApi,
|
12
11
|
StatusApi,
|
@@ -26,19 +25,23 @@ class TradeClient:
|
|
26
25
|
config_class = Configuration
|
27
26
|
|
28
27
|
def __init__(
|
29
|
-
self,
|
28
|
+
self,
|
29
|
+
config: Configuration,
|
30
|
+
http_client: Optional[ClientSession] = None,
|
31
|
+
is_sync: bool = False,
|
30
32
|
):
|
31
33
|
self.config = config
|
32
34
|
self.base_client = ApiClient(configuration=self.config)
|
33
35
|
if http_client is not None:
|
34
36
|
self.base_client.rest_client.pool_manager = http_client
|
37
|
+
# Pass sync context to REST client for proper session management
|
38
|
+
self.base_client.rest_client.is_sync = is_sync
|
35
39
|
# Instantiate all the endpoint clients
|
36
|
-
self.bots = BotsApi(self.base_client)
|
37
|
-
self.exchanges = ExchangesApi(self.base_client)
|
38
|
-
self.notifications = NotificationsApi(self.base_client)
|
39
|
-
self.orders = OrdersApi(self.base_client)
|
40
|
-
self.status = StatusApi(self.base_client)
|
41
|
-
self.strategies = StrategiesApi(self.base_client)
|
42
|
-
self.actions = TradingActionsApi(self.base_client)
|
43
|
-
self.
|
44
|
-
self.keys = APIKeysApi(self.base_client)
|
40
|
+
self.bots = BotsApi(self.base_client, is_sync=is_sync)
|
41
|
+
self.exchanges = ExchangesApi(self.base_client, is_sync=is_sync)
|
42
|
+
self.notifications = NotificationsApi(self.base_client, is_sync=is_sync)
|
43
|
+
self.orders = OrdersApi(self.base_client, is_sync=is_sync)
|
44
|
+
self.status = StatusApi(self.base_client, is_sync=is_sync)
|
45
|
+
self.strategies = StrategiesApi(self.base_client, is_sync=is_sync)
|
46
|
+
self.actions = TradingActionsApi(self.base_client, is_sync=is_sync)
|
47
|
+
self.keys = APIKeysApi(self.base_client, is_sync=is_sync)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: crypticorn
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.17.0
|
4
4
|
Summary: Maximise Your Crypto Trading Profits with Machine Learning
|
5
5
|
Author-email: Crypticorn <timon@crypticorn.com>
|
6
6
|
License-Expression: MIT
|
@@ -25,6 +25,8 @@ Requires-Dist: click<9.0.0,>=8.0.0
|
|
25
25
|
Requires-Dist: psutil<8.0.0,>=7.0.0
|
26
26
|
Requires-Dist: setuptools<81.0.0,>=80.0.0
|
27
27
|
Requires-Dist: strenum
|
28
|
+
Requires-Dist: prometheus-client<1.0.0,>=0.22.0
|
29
|
+
Requires-Dist: asgiref<4.0.0,>=3.6.0
|
28
30
|
Requires-Dist: urllib3<3.0.0,>=1.25.3
|
29
31
|
Requires-Dist: python_dateutil<3.0.0,>=2.8.2
|
30
32
|
Requires-Dist: aiohttp<4.0.0,>=3.8.4
|
@@ -49,6 +51,7 @@ Requires-Dist: pytest-asyncio==0.26.0; extra == "test"
|
|
49
51
|
Requires-Dist: pytest-cov==6.1.1; extra == "test"
|
50
52
|
Requires-Dist: python-dotenv==1.0.1; extra == "test"
|
51
53
|
Requires-Dist: PyJWT==2.10.0; extra == "test"
|
54
|
+
Requires-Dist: httpx>=0.27.0; extra == "test"
|
52
55
|
Provides-Extra: extra
|
53
56
|
Requires-Dist: pandas<3.0.0,>=2.2.0; extra == "extra"
|
54
57
|
Dynamic: license-file
|
@@ -78,11 +81,15 @@ You can install extra dependencies grouped in the extras `extra`, `dev` (develop
|
|
78
81
|
|
79
82
|
## Structure
|
80
83
|
|
81
|
-
Our API is available as an asynchronous Python SDK. The main entry
|
84
|
+
Our API is available as both an asynchronous and synchronous Python SDK. The main entry points are:
|
85
|
+
|
86
|
+
- `AsyncClient` - Asynchronous client for async/await usage
|
87
|
+
- `SyncClient` - Synchronous client for traditional blocking calls
|
88
|
+
|
82
89
|
```python
|
83
|
-
from crypticorn import
|
90
|
+
from crypticorn import AsyncClient, SyncClient
|
84
91
|
```
|
85
|
-
|
92
|
+
Both clients serve as the central interface for API operations and instantiate multiple API wrappers corresponding to our micro services. These are structured the following:
|
86
93
|
|
87
94
|
<img src="static/pip-structure.svg" alt="pip package structure" />
|
88
95
|
|
@@ -110,20 +117,22 @@ There are scopes which don't follow this structure. Those are either scopes that
|
|
110
117
|
|
111
118
|
## Basic Usage
|
112
119
|
|
113
|
-
|
120
|
+
### Asynchronous Client
|
121
|
+
|
122
|
+
You can use the async client with the context manager protocol...
|
114
123
|
```python
|
115
|
-
async with
|
116
|
-
|
124
|
+
async with AsyncClient(api_key="your-api-key") as client:
|
125
|
+
await client.pay.products.get_products()
|
117
126
|
```
|
118
127
|
...or without it like this...
|
119
128
|
```python
|
120
|
-
client =
|
121
|
-
asyncio.run(client.pay.
|
129
|
+
client = AsyncClient(api_key="your-api-key")
|
130
|
+
asyncio.run(client.pay.products.get_products())
|
122
131
|
asyncio.run(client.close())
|
123
132
|
```
|
124
133
|
...or this.
|
125
134
|
```python
|
126
|
-
client =
|
135
|
+
client = AsyncClient(api_key="your-api-key")
|
127
136
|
|
128
137
|
async def main():
|
129
138
|
await client.pay.products.get_products()
|
@@ -132,6 +141,29 @@ asyncio.run(main())
|
|
132
141
|
asyncio.run(client.close())
|
133
142
|
```
|
134
143
|
|
144
|
+
### Synchronous Client
|
145
|
+
|
146
|
+
For traditional synchronous usage without async/await, use the `SyncClient`:
|
147
|
+
|
148
|
+
```python
|
149
|
+
from crypticorn import SyncClient
|
150
|
+
|
151
|
+
# With context manager (recommended)
|
152
|
+
with SyncClient(api_key="your-api-key") as client:
|
153
|
+
products = client.pay.products.get_products()
|
154
|
+
status = client.trade.status.ping()
|
155
|
+
|
156
|
+
# Or without context manager
|
157
|
+
client = SyncClient(api_key="your-api-key")
|
158
|
+
try:
|
159
|
+
products = client.pay.products.get_products()
|
160
|
+
status = client.trade.status.ping()
|
161
|
+
finally:
|
162
|
+
client.close() # Manual cleanup required
|
163
|
+
```
|
164
|
+
|
165
|
+
The sync client provides the same API surface as the async client, but all methods return results directly instead of coroutines. Under the hood, it uses `asgiref.async_to_sync` to bridge async operations to synchronous calls, ensuring reliable operation without requiring async/await syntax.
|
166
|
+
|
135
167
|
## Response Types
|
136
168
|
|
137
169
|
There are three different available output formats you can choose from:
|
@@ -139,7 +171,10 @@ There are three different available output formats you can choose from:
|
|
139
171
|
### Serialized Response
|
140
172
|
You can get fully serialized responses as pydantic models. Using this, you get the full benefits of pydantic's type checking.
|
141
173
|
```python
|
174
|
+
# Async client
|
142
175
|
res = await client.pay.products.get_products()
|
176
|
+
# Sync client
|
177
|
+
res = client.pay.products.get_products()
|
143
178
|
print(res)
|
144
179
|
```
|
145
180
|
The output would look like this:
|
@@ -149,7 +184,10 @@ The output would look like this:
|
|
149
184
|
|
150
185
|
### Serialized Response with HTTP Info
|
151
186
|
```python
|
187
|
+
# Async client
|
152
188
|
res = await client.pay.products.get_products_with_http_info()
|
189
|
+
# Sync client
|
190
|
+
res = client.pay.products.get_products_with_http_info()
|
153
191
|
print(res)
|
154
192
|
```
|
155
193
|
The output would look like this:
|
@@ -170,8 +208,12 @@ print(res.headers)
|
|
170
208
|
### JSON Response
|
171
209
|
You can receive a classical JSON response by suffixing the function name with `_without_preload_content`
|
172
210
|
```python
|
211
|
+
# Async client
|
173
212
|
response = await client.pay.products.get_products_without_preload_content()
|
174
213
|
print(await response.json())
|
214
|
+
|
215
|
+
# Sync client - Note: use regular methods instead as response.json() returns a coroutine
|
216
|
+
response = client.pay.products.get_products_without_preload_content()
|
175
217
|
```
|
176
218
|
The output would look like this:
|
177
219
|
```python
|
@@ -201,33 +243,36 @@ To override e.g. the host for the Hive client to connect to localhost:8000 inste
|
|
201
243
|
from crypticorn.hive import Configuration as HiveConfig
|
202
244
|
from crypticorn.common import Service
|
203
245
|
|
204
|
-
|
246
|
+
# Async client
|
247
|
+
async with AsyncClient() as client:
|
248
|
+
client.configure(config=HiveConfig(host="http://localhost:8000"), service=Service.HIVE)
|
249
|
+
|
250
|
+
# Sync client
|
251
|
+
with SyncClient() as client:
|
205
252
|
client.configure(config=HiveConfig(host="http://localhost:8000"), service=Service.HIVE)
|
206
253
|
```
|
207
254
|
|
208
255
|
### Session Management
|
209
256
|
|
210
|
-
By default, `
|
257
|
+
By default, `AsyncClient` manages a single shared `aiohttp.ClientSession` for all service wrappers.
|
211
258
|
However, you can pass your own pre-configured `aiohttp.ClientSession` if you need advanced control — for example, to add retries, custom headers, logging, or mocking behavior.
|
212
259
|
|
213
260
|
When you inject a custom session, you are responsible for managing its lifecycle, including closing when you're done.
|
214
261
|
|
215
262
|
```python
|
216
263
|
import aiohttp
|
217
|
-
from crypticorn import
|
264
|
+
from crypticorn import AsyncClient
|
218
265
|
|
219
266
|
async def main():
|
220
267
|
custom_session = aiohttp.ClientSession()
|
221
|
-
async with
|
268
|
+
async with AsyncClient(api_key="your-key", http_client=custom_session) as client:
|
222
269
|
await client.trade.status.ping()
|
223
270
|
await custom_session.close()
|
224
|
-
|
225
|
-
custom_session = aiohttp.ClientSession()
|
226
|
-
client = ApiClient(api_key="your-key", http_client=custom_session)
|
227
|
-
await client.trade.status.ping()
|
228
|
-
await custom_session.close()
|
271
|
+
|
229
272
|
```
|
230
|
-
If you don’t pass a session, `
|
273
|
+
If you don’t pass a session, `AsyncClient` will create and manage one internally. In that case, it will be automatically closed when using `async with` or when calling `await client.close()` manually.
|
274
|
+
|
275
|
+
**Note on Sync Client**: The `SyncClient` uses per-operation sessions (creates and closes a session for each API call) to ensure reliable synchronous behavior. Custom sessions are accepted but not used. This approach prevents event loop conflicts at the cost of slightly higher overhead per operation.
|
231
276
|
|
232
277
|
### Disable Logging
|
233
278
|
|