crypticorn 2.15.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.
Files changed (168) hide show
  1. crypticorn/__init__.py +2 -2
  2. crypticorn/auth/client/api/admin_api.py +397 -13
  3. crypticorn/auth/client/api/auth_api.py +3610 -341
  4. crypticorn/auth/client/api/service_api.py +249 -7
  5. crypticorn/auth/client/api/user_api.py +2295 -179
  6. crypticorn/auth/client/api/wallet_api.py +1468 -81
  7. crypticorn/auth/client/configuration.py +2 -2
  8. crypticorn/auth/client/models/create_api_key_request.py +2 -1
  9. crypticorn/auth/client/models/get_api_keys200_response_inner.py +2 -1
  10. crypticorn/auth/client/rest.py +23 -4
  11. crypticorn/auth/main.py +8 -5
  12. crypticorn/cli/init.py +1 -1
  13. crypticorn/cli/templates/.env.docker.temp +3 -0
  14. crypticorn/cli/templates/.env.example.temp +4 -0
  15. crypticorn/cli/templates/Dockerfile +5 -2
  16. crypticorn/client.py +226 -59
  17. crypticorn/common/__init__.py +1 -0
  18. crypticorn/common/auth.py +45 -14
  19. crypticorn/common/decorators.py +1 -2
  20. crypticorn/common/enums.py +0 -2
  21. crypticorn/common/errors.py +10 -0
  22. crypticorn/common/metrics.py +30 -0
  23. crypticorn/common/middleware.py +94 -1
  24. crypticorn/common/pagination.py +252 -18
  25. crypticorn/common/router/admin_router.py +2 -2
  26. crypticorn/common/router/status_router.py +40 -2
  27. crypticorn/common/scopes.py +2 -0
  28. crypticorn/common/warnings.py +8 -0
  29. crypticorn/dex/__init__.py +6 -0
  30. crypticorn/dex/client/__init__.py +49 -0
  31. crypticorn/dex/client/api/__init__.py +6 -0
  32. crypticorn/dex/client/api/admin_api.py +2986 -0
  33. crypticorn/dex/client/api/signals_api.py +1798 -0
  34. crypticorn/dex/client/api/status_api.py +892 -0
  35. crypticorn/dex/client/api_client.py +758 -0
  36. crypticorn/dex/client/api_response.py +20 -0
  37. crypticorn/dex/client/configuration.py +620 -0
  38. crypticorn/dex/client/exceptions.py +220 -0
  39. crypticorn/dex/client/models/__init__.py +30 -0
  40. crypticorn/dex/client/models/api_error_identifier.py +121 -0
  41. crypticorn/dex/client/models/api_error_level.py +37 -0
  42. crypticorn/dex/client/models/api_error_type.py +37 -0
  43. crypticorn/dex/client/models/exception_detail.py +117 -0
  44. crypticorn/dex/client/models/log_level.py +38 -0
  45. crypticorn/dex/client/models/paginated_response_signal_with_token.py +134 -0
  46. crypticorn/dex/client/models/risk.py +86 -0
  47. crypticorn/dex/client/models/signal_overview_stats.py +158 -0
  48. crypticorn/dex/client/models/signal_volume.py +84 -0
  49. crypticorn/dex/client/models/signal_with_token.py +163 -0
  50. crypticorn/dex/client/models/token_data.py +127 -0
  51. crypticorn/dex/client/models/token_detail.py +116 -0
  52. crypticorn/dex/client/py.typed +0 -0
  53. crypticorn/dex/client/rest.py +217 -0
  54. crypticorn/dex/main.py +1 -0
  55. crypticorn/hive/client/api/admin_api.py +1173 -47
  56. crypticorn/hive/client/api/data_api.py +499 -17
  57. crypticorn/hive/client/api/models_api.py +1595 -87
  58. crypticorn/hive/client/api/status_api.py +397 -16
  59. crypticorn/hive/client/api_client.py +0 -5
  60. crypticorn/hive/client/models/api_error_identifier.py +1 -1
  61. crypticorn/hive/client/models/coin_info.py +1 -1
  62. crypticorn/hive/client/models/exception_detail.py +1 -1
  63. crypticorn/hive/client/models/target_info.py +1 -1
  64. crypticorn/hive/client/rest.py +23 -4
  65. crypticorn/hive/main.py +99 -25
  66. crypticorn/hive/utils.py +2 -2
  67. crypticorn/klines/client/api/admin_api.py +1173 -47
  68. crypticorn/klines/client/api/change_in_timeframe_api.py +269 -11
  69. crypticorn/klines/client/api/funding_rates_api.py +315 -11
  70. crypticorn/klines/client/api/ohlcv_data_api.py +390 -11
  71. crypticorn/klines/client/api/status_api.py +397 -16
  72. crypticorn/klines/client/api/symbols_api.py +216 -11
  73. crypticorn/klines/client/api/udf_api.py +1268 -51
  74. crypticorn/klines/client/api_client.py +0 -5
  75. crypticorn/klines/client/models/api_error_identifier.py +3 -1
  76. crypticorn/klines/client/models/exception_detail.py +1 -1
  77. crypticorn/klines/client/models/ohlcv.py +1 -1
  78. crypticorn/klines/client/models/symbol_group.py +1 -1
  79. crypticorn/klines/client/models/udf_config.py +1 -1
  80. crypticorn/klines/client/rest.py +23 -4
  81. crypticorn/klines/main.py +89 -12
  82. crypticorn/metrics/client/api/admin_api.py +1173 -47
  83. crypticorn/metrics/client/api/exchanges_api.py +1370 -145
  84. crypticorn/metrics/client/api/indicators_api.py +622 -17
  85. crypticorn/metrics/client/api/logs_api.py +296 -11
  86. crypticorn/metrics/client/api/marketcap_api.py +1207 -67
  87. crypticorn/metrics/client/api/markets_api.py +343 -11
  88. crypticorn/metrics/client/api/quote_currencies_api.py +228 -11
  89. crypticorn/metrics/client/api/status_api.py +397 -16
  90. crypticorn/metrics/client/api/tokens_api.py +382 -15
  91. crypticorn/metrics/client/api_client.py +0 -5
  92. crypticorn/metrics/client/configuration.py +4 -2
  93. crypticorn/metrics/client/models/exception_detail.py +1 -1
  94. crypticorn/metrics/client/models/exchange_mapping.py +1 -1
  95. crypticorn/metrics/client/models/marketcap_ranking.py +1 -1
  96. crypticorn/metrics/client/models/marketcap_symbol_ranking.py +1 -1
  97. crypticorn/metrics/client/models/ohlcv.py +1 -1
  98. crypticorn/metrics/client/rest.py +23 -4
  99. crypticorn/metrics/main.py +113 -19
  100. crypticorn/pay/client/api/admin_api.py +1585 -57
  101. crypticorn/pay/client/api/now_payments_api.py +961 -39
  102. crypticorn/pay/client/api/payments_api.py +562 -17
  103. crypticorn/pay/client/api/products_api.py +880 -30
  104. crypticorn/pay/client/api/status_api.py +397 -16
  105. crypticorn/pay/client/api_client.py +0 -5
  106. crypticorn/pay/client/configuration.py +2 -2
  107. crypticorn/pay/client/models/api_error_identifier.py +7 -7
  108. crypticorn/pay/client/models/exception_detail.py +1 -1
  109. crypticorn/pay/client/models/now_create_invoice_req.py +1 -1
  110. crypticorn/pay/client/models/now_create_invoice_res.py +1 -1
  111. crypticorn/pay/client/models/product.py +1 -1
  112. crypticorn/pay/client/models/product_create.py +1 -1
  113. crypticorn/pay/client/models/product_update.py +1 -1
  114. crypticorn/pay/client/models/scope.py +1 -0
  115. crypticorn/pay/client/rest.py +23 -4
  116. crypticorn/pay/main.py +10 -6
  117. crypticorn/trade/client/__init__.py +11 -1
  118. crypticorn/trade/client/api/__init__.py +0 -1
  119. crypticorn/trade/client/api/admin_api.py +1184 -55
  120. crypticorn/trade/client/api/api_keys_api.py +1678 -162
  121. crypticorn/trade/client/api/bots_api.py +7563 -187
  122. crypticorn/trade/client/api/exchanges_api.py +565 -19
  123. crypticorn/trade/client/api/notifications_api.py +1290 -116
  124. crypticorn/trade/client/api/orders_api.py +393 -55
  125. crypticorn/trade/client/api/status_api.py +397 -13
  126. crypticorn/trade/client/api/strategies_api.py +1133 -77
  127. crypticorn/trade/client/api/trading_actions_api.py +786 -65
  128. crypticorn/trade/client/models/__init__.py +11 -0
  129. crypticorn/trade/client/models/actions_count.py +88 -0
  130. crypticorn/trade/client/models/api_error_identifier.py +8 -7
  131. crypticorn/trade/client/models/bot.py +7 -18
  132. crypticorn/trade/client/models/bot_create.py +17 -1
  133. crypticorn/trade/client/models/bot_update.py +17 -1
  134. crypticorn/trade/client/models/exchange.py +6 -1
  135. crypticorn/trade/client/models/exchange_key.py +1 -1
  136. crypticorn/trade/client/models/exchange_key_balance.py +111 -0
  137. crypticorn/trade/client/models/exchange_key_create.py +17 -1
  138. crypticorn/trade/client/models/exchange_key_update.py +17 -1
  139. crypticorn/trade/client/models/execution_ids.py +1 -1
  140. crypticorn/trade/client/models/futures_balance.py +27 -25
  141. crypticorn/trade/client/models/futures_trading_action.py +6 -28
  142. crypticorn/trade/client/models/futures_trading_action_create.py +10 -13
  143. crypticorn/trade/client/models/notification.py +17 -1
  144. crypticorn/trade/client/models/notification_create.py +18 -2
  145. crypticorn/trade/client/models/notification_update.py +17 -1
  146. crypticorn/trade/client/models/order.py +2 -14
  147. crypticorn/trade/client/models/orders_count.py +88 -0
  148. crypticorn/trade/client/models/paginated_response_futures_trading_action.py +134 -0
  149. crypticorn/trade/client/models/paginated_response_order.py +134 -0
  150. crypticorn/trade/client/models/pn_l.py +95 -0
  151. crypticorn/trade/client/models/post_futures_action.py +1 -1
  152. crypticorn/trade/client/models/spot_balance.py +109 -0
  153. crypticorn/trade/client/models/spot_trading_action_create.py +4 -1
  154. crypticorn/trade/client/models/strategy.py +22 -4
  155. crypticorn/trade/client/models/strategy_create.py +23 -5
  156. crypticorn/trade/client/models/strategy_exchange_info.py +16 -4
  157. crypticorn/trade/client/models/strategy_update.py +19 -3
  158. crypticorn/trade/client/models/tpsl.py +4 -27
  159. crypticorn/trade/client/models/tpsl_create.py +6 -19
  160. crypticorn/trade/client/rest.py +23 -4
  161. crypticorn/trade/main.py +15 -12
  162. {crypticorn-2.15.0.dist-info → crypticorn-2.17.0.dist-info}/METADATA +65 -20
  163. {crypticorn-2.15.0.dist-info → crypticorn-2.17.0.dist-info}/RECORD +167 -132
  164. crypticorn/trade/client/api/futures_trading_panel_api.py +0 -1285
  165. {crypticorn-2.15.0.dist-info → crypticorn-2.17.0.dist-info}/WHEEL +0 -0
  166. {crypticorn-2.15.0.dist-info → crypticorn-2.17.0.dist-info}/entry_points.txt +0 -0
  167. {crypticorn-2.15.0.dist-info → crypticorn-2.17.0.dist-info}/licenses/LICENSE +0 -0
  168. {crypticorn-2.15.0.dist-info → crypticorn-2.17.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,95 @@
1
+ # coding: utf-8
2
+
3
+ """
4
+ Trading API
5
+
6
+ API for automated trading and exchange interface. This API is used to trade on the exchange and manage bots, API keys, orders, and more.
7
+
8
+ The version of the OpenAPI document: 1.0.0
9
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
10
+
11
+ Do not edit the class manually.
12
+ """ # noqa: E501
13
+
14
+
15
+ from __future__ import annotations
16
+ import pprint
17
+ import re # noqa: F401
18
+ import json
19
+
20
+ from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt
21
+ from typing import Any, ClassVar, Dict, List, Union
22
+ from typing import Optional, Set
23
+ from typing_extensions import Self
24
+
25
+
26
+ class PnL(BaseModel):
27
+ """
28
+ The profit and loss of a bot by timestamp. In the case of sampling, the PnL is the sum of the PnLs between the prior timestamp and the current timestamp.
29
+ """ # noqa: E501
30
+
31
+ timestamp: StrictInt = Field(description="Timestamp of the order")
32
+ pnl: Union[StrictFloat, StrictInt] = Field(
33
+ description="The profit and loss of the order"
34
+ )
35
+ cum_pnl: Union[StrictFloat, StrictInt] = Field(
36
+ description="The cumulative profit and loss of the bot until the order (inclusive)"
37
+ )
38
+ __properties: ClassVar[List[str]] = ["timestamp", "pnl", "cum_pnl"]
39
+
40
+ model_config = ConfigDict(
41
+ populate_by_name=True,
42
+ validate_assignment=True,
43
+ protected_namespaces=(),
44
+ )
45
+
46
+ def to_str(self) -> str:
47
+ """Returns the string representation of the model using alias"""
48
+ return pprint.pformat(self.model_dump(by_alias=True))
49
+
50
+ def to_json(self) -> str:
51
+ """Returns the JSON representation of the model using alias"""
52
+ # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
53
+ return json.dumps(self.to_dict())
54
+
55
+ @classmethod
56
+ def from_json(cls, json_str: str) -> Optional[Self]:
57
+ """Create an instance of PnL from a JSON string"""
58
+ return cls.from_dict(json.loads(json_str))
59
+
60
+ def to_dict(self) -> Dict[str, Any]:
61
+ """Return the dictionary representation of the model using alias.
62
+
63
+ This has the following differences from calling pydantic's
64
+ `self.model_dump(by_alias=True)`:
65
+
66
+ * `None` is only added to the output dict for nullable fields that
67
+ were set at model initialization. Other fields with value `None`
68
+ are ignored.
69
+ """
70
+ excluded_fields: Set[str] = set([])
71
+
72
+ _dict = self.model_dump(
73
+ by_alias=True,
74
+ exclude=excluded_fields,
75
+ exclude_none=True,
76
+ )
77
+ return _dict
78
+
79
+ @classmethod
80
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
81
+ """Create an instance of PnL from a dict"""
82
+ if obj is None:
83
+ return None
84
+
85
+ if not isinstance(obj, dict):
86
+ return cls.model_validate(obj)
87
+
88
+ _obj = cls.model_validate(
89
+ {
90
+ "timestamp": obj.get("timestamp"),
91
+ "pnl": obj.get("pnl"),
92
+ "cum_pnl": obj.get("cum_pnl"),
93
+ }
94
+ )
95
+ return _obj
@@ -26,7 +26,7 @@ from typing_extensions import Self
26
26
 
27
27
  class PostFuturesAction(BaseModel):
28
28
  """
29
- PostFuturesAction
29
+ The response when posting a futures action
30
30
  """ # noqa: E501
31
31
 
32
32
  id: StrictStr = Field(description="Action ID.")
@@ -0,0 +1,109 @@
1
+ # coding: utf-8
2
+
3
+ """
4
+ Trading API
5
+
6
+ API for automated trading and exchange interface. This API is used to trade on the exchange and manage bots, API keys, orders, and more.
7
+
8
+ The version of the OpenAPI document: 1.0.0
9
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
10
+
11
+ Do not edit the class manually.
12
+ """ # noqa: E501
13
+
14
+
15
+ from __future__ import annotations
16
+ import pprint
17
+ import re # noqa: F401
18
+ import json
19
+
20
+ from pydantic import BaseModel, ConfigDict, Field, StrictFloat, StrictInt, StrictStr
21
+ from typing import Any, ClassVar, Dict, List, Optional, Union
22
+ from typing import Optional, Set
23
+ from typing_extensions import Self
24
+
25
+
26
+ class SpotBalance(BaseModel):
27
+ """
28
+ Model for spot balance
29
+ """ # noqa: E501
30
+
31
+ asset: StrictStr = Field(description="Asset/Currency code")
32
+ balance: Union[StrictFloat, StrictInt] = Field(description="Balance of the asset")
33
+ available: Union[StrictFloat, StrictInt] = Field(
34
+ description="Available balance for trading/withdrawal"
35
+ )
36
+ frozen: Union[StrictFloat, StrictInt] = Field(
37
+ description="Frozen funds not available for use"
38
+ )
39
+ allocated: Optional[Union[StrictFloat, StrictInt]] = Field(
40
+ default=0, description="Allocated balance for bots. Added on runtime."
41
+ )
42
+ __properties: ClassVar[List[str]] = [
43
+ "asset",
44
+ "balance",
45
+ "available",
46
+ "frozen",
47
+ "allocated",
48
+ ]
49
+
50
+ model_config = ConfigDict(
51
+ populate_by_name=True,
52
+ validate_assignment=True,
53
+ protected_namespaces=(),
54
+ )
55
+
56
+ def to_str(self) -> str:
57
+ """Returns the string representation of the model using alias"""
58
+ return pprint.pformat(self.model_dump(by_alias=True))
59
+
60
+ def to_json(self) -> str:
61
+ """Returns the JSON representation of the model using alias"""
62
+ # TODO: pydantic v2: use .model_dump_json(by_alias=True, exclude_unset=True) instead
63
+ return json.dumps(self.to_dict())
64
+
65
+ @classmethod
66
+ def from_json(cls, json_str: str) -> Optional[Self]:
67
+ """Create an instance of SpotBalance from a JSON string"""
68
+ return cls.from_dict(json.loads(json_str))
69
+
70
+ def to_dict(self) -> Dict[str, Any]:
71
+ """Return the dictionary representation of the model using alias.
72
+
73
+ This has the following differences from calling pydantic's
74
+ `self.model_dump(by_alias=True)`:
75
+
76
+ * `None` is only added to the output dict for nullable fields that
77
+ were set at model initialization. Other fields with value `None`
78
+ are ignored.
79
+ """
80
+ excluded_fields: Set[str] = set([])
81
+
82
+ _dict = self.model_dump(
83
+ by_alias=True,
84
+ exclude=excluded_fields,
85
+ exclude_none=True,
86
+ )
87
+ return _dict
88
+
89
+ @classmethod
90
+ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
91
+ """Create an instance of SpotBalance from a dict"""
92
+ if obj is None:
93
+ return None
94
+
95
+ if not isinstance(obj, dict):
96
+ return cls.model_validate(obj)
97
+
98
+ _obj = cls.model_validate(
99
+ {
100
+ "asset": obj.get("asset"),
101
+ "balance": obj.get("balance"),
102
+ "available": obj.get("available"),
103
+ "frozen": obj.get("frozen"),
104
+ "allocated": (
105
+ obj.get("allocated") if obj.get("allocated") is not None else 0
106
+ ),
107
+ }
108
+ )
109
+ return _obj
@@ -34,7 +34,10 @@ class SpotTradingActionCreate(BaseModel):
34
34
  execution_id: Optional[StrictStr] = None
35
35
  open_order_execution_id: Optional[StrictStr] = None
36
36
  action_type: TradingActionType = Field(description="The type of action.")
37
- market_type: MarketType = Field(description="The type of market the action is for.")
37
+ market_type: Optional[MarketType] = Field(
38
+ default=None,
39
+ description="The type of market the action is for. Must be set to spot.",
40
+ )
38
41
  strategy_id: StrictStr = Field(description="UID for the strategy.")
39
42
  symbol: StrictStr = Field(
40
43
  description="Trading symbol or asset pair in format: 'symbol/quote_currency' (see market service for valid symbols)"
@@ -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: StrictInt = Field(description="Leverage for the strategy")
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, StrictInt, StrictStr
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: StrictInt = Field(description="Leverage for the strategy")
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
- {"exchange": obj.get("exchange"), "min_amount": obj.get("min_amount")}
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,20 +28,14 @@ class TPSL(BaseModel):
28
28
  Runtime fields for take profit and stop loss
29
29
  """ # noqa: E501
30
30
 
31
- price_delta: Optional[StrictStr] = None
32
- price: Optional[StrictStr] = None
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
- client_order_id: Optional[StrictStr] = None
38
- __properties: ClassVar[List[str]] = [
39
- "price_delta",
40
- "price",
41
- "allocation",
42
- "execution_id",
43
- "client_order_id",
44
- ]
38
+ __properties: ClassVar[List[str]] = ["price_delta", "allocation", "execution_id"]
45
39
 
46
40
  model_config = ConfigDict(
47
41
  populate_by_name=True,
@@ -80,26 +74,11 @@ class TPSL(BaseModel):
80
74
  exclude=excluded_fields,
81
75
  exclude_none=True,
82
76
  )
83
- # set to None if price_delta (nullable) is None
84
- # and model_fields_set contains the field
85
- if self.price_delta is None and "price_delta" in self.model_fields_set:
86
- _dict["price_delta"] = None
87
-
88
- # set to None if price (nullable) is None
89
- # and model_fields_set contains the field
90
- if self.price is None and "price" in self.model_fields_set:
91
- _dict["price"] = None
92
-
93
77
  # set to None if execution_id (nullable) is None
94
78
  # and model_fields_set contains the field
95
79
  if self.execution_id is None and "execution_id" in self.model_fields_set:
96
80
  _dict["execution_id"] = None
97
81
 
98
- # set to None if client_order_id (nullable) is None
99
- # and model_fields_set contains the field
100
- if self.client_order_id is None and "client_order_id" in self.model_fields_set:
101
- _dict["client_order_id"] = None
102
-
103
82
  return _dict
104
83
 
105
84
  @classmethod
@@ -114,10 +93,8 @@ class TPSL(BaseModel):
114
93
  _obj = cls.model_validate(
115
94
  {
116
95
  "price_delta": obj.get("price_delta"),
117
- "price": obj.get("price"),
118
96
  "allocation": obj.get("allocation"),
119
97
  "execution_id": obj.get("execution_id"),
120
- "client_order_id": obj.get("client_order_id"),
121
98
  }
122
99
  )
123
100
  return _obj
@@ -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, Optional
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: Optional[StrictStr] = None
32
- price: Optional[StrictStr] = None
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", "price", "allocation"]
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
@@ -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
- # https pool manager
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
- r = await pool_manager.request(**args)
197
-
198
- return RESTResponse(r)
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, config: Configuration, http_client: Optional[ClientSession] = None
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.futures = FuturesTradingPanelApi(self.base_client)
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)