cluefin-openapi 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. cluefin_openapi-0.1.0/.gitignore +84 -0
  2. cluefin_openapi-0.1.0/PKG-INFO +65 -0
  3. cluefin_openapi-0.1.0/README.md +53 -0
  4. cluefin_openapi-0.1.0/pyproject.toml +28 -0
  5. cluefin_openapi-0.1.0/src/cluefin_openapi/__init__.py +2 -0
  6. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/__init__.py +31 -0
  7. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_auth.py +96 -0
  8. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_auth_types.py +18 -0
  9. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_cache.py +130 -0
  10. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_client.py +386 -0
  11. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_account.py +1129 -0
  12. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_account_types.py +937 -0
  13. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_chart.py +587 -0
  14. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_chart_types.py +1072 -0
  15. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_credit_order.py +35 -0
  16. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_etf.py +322 -0
  17. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_etf_types.py +188 -0
  18. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_foreign.py +131 -0
  19. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_foreign_types.py +70 -0
  20. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_market_condition.py +909 -0
  21. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_market_condition_types.py +672 -0
  22. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_order.py +262 -0
  23. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_order_types.py +38 -0
  24. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_rank_info.py +1495 -0
  25. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_rank_info_types.py +554 -0
  26. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_realtime.py +22 -0
  27. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_sector.py +244 -0
  28. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_sector_types.py +192 -0
  29. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_stock_info.py +1280 -0
  30. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_stock_info_types.py +752 -0
  31. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_theme.py +101 -0
  32. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_domestic_theme_types.py +45 -0
  33. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_exceptions.py +77 -0
  34. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_model.py +28 -0
  35. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/_rate_limiter.py +90 -0
  36. cluefin_openapi-0.1.0/src/cluefin_openapi/kiwoom/py.typed +0 -0
  37. cluefin_openapi-0.1.0/tests/integration/kiwoom/test_auth.py +25 -0
  38. cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_account.py +342 -0
  39. cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_chart.py +183 -0
  40. cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_etf.py +107 -0
  41. cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_foreign.py +67 -0
  42. cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_market_condition.py +242 -0
  43. cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_order.py +65 -0
  44. cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_rank_info.py +351 -0
  45. cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_sector.py +218 -0
  46. cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_stock_info.py +510 -0
  47. cluefin_openapi-0.1.0/tests/integration/kiwoom/test_domestic_theme.py +52 -0
  48. cluefin_openapi-0.1.0/tests/unit/kiwoom/test_auth.py +70 -0
  49. cluefin_openapi-0.1.0/tests/unit/kiwoom/test_client_improvements.py +278 -0
  50. cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_account.py +1413 -0
  51. cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_chart.py +831 -0
  52. cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_etf.py +448 -0
  53. cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_foreign.py +163 -0
  54. cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_market_condition.py +1193 -0
  55. cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_order.py +107 -0
  56. cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_rank_info.py +1148 -0
  57. cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_sector.py +493 -0
  58. cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_stock_info.py +1730 -0
  59. cluefin_openapi-0.1.0/tests/unit/kiwoom/test_domestic_theme.py +131 -0
@@ -0,0 +1,84 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.so
5
+ *.pyd
6
+
7
+ # C extensions
8
+ *.c
9
+ *.cpp
10
+
11
+ # Distribution / packaging
12
+ build/
13
+ dist/
14
+ wheels/
15
+ *.egg-info/
16
+ *.egg
17
+ .eggs/
18
+ MANIFEST
19
+ *.manifest
20
+ *.spec
21
+
22
+ # Installer logs
23
+ pip-log.txt
24
+ pip-delete-this-directory.txt
25
+
26
+ # Unit test / coverage reports
27
+ htmlcov/
28
+ .tox/
29
+ .nox/
30
+ .coverage
31
+ .coverage.*
32
+ .cache
33
+ nosetests.xml
34
+ coverage.xml
35
+ *.cover
36
+ *.py,cover
37
+ .hypothesis/
38
+ .pytest_cache/
39
+ test-results/
40
+
41
+ # Jupyter Notebook
42
+ .ipynb_checkpoints
43
+
44
+ # pyenv
45
+ .python-version
46
+
47
+ # mypy
48
+ .mypy_cache/
49
+ .dmypy.json
50
+ dmypy.json
51
+
52
+ # Pyre type checker
53
+ .pyre/
54
+
55
+ # VS Code
56
+ .vscode/
57
+ .code-workspace
58
+
59
+ # PyCharm
60
+ .idea/
61
+ *.iml
62
+
63
+ # macOS
64
+ .DS_Store
65
+
66
+ # Virtual environments
67
+ .venv/
68
+ venv/
69
+ ENV/
70
+ env/
71
+ env.bak/
72
+ venv.bak/
73
+
74
+ # Lock files
75
+ Pipfile.lock
76
+ poetry.lock
77
+ uv.lock
78
+
79
+ # Project-specific
80
+ packages/*/src/*/__pycache__/
81
+ packages/*/src/*/*.py[cod]
82
+
83
+ # Environment variables
84
+ .env.*
@@ -0,0 +1,65 @@
1
+ Metadata-Version: 2.4
2
+ Name: cluefin-openapi
3
+ Version: 0.1.0
4
+ Summary: OpenAPI Client for Cluefin
5
+ Author-email: Hangoo Kang <kgcrom@hotmail.com>
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: loguru>=0.7.3
8
+ Requires-Dist: pydantic>=2.11.7
9
+ Requires-Dist: requests>=2.32.4
10
+ Provides-Extra: kiwoom
11
+ Description-Content-Type: text/markdown
12
+
13
+ # cluefin-openapi
14
+
15
+ > **cluefin-openapi**: A Python client for Kiwoom Securities investment REST API.
16
+
17
+ ---
18
+
19
+ ## Features
20
+ - Account information retrieval
21
+ - Domestic/foreign stock info
22
+ - Chart data and analytics
23
+ - ETF, sector, theme, and market condition support
24
+ - Order management and real-time updates
25
+
26
+ ## Quickstart
27
+ ```bash
28
+ $> pip install cluefin-openapi
29
+ or
30
+ $> pip install cluefin-openapi[kiwoom]
31
+ ```
32
+
33
+ ```python
34
+ from cluefin_openapi.kiwoom._auth import Auth
35
+ from cluefin_openapi.kiwoom._client import Client
36
+
37
+ auth = Auth(
38
+ app_key=os.getenv("APP_KEY"),
39
+ secret_key=os.getenv("SECRET_KEY"),
40
+ env="dev",
41
+ )
42
+
43
+ token = auth.generate_token()
44
+ client = Client(token=token.token, env="dev")
45
+
46
+ response = client.account.get_daily_stock_realized_profit_loss_by_date("005930", "20250630")
47
+ print(response.headers)
48
+ print(response.body)
49
+ ```
50
+
51
+ ## Why cluefin-openapi?
52
+ Easily access investment data and trading features from Kiwoom Securities, DART, KRX, and more—all through a unified Python interface.
53
+ Save time integrating with multiple financial APIs and focus on building your investment tools.
54
+
55
+ ## Getting Started
56
+ 1. Install via pip
57
+ 2. Obtain your Kiwoom REST API credentials
58
+ 3. See [examples](./test/integration/kiwoom/) for more usage
59
+
60
+ ## Contributing
61
+ See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines.
62
+
63
+ ---
64
+
65
+ *Invest responsibly. This project is not affiliated with Kiwoom Securities.*
@@ -0,0 +1,53 @@
1
+ # cluefin-openapi
2
+
3
+ > **cluefin-openapi**: A Python client for Kiwoom Securities investment REST API.
4
+
5
+ ---
6
+
7
+ ## Features
8
+ - Account information retrieval
9
+ - Domestic/foreign stock info
10
+ - Chart data and analytics
11
+ - ETF, sector, theme, and market condition support
12
+ - Order management and real-time updates
13
+
14
+ ## Quickstart
15
+ ```bash
16
+ $> pip install cluefin-openapi
17
+ or
18
+ $> pip install cluefin-openapi[kiwoom]
19
+ ```
20
+
21
+ ```python
22
+ from cluefin_openapi.kiwoom._auth import Auth
23
+ from cluefin_openapi.kiwoom._client import Client
24
+
25
+ auth = Auth(
26
+ app_key=os.getenv("APP_KEY"),
27
+ secret_key=os.getenv("SECRET_KEY"),
28
+ env="dev",
29
+ )
30
+
31
+ token = auth.generate_token()
32
+ client = Client(token=token.token, env="dev")
33
+
34
+ response = client.account.get_daily_stock_realized_profit_loss_by_date("005930", "20250630")
35
+ print(response.headers)
36
+ print(response.body)
37
+ ```
38
+
39
+ ## Why cluefin-openapi?
40
+ Easily access investment data and trading features from Kiwoom Securities, DART, KRX, and more—all through a unified Python interface.
41
+ Save time integrating with multiple financial APIs and focus on building your investment tools.
42
+
43
+ ## Getting Started
44
+ 1. Install via pip
45
+ 2. Obtain your Kiwoom REST API credentials
46
+ 3. See [examples](./test/integration/kiwoom/) for more usage
47
+
48
+ ## Contributing
49
+ See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines.
50
+
51
+ ---
52
+
53
+ *Invest responsibly. This project is not affiliated with Kiwoom Securities.*
@@ -0,0 +1,28 @@
1
+ [project]
2
+ name = "cluefin-openapi"
3
+ version = "0.1.0"
4
+ description = "OpenAPI Client for Cluefin"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Hangoo Kang", email = "kgcrom@hotmail.com" }
8
+ ]
9
+ requires-python = ">=3.10"
10
+ dependencies = [
11
+ "loguru>=0.7.3",
12
+ "pydantic>=2.11.7",
13
+ "requests>=2.32.4",
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ kiwoom = []
18
+
19
+ [build-system]
20
+ requires = ["hatchling"]
21
+ build-backend = "hatchling.build"
22
+
23
+ [tool.hatch.build.targets.wheel]
24
+ packages = ["src/cluefin_openapi"]
25
+
26
+ [tool.ruff]
27
+ extend = "../../pyproject.toml"
28
+ include = ["src/**", "tests/**"]
@@ -0,0 +1,2 @@
1
+ def main() -> None:
2
+ print("Hello from cluefin-openapi!")
@@ -0,0 +1,31 @@
1
+ """Cluefin Kiwoom API Client Package."""
2
+
3
+ from ._auth import Auth
4
+ from ._client import Client
5
+ from ._exceptions import (
6
+ KiwoomAPIError,
7
+ KiwoomAuthenticationError,
8
+ KiwoomAuthorizationError,
9
+ KiwoomNetworkError,
10
+ KiwoomRateLimitError,
11
+ KiwoomServerError,
12
+ KiwoomTimeoutError,
13
+ KiwoomValidationError,
14
+ )
15
+
16
+ __all__ = [
17
+ "Auth",
18
+ "Client",
19
+ "KiwoomAPIError",
20
+ "KiwoomAuthenticationError",
21
+ "KiwoomAuthorizationError",
22
+ "KiwoomNetworkError",
23
+ "KiwoomRateLimitError",
24
+ "KiwoomServerError",
25
+ "KiwoomTimeoutError",
26
+ "KiwoomValidationError",
27
+ ]
28
+
29
+
30
+ def hello() -> str:
31
+ return "Hello from cluefin-kiwoom!"
@@ -0,0 +1,96 @@
1
+ """Authentication module for Kiwoom API.
2
+
3
+ This module provides functionality for generating and revoking API tokens
4
+ using client credentials authentication flow.
5
+ """
6
+
7
+ from typing import Literal
8
+
9
+ import requests
10
+
11
+ from ._auth_types import TokenResponse
12
+
13
+
14
+ class Auth:
15
+ """Initialize the Auth client.
16
+
17
+ Args:
18
+ app_key: The application key provided by Kiwoom.
19
+ secret_key: The secret key provided by Kiwoom.
20
+ env: The environment to use. Either "dev" or "prod".
21
+ Defaults to "dev".
22
+
23
+ Raises:
24
+ ValueError: If an invalid environment is provided.
25
+ """
26
+
27
+ def __init__(self, app_key: str, secret_key: str, env: Literal["dev", "prod"] = "dev") -> None:
28
+ self.app_key = app_key
29
+ self.secret_key = secret_key
30
+
31
+ if env == "dev":
32
+ self.url = "https://mockapi.kiwoom.com"
33
+ elif env == "prod":
34
+ self.url = "https://api.kiwoom.com"
35
+ else:
36
+ raise ValueError("Invalid environment. Must be either 'dev' or 'prod'.")
37
+
38
+ @property
39
+ def token(self) -> TokenResponse:
40
+ """Get the current token data.
41
+
42
+ Returns:
43
+ TokenResponse: The current token data.
44
+
45
+ Raises:
46
+ AttributeError: If no token has been generated yet.
47
+ """
48
+ return self._token_data
49
+
50
+ def generate_token(self) -> TokenResponse:
51
+ """Generate a new access token.
52
+
53
+ Calls the Kiwoom OAuth2 token endpoint to generate a new access token
54
+ using the client credentials flow.
55
+
56
+ Returns:
57
+ TokenResponse: The generated token data including access token
58
+ and expiration.
59
+
60
+ Raises:
61
+ requests.exceptions.HTTPError: If the API request fails.
62
+ """
63
+ headers = {
64
+ "Content-Type": "application/json;charset=UTF-8",
65
+ }
66
+ data = {"grant_type": "client_credentials", "appkey": self.app_key, "secretkey": self.secret_key}
67
+
68
+ response = requests.post(f"{self.url}/oauth2/token", headers=headers, json=data)
69
+ response.raise_for_status()
70
+
71
+ token_data = TokenResponse(**response.json())
72
+ self._token_data = token_data
73
+
74
+ return self._token_data
75
+
76
+ def revoke_token(self, token: str) -> bool:
77
+ """Revoke an access token.
78
+
79
+ Args:
80
+ token: The token to revoke.
81
+ Returns:
82
+ bool: True if the token was successfully revoked.
83
+
84
+ Raises:
85
+ requests.exceptions.HTTPError: If the API request fails.
86
+ """
87
+ headers = {
88
+ "Content-Type": "application/json;charset=UTF-8",
89
+ }
90
+
91
+ data = {"appkey": self.app_key, "secretkey": self.secret_key, "token": token}
92
+
93
+ response = requests.post(f"{self.url}/oauth2/revoke", headers=headers, json=data)
94
+ response.raise_for_status()
95
+
96
+ return True
@@ -0,0 +1,18 @@
1
+ from datetime import datetime
2
+
3
+ from pydantic import BaseModel, field_validator
4
+
5
+
6
+ class TokenResponse(BaseModel):
7
+ expires_dt: datetime
8
+ token_type: str
9
+ token: str
10
+
11
+ @field_validator("expires_dt", mode="before")
12
+ def parse_expires_dt(cls, v):
13
+ if isinstance(v, str):
14
+ try:
15
+ return datetime.strptime(v, "%Y%m%d%H%M%S")
16
+ except ValueError:
17
+ pass # 다른 형식도 추가 가능
18
+ return v
@@ -0,0 +1,130 @@
1
+ """Caching utilities for Kiwoom API client."""
2
+
3
+ import hashlib
4
+ import time
5
+ from typing import Any, Dict, Optional
6
+
7
+
8
+ class SimpleCache:
9
+ """Simple in-memory cache with TTL support."""
10
+
11
+ def __init__(self, default_ttl: int = 300):
12
+ """
13
+ Initialize cache.
14
+
15
+ Args:
16
+ default_ttl: Default time-to-live in seconds (default: 5 minutes)
17
+ """
18
+ self.default_ttl = default_ttl
19
+ self._cache: Dict[str, Dict[str, Any]] = {}
20
+
21
+ def get(self, key: str) -> Optional[Any]:
22
+ """
23
+ Get value from cache.
24
+
25
+ Args:
26
+ key: Cache key
27
+
28
+ Returns:
29
+ Cached value or None if not found/expired
30
+ """
31
+ if key in self._cache:
32
+ entry = self._cache[key]
33
+ if time.time() < entry["expires_at"]:
34
+ return entry["value"]
35
+ else:
36
+ # Remove expired entry
37
+ del self._cache[key]
38
+ return None
39
+
40
+ def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
41
+ """
42
+ Set value in cache.
43
+
44
+ Args:
45
+ key: Cache key
46
+ value: Value to cache
47
+ ttl: Time-to-live in seconds (uses default if None)
48
+ """
49
+ if ttl is None:
50
+ ttl = self.default_ttl
51
+
52
+ self._cache[key] = {
53
+ "value": value,
54
+ "expires_at": time.time() + ttl,
55
+ "created_at": time.time(),
56
+ }
57
+
58
+ def delete(self, key: str) -> bool:
59
+ """
60
+ Delete key from cache.
61
+
62
+ Args:
63
+ key: Cache key
64
+
65
+ Returns:
66
+ True if key was deleted, False if not found
67
+ """
68
+ if key in self._cache:
69
+ del self._cache[key]
70
+ return True
71
+ return False
72
+
73
+ def clear(self) -> None:
74
+ """Clear all cached entries."""
75
+ self._cache.clear()
76
+
77
+ def cleanup_expired(self) -> int:
78
+ """
79
+ Remove expired entries from cache.
80
+
81
+ Returns:
82
+ Number of entries removed
83
+ """
84
+ current_time = time.time()
85
+ expired_keys = [key for key, entry in self._cache.items() if current_time >= entry["expires_at"]]
86
+
87
+ for key in expired_keys:
88
+ del self._cache[key]
89
+
90
+ return len(expired_keys)
91
+
92
+ def cache_info(self) -> Dict[str, Any]:
93
+ """Get cache statistics."""
94
+ current_time = time.time()
95
+ valid_entries = sum(1 for entry in self._cache.values() if current_time < entry["expires_at"])
96
+
97
+ return {
98
+ "total_entries": len(self._cache),
99
+ "valid_entries": valid_entries,
100
+ "expired_entries": len(self._cache) - valid_entries,
101
+ }
102
+
103
+
104
+ def create_cache_key(url: str, headers: Dict[str, str], body: Dict[str, Any]) -> str:
105
+ """
106
+ Create a cache key from request parameters.
107
+
108
+ Args:
109
+ url: Request URL
110
+ headers: Request headers (excluding auth and dynamic headers)
111
+ body: Request body
112
+
113
+ Returns:
114
+ Cache key string
115
+ """
116
+ # Filter out dynamic headers that shouldn't affect caching
117
+ cacheable_headers = {
118
+ k: v for k, v in headers.items() if k.lower() not in ["authorization", "user-agent", "content-length"]
119
+ }
120
+
121
+ # Create a deterministic string representation
122
+ cache_data = {
123
+ "url": url,
124
+ "headers": sorted(cacheable_headers.items()),
125
+ "body": sorted(body.items()) if isinstance(body, dict) else body,
126
+ }
127
+
128
+ # Create hash of the cache data
129
+ cache_string = str(cache_data)
130
+ return hashlib.sha256(cache_string.encode()).hexdigest()[:16] # First 16 chars for brevity