crypticorn 2.7.5__py3-none-any.whl → 2.8.0rc1__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/common/__init__.py +4 -1
- crypticorn/common/ansi_colors.py +40 -0
- crypticorn/common/auth.py +1 -1
- crypticorn/common/exceptions.py +20 -16
- crypticorn/common/logging.py +103 -0
- crypticorn/common/router/admin_router.py +96 -0
- crypticorn/common/{status_router.py → router/status_router.py} +0 -20
- crypticorn/common/scopes.py +5 -0
- {crypticorn-2.7.5.dist-info → crypticorn-2.8.0rc1.dist-info}/METADATA +1 -1
- {crypticorn-2.7.5.dist-info → crypticorn-2.8.0rc1.dist-info}/RECORD +13 -10
- {crypticorn-2.7.5.dist-info → crypticorn-2.8.0rc1.dist-info}/WHEEL +0 -0
- {crypticorn-2.7.5.dist-info → crypticorn-2.8.0rc1.dist-info}/entry_points.txt +0 -0
- {crypticorn-2.7.5.dist-info → crypticorn-2.8.0rc1.dist-info}/top_level.txt +0 -0
crypticorn/common/__init__.py
CHANGED
@@ -8,4 +8,7 @@ from crypticorn.common.enums import *
|
|
8
8
|
from crypticorn.common.utils import *
|
9
9
|
from crypticorn.common.exceptions import *
|
10
10
|
from crypticorn.common.pagination import *
|
11
|
-
from crypticorn.common.
|
11
|
+
from crypticorn.common.logging import *
|
12
|
+
from crypticorn.common.ansi_colors import *
|
13
|
+
from crypticorn.common.router.status_router import router as status_router
|
14
|
+
from crypticorn.common.router.admin_router import router as admin_router
|
@@ -0,0 +1,40 @@
|
|
1
|
+
from enum import StrEnum
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
if TYPE_CHECKING:
|
5
|
+
pass
|
6
|
+
|
7
|
+
|
8
|
+
class AnsiColors(StrEnum):
|
9
|
+
# Regular Text Colors
|
10
|
+
BLACK = "\033[30m" # black
|
11
|
+
RED = "\033[31m" # red
|
12
|
+
GREEN = "\033[32m" # green
|
13
|
+
YELLOW = "\033[33m" # yellow
|
14
|
+
BLUE = "\033[34m" # blue
|
15
|
+
MAGENTA = "\033[35m" # magenta
|
16
|
+
CYAN = "\033[36m" # cyan
|
17
|
+
WHITE = "\033[37m" # white
|
18
|
+
|
19
|
+
# Bright Text Colors
|
20
|
+
BLACK_BRIGHT = "\033[90m" # black_bright
|
21
|
+
RED_BRIGHT = "\033[91m" # red_bright
|
22
|
+
GREEN_BRIGHT = "\033[92m" # green_bright
|
23
|
+
YELLOW_BRIGHT = "\033[93m" # yellow_bright
|
24
|
+
BLUE_BRIGHT = "\033[94m" # blue_bright
|
25
|
+
MAGENTA_BRIGHT = "\033[95m" # magenta_bright
|
26
|
+
CYAN_BRIGHT = "\033[96m" # cyan_bright
|
27
|
+
WHITE_BRIGHT = "\033[97m" # white_bright
|
28
|
+
|
29
|
+
# Bold Text Colors
|
30
|
+
BLACK_BOLD = "\033[1;30m" # black_bold
|
31
|
+
RED_BOLD = "\033[1;31m" # red_bold
|
32
|
+
GREEN_BOLD = "\033[1;32m" # green_bold
|
33
|
+
YELLOW_BOLD = "\033[1;33m" # yellow_bold
|
34
|
+
BLUE_BOLD = "\033[1;34m" # blue_bold
|
35
|
+
MAGENTA_BOLD = "\033[1;35m" # magenta_bold
|
36
|
+
CYAN_BOLD = "\033[1;36m" # cyan_bold
|
37
|
+
WHITE_BOLD = "\033[1;37m" # white_bold
|
38
|
+
|
39
|
+
# Reset Color
|
40
|
+
RESET = "\033[0m"
|
crypticorn/common/auth.py
CHANGED
@@ -75,7 +75,7 @@ class AuthHandler:
|
|
75
75
|
raise HTTPException(
|
76
76
|
content=ExceptionContent(
|
77
77
|
error=ApiError.INSUFFICIENT_SCOPES,
|
78
|
-
message="Insufficient scopes to access this resource",
|
78
|
+
message="Insufficient scopes to access this resource (required: " + ", ".join(api_scopes) + ")",
|
79
79
|
),
|
80
80
|
)
|
81
81
|
|
crypticorn/common/exceptions.py
CHANGED
@@ -9,6 +9,7 @@ import logging
|
|
9
9
|
|
10
10
|
logger = logging.getLogger(__name__)
|
11
11
|
|
12
|
+
|
12
13
|
class ExceptionType(StrEnum):
|
13
14
|
HTTP = "http"
|
14
15
|
WEBSOCKET = "websocket"
|
@@ -91,34 +92,37 @@ class WebSocketException(HTTPException):
|
|
91
92
|
)
|
92
93
|
|
93
94
|
|
94
|
-
async def general_handler(request: Request, exc: Exception):
|
95
|
+
async def general_handler(request: Request, exc: Exception) -> JSONResponse:
|
95
96
|
"""This is the default exception handler for all exceptions."""
|
96
|
-
body = ExceptionContent(message=str(exc), error=ApiError.UNKNOWN_ERROR)
|
97
|
-
|
98
|
-
|
99
|
-
status_code=body.status_code, content=HTTPException(content=body).detail
|
97
|
+
body = ExceptionContent(message=str(exc), error=ApiError.UNKNOWN_ERROR)
|
98
|
+
res = JSONResponse(
|
99
|
+
status_code=body.enrich().status_code, content=HTTPException(content=body).detail
|
100
100
|
)
|
101
|
+
logger.error(f"Response validation error: {res}")
|
102
|
+
return res
|
101
103
|
|
102
104
|
|
103
|
-
async def request_validation_handler(request: Request, exc: RequestValidationError):
|
105
|
+
async def request_validation_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
|
104
106
|
"""This is the exception handler for all request validation errors."""
|
105
|
-
body = ExceptionContent(message=str(exc), error=ApiError.INVALID_DATA_REQUEST)
|
106
|
-
|
107
|
-
|
108
|
-
status_code=body.status_code, content=HTTPException(content=body).detail
|
107
|
+
body = ExceptionContent(message=str(exc), error=ApiError.INVALID_DATA_REQUEST)
|
108
|
+
res = JSONResponse(
|
109
|
+
status_code=body.enrich().status_code, content=HTTPException(content=body).detail
|
109
110
|
)
|
111
|
+
logger.error(f"Response validation error: {res}")
|
112
|
+
return res
|
110
113
|
|
111
114
|
|
112
|
-
async def response_validation_handler(request: Request, exc: ResponseValidationError):
|
115
|
+
async def response_validation_handler(request: Request, exc: ResponseValidationError) -> JSONResponse:
|
113
116
|
"""This is the exception handler for all response validation errors."""
|
114
|
-
body = ExceptionContent(message=str(exc), error=ApiError.INVALID_DATA_RESPONSE)
|
115
|
-
|
116
|
-
|
117
|
-
status_code=body.status_code, content=HTTPException(content=body).detail
|
117
|
+
body = ExceptionContent(message=str(exc), error=ApiError.INVALID_DATA_RESPONSE)
|
118
|
+
res = JSONResponse(
|
119
|
+
status_code=body.enrich().status_code, content=HTTPException(content=body).detail
|
118
120
|
)
|
121
|
+
logger.error(f"Response validation error: {res}")
|
122
|
+
return res
|
119
123
|
|
120
124
|
|
121
|
-
async def http_handler(request: Request, exc: HTTPException):
|
125
|
+
async def http_handler(request: Request, exc: HTTPException) -> JSONResponse:
|
122
126
|
"""This is the exception handler for HTTPExceptions. It unwraps the HTTPException and returns the detail in a flat JSON response."""
|
123
127
|
logger.error(f"HTTP error: {exc.detail}")
|
124
128
|
return JSONResponse(status_code=exc.status_code, content=exc.detail)
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# shared_logger.py
|
2
|
+
import logging
|
3
|
+
from logging import _nameToLevel, _levelToName
|
4
|
+
import sys
|
5
|
+
from contextvars import ContextVar
|
6
|
+
import json
|
7
|
+
from pydantic import BaseModel
|
8
|
+
from enum import StrEnum
|
9
|
+
from crypticorn.common.mixins import ValidateEnumMixin
|
10
|
+
from crypticorn.common.ansi_colors import AnsiColors as C
|
11
|
+
from datetime import datetime
|
12
|
+
|
13
|
+
class LogLevel(ValidateEnumMixin, StrEnum):
|
14
|
+
DEBUG = "DEBUG"
|
15
|
+
INFO = "INFO"
|
16
|
+
WARNING = "WARNING"
|
17
|
+
ERROR = "ERROR"
|
18
|
+
CRITICAL = "CRITICAL"
|
19
|
+
|
20
|
+
@classmethod
|
21
|
+
def get_color(cls, level: str) -> str:
|
22
|
+
if level == cls.DEBUG:
|
23
|
+
return C.GREEN_BRIGHT
|
24
|
+
elif level == cls.INFO:
|
25
|
+
return C.BLUE_BRIGHT
|
26
|
+
elif level == cls.WARNING:
|
27
|
+
return C.YELLOW_BRIGHT
|
28
|
+
elif level == cls.ERROR:
|
29
|
+
return C.RED_BRIGHT
|
30
|
+
elif level == cls.CRITICAL:
|
31
|
+
return C.RED_BOLD
|
32
|
+
else:
|
33
|
+
return C.RESET
|
34
|
+
|
35
|
+
@staticmethod
|
36
|
+
def get_level(level: "LogLevel") -> int:
|
37
|
+
return _nameToLevel.get(level, logging.INFO)
|
38
|
+
|
39
|
+
@staticmethod
|
40
|
+
def get_name(level: int) -> "LogLevel":
|
41
|
+
return LogLevel(_levelToName.get(level, "INFO"))
|
42
|
+
|
43
|
+
|
44
|
+
_LOGFORMAT = (
|
45
|
+
f"{C.CYAN_BOLD}%(asctime)s{C.RESET} - "
|
46
|
+
f"{C.GREEN_BOLD}%(name)s{C.RESET} - "
|
47
|
+
f"%(levelcolor)s%(levelname)s{C.RESET} - "
|
48
|
+
f"%(message)s"
|
49
|
+
)
|
50
|
+
_PLAIN_LOGFORMAT = (
|
51
|
+
"%(asctime)s - " "%(name)s - " "%(levelname)s - " "%(message)s"
|
52
|
+
)
|
53
|
+
_DATEFMT = "%Y-%m-%d %H:%M:%S.%f:"
|
54
|
+
|
55
|
+
|
56
|
+
class CustomFormatter(logging.Formatter):
|
57
|
+
def __init__(self, *args, **kwargs):
|
58
|
+
super().__init__(*args, **kwargs)
|
59
|
+
|
60
|
+
def format(self, record):
|
61
|
+
color = LogLevel.get_color(record.levelname)
|
62
|
+
record.levelcolor = color
|
63
|
+
return super().format(record)
|
64
|
+
|
65
|
+
def formatTime(self, record, datefmt=_DATEFMT):
|
66
|
+
dt = datetime.fromtimestamp(record.created)
|
67
|
+
s = dt.strftime(datefmt)
|
68
|
+
return s[:-3] # Trim last 3 digits to get milliseconds
|
69
|
+
|
70
|
+
def get_logger(
|
71
|
+
name: str, fmt: str = _LOGFORMAT, datefmt: str = _DATEFMT,
|
72
|
+
stdout_level: int = logging.INFO,
|
73
|
+
file_level: int = logging.INFO,
|
74
|
+
log_file: str = None,
|
75
|
+
filters: list[logging.Filter] = [],
|
76
|
+
) -> logging.Logger:
|
77
|
+
"""Returns crypticorn logger instance."""
|
78
|
+
logger = logging.getLogger(name)
|
79
|
+
|
80
|
+
if logger.handlers: # clear existing handlers to avoid duplicates
|
81
|
+
logger.handlers.clear()
|
82
|
+
|
83
|
+
logger.setLevel(min(stdout_level, file_level)) # set to most verbose level
|
84
|
+
|
85
|
+
# Configure stdout handler
|
86
|
+
stdout_handler = logging.StreamHandler(sys.stdout)
|
87
|
+
stdout_handler.setLevel(stdout_level)
|
88
|
+
stdout_handler.setFormatter(CustomFormatter(fmt=fmt, datefmt=datefmt))
|
89
|
+
for filter in filters:
|
90
|
+
stdout_handler.addFilter(filter)
|
91
|
+
logger.addHandler(stdout_handler)
|
92
|
+
|
93
|
+
# Configure file handler
|
94
|
+
if log_file:
|
95
|
+
os.makedirs(os.path.dirname(log_file), exist_ok=True)
|
96
|
+
file_handler = logging.RotatingFileHandler(log_file, maxBytes=10*1024*1024, backupCount=5)
|
97
|
+
file_handler.setLevel(file_level)
|
98
|
+
file_handler.setFormatter(CustomFormatter(fmt=fmt, datefmt=datefmt))
|
99
|
+
for filter in filters:
|
100
|
+
file_handler.addFilter(filter)
|
101
|
+
logger.addHandler(file_handler)
|
102
|
+
|
103
|
+
return logger
|
@@ -0,0 +1,96 @@
|
|
1
|
+
"""
|
2
|
+
This module contains the admin router for the API.
|
3
|
+
It provides endpoints for monitoring the server and getting information about the environment.
|
4
|
+
ONLY ALLOW ACCESS TO THIS ROUTER WITH ADMIN SCOPES.
|
5
|
+
>>> app.include_router(admin_router, dependencies=[Security(auth_handler.combined_auth, scopes=[Scope.WRITE_LOGS])])
|
6
|
+
"""
|
7
|
+
|
8
|
+
import os
|
9
|
+
import sys
|
10
|
+
import pkg_resources
|
11
|
+
import threading
|
12
|
+
import time
|
13
|
+
import json
|
14
|
+
import psutil
|
15
|
+
from fastapi import APIRouter, HTTPException, Body, Query
|
16
|
+
from typing import Literal, Union
|
17
|
+
from dotenv import dotenv_values
|
18
|
+
from crypticorn.common.logging import LogLevel
|
19
|
+
import logging
|
20
|
+
|
21
|
+
router = APIRouter(tags=["Admin"], prefix="/admin")
|
22
|
+
|
23
|
+
START_TIME = time.time()
|
24
|
+
|
25
|
+
@router.get("/log-level", status_code=200, operation_id="getLogLevel")
|
26
|
+
async def get_logging_level() -> LogLevel:
|
27
|
+
"""
|
28
|
+
Get the log level of the server logger.
|
29
|
+
"""
|
30
|
+
return LogLevel.get_name(logging.getLogger().level)
|
31
|
+
|
32
|
+
@router.get("/uptime", operation_id="getUptime", status_code=200)
|
33
|
+
def get_uptime(
|
34
|
+
type: Literal["seconds", "human"] = "seconds"
|
35
|
+
) -> Union[int, str]:
|
36
|
+
"""Return the server uptime in seconds or human-readable form."""
|
37
|
+
uptime_seconds = int(time.time() - START_TIME)
|
38
|
+
if type == "seconds":
|
39
|
+
return uptime_seconds
|
40
|
+
elif type == "human":
|
41
|
+
return time.strftime("%H:%M:%S", time.gmtime(uptime_seconds))
|
42
|
+
|
43
|
+
|
44
|
+
@router.get("/memory", operation_id="getMemoryUsage", status_code=200)
|
45
|
+
def get_memory_usage() -> int:
|
46
|
+
"""
|
47
|
+
Resident Set Size (RSS) in MB — the actual memory used by the process in RAM.
|
48
|
+
Represents the physical memory footprint. Important for monitoring real usage.
|
49
|
+
"""
|
50
|
+
process = psutil.Process(os.getpid())
|
51
|
+
mem_info = process.memory_info()
|
52
|
+
return round(mem_info.rss / (1024 * 1024), 2)
|
53
|
+
|
54
|
+
|
55
|
+
@router.get("/threads", operation_id="getThreads", status_code=200)
|
56
|
+
def get_threads() -> dict:
|
57
|
+
"""Return count and names of active threads."""
|
58
|
+
threads = threading.enumerate()
|
59
|
+
return {
|
60
|
+
"count": len(threads),
|
61
|
+
"threads": [t.name for t in threads],
|
62
|
+
}
|
63
|
+
|
64
|
+
|
65
|
+
@router.get("/limits", operation_id="getContainerLimits", status_code=200)
|
66
|
+
def get_container_limits() -> dict:
|
67
|
+
"""Return container resource limits from cgroup."""
|
68
|
+
limits = {}
|
69
|
+
try:
|
70
|
+
with open("/sys/fs/cgroup/memory/memory.limit_in_bytes") as f:
|
71
|
+
limits["memory_limit_MB"] = int(f.read().strip()) / 1024 / 1024
|
72
|
+
except Exception:
|
73
|
+
limits["memory_limit_MB"] = "N/A"
|
74
|
+
|
75
|
+
try:
|
76
|
+
with open("/sys/fs/cgroup/cpu/cpu.cfs_quota_us") as f1, open("/sys/fs/cgroup/cpu/cpu.cfs_period_us") as f2:
|
77
|
+
quota = int(f1.read().strip())
|
78
|
+
period = int(f2.read().strip())
|
79
|
+
limits["cpu_limit_cores"] = quota / period if quota > 0 else "N/A"
|
80
|
+
except Exception:
|
81
|
+
limits["cpu_limit_cores"] = "N/A"
|
82
|
+
|
83
|
+
return limits
|
84
|
+
|
85
|
+
|
86
|
+
@router.get("/dependencies", operation_id="getDependencies", status_code=200)
|
87
|
+
def list_installed_packages(
|
88
|
+
include: list[str] = Query(
|
89
|
+
default=None, description="List of dependencies to include in the response. If not provided, all installed packages will be returned."
|
90
|
+
)
|
91
|
+
) -> list:
|
92
|
+
"""Return a list of installed packages and versions."""
|
93
|
+
return sorted(
|
94
|
+
[{dist.project_name: dist.version} for dist in pkg_resources.working_set if include is None or dist.project_name in include],
|
95
|
+
key=lambda x: next(iter(x)),
|
96
|
+
)
|
@@ -22,23 +22,3 @@ async def time(type: Literal["iso", "unix"] = "iso") -> str:
|
|
22
22
|
return datetime.now().isoformat()
|
23
23
|
else:
|
24
24
|
return str(int(datetime.now().timestamp()))
|
25
|
-
|
26
|
-
|
27
|
-
@router.get("/config", operation_id="getConfig")
|
28
|
-
async def config() -> dict:
|
29
|
-
"""
|
30
|
-
Returns the version of the crypticorn library and the environment.
|
31
|
-
"""
|
32
|
-
import importlib.metadata
|
33
|
-
import os
|
34
|
-
from dotenv import load_dotenv
|
35
|
-
|
36
|
-
load_dotenv()
|
37
|
-
try:
|
38
|
-
crypticorn_version = importlib.metadata.version("crypticorn")
|
39
|
-
except importlib.metadata.PackageNotFoundError:
|
40
|
-
crypticorn_version = "not installed"
|
41
|
-
return {
|
42
|
-
"crypticorn": f"v{crypticorn_version}",
|
43
|
-
"environment": os.getenv("API_ENV", "not set"),
|
44
|
-
}
|
crypticorn/common/scopes.py
CHANGED
@@ -8,6 +8,9 @@ class Scope(StrEnum):
|
|
8
8
|
|
9
9
|
# If you update anything here, also update the scopes in the auth-service repository
|
10
10
|
|
11
|
+
WRITE_ADMIN = "write:admin"
|
12
|
+
READ_ADMIN = "read:admin"
|
13
|
+
|
11
14
|
# Scopes that can be purchased - these actually exist in the jwt token
|
12
15
|
READ_PREDICTIONS = "read:predictions"
|
13
16
|
|
@@ -55,6 +58,8 @@ class Scope(StrEnum):
|
|
55
58
|
return [
|
56
59
|
cls.WRITE_TRADE_STRATEGIES,
|
57
60
|
cls.WRITE_PAY_PRODUCTS,
|
61
|
+
cls.WRITE_ADMIN,
|
62
|
+
cls.READ_ADMIN,
|
58
63
|
]
|
59
64
|
|
60
65
|
@classmethod
|
@@ -63,18 +63,21 @@ crypticorn/cli/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
63
63
|
crypticorn/cli/templates/auth.py,sha256=Q1TxlA7qzhjvrqp1xz1aV2vGnj3DKFNN-VSl3o0B-dI,983
|
64
64
|
crypticorn/cli/templates/dependabot.yml,sha256=ct5ieB8KAV1KLzoYKUNm6dZ9wKG_P_JQHgRjZUfT54w,861
|
65
65
|
crypticorn/cli/templates/ruff.yml,sha256=gWicFFTzC4nToSmRkIIGipos8CZ447YG0kebBCJhtJE,319
|
66
|
-
crypticorn/common/__init__.py,sha256=
|
67
|
-
crypticorn/common/
|
66
|
+
crypticorn/common/__init__.py,sha256=FkOmpnrSPtSyz5CoWWqIEslEF601vb_3H46ZUwwEtAQ,628
|
67
|
+
crypticorn/common/ansi_colors.py,sha256=WqyRMlu3tJj5e-c3hEpa0dupNVZhzRmQcxJgRWSyJ_k,1202
|
68
|
+
crypticorn/common/auth.py,sha256=6oJ26t0N2Msup-okC2iFhpHz9ZeaGnQZ5zaCODIsN_k,8673
|
68
69
|
crypticorn/common/decorators.py,sha256=pmnGYCIrLv59wZkDbvPyK9NJmgPJWW74LXTdIWSjOkY,1063
|
69
70
|
crypticorn/common/enums.py,sha256=RitDVqlG_HTe6tHT6bWusZNFCeYk1eQvJVH-7x3_Zlg,668
|
70
71
|
crypticorn/common/errors.py,sha256=8jxZ2lLn_NoFKKq6n2JwKPsR0dA2vkGnbXDfEK6ndH0,27851
|
71
|
-
crypticorn/common/exceptions.py,sha256=
|
72
|
+
crypticorn/common/exceptions.py,sha256=tOg8WjJXTtDcqZjokwk4QD5cQtXq5-2gAbvbHENJTng,6050
|
73
|
+
crypticorn/common/logging.py,sha256=ncy4E9ogL7HPPgvcHS4zMiXBF5kyOrNcxHFHYJ7IyMw,3220
|
72
74
|
crypticorn/common/mixins.py,sha256=LKPcNTR8uREeDGWTlWozNx7rS1mYdQVx1RllLhxIAsE,1640
|
73
75
|
crypticorn/common/pagination.py,sha256=c07jrMNrBaNTmgx4sppdP7ND4RNT7NBqBXWvofazIlE,2251
|
74
|
-
crypticorn/common/scopes.py,sha256=
|
75
|
-
crypticorn/common/status_router.py,sha256=s7LY3aNQPhtDUgNWHRszfCQMl0Uh13li_jR8jeeolnw,1139
|
76
|
+
crypticorn/common/scopes.py,sha256=ofJ5FDf30wab572XvDzAXVKBIUWa3shScAmzNrJsWqQ,2453
|
76
77
|
crypticorn/common/urls.py,sha256=3Gf1NU1XQYcOTjcdztG3bDAE98FVbgTK2QXzUe7tFVQ,878
|
77
78
|
crypticorn/common/utils.py,sha256=Kz2-I96MKIGKM18PHQ77VbKHLMGUvZG_jjj7xpQed8k,2138
|
79
|
+
crypticorn/common/router/admin_router.py,sha256=aaAqWAnaZ2CkHU9cJgeZKyUoc6JnC6oEQhUa_P0bWKw,3380
|
80
|
+
crypticorn/common/router/status_router.py,sha256=RIgyNVVnD6Y5Qsfz_oJk0BDuPPmCBM_B4ytcRiSXvo8,576
|
78
81
|
crypticorn/hive/__init__.py,sha256=hRfTlEzEql4msytdUC_04vfaHzVKG5CGZle1M-9QFgY,81
|
79
82
|
crypticorn/hive/main.py,sha256=4oQ2RybZMbe0kRxVJrVAABsN5kUTCMExQFJDSnAzBUY,2428
|
80
83
|
crypticorn/hive/utils.py,sha256=dxQ_OszrnTsslO5hDefMmgfj6yRwRPr8sr17fGizWIw,2066
|
@@ -224,8 +227,8 @@ crypticorn/trade/client/models/strategy_model_input.py,sha256=ala19jARyfA5ysys5D
|
|
224
227
|
crypticorn/trade/client/models/strategy_model_output.py,sha256=2o2lhbgUSTznowpMLEHF1Ex9TG9oRmzlCIb-gXqo7_s,5643
|
225
228
|
crypticorn/trade/client/models/tpsl.py,sha256=C2KgTIZs-a8W4msdaXgBKJcwtA-o5wR4rBauRP-iQxU,4317
|
226
229
|
crypticorn/trade/client/models/trading_action_type.py,sha256=pGq_TFLMPfYFizYP-xKgEC1ZF4U3lGdJYoGa_ZH2x-Q,769
|
227
|
-
crypticorn-2.
|
228
|
-
crypticorn-2.
|
229
|
-
crypticorn-2.
|
230
|
-
crypticorn-2.
|
231
|
-
crypticorn-2.
|
230
|
+
crypticorn-2.8.0rc1.dist-info/METADATA,sha256=HdPQkiN3MMSTb4TS9IJQrhfN758XihFO2IG1OBJtrMg,6610
|
231
|
+
crypticorn-2.8.0rc1.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
|
232
|
+
crypticorn-2.8.0rc1.dist-info/entry_points.txt,sha256=d_xHsGvUTebPveVUK0SrpDFQ5ZRSjlI7lNCc11sn2PM,59
|
233
|
+
crypticorn-2.8.0rc1.dist-info/top_level.txt,sha256=EP3NY216qIBYfmvGl0L2Zc9ItP0DjGSkiYqd9xJwGcM,11
|
234
|
+
crypticorn-2.8.0rc1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|