Python-KIS ํ๋ก์ ํธ์ ๊ธฐ์ฌํด ์ฃผ์ ์ ๊ฐ์ฌํฉ๋๋ค! ๐
์ด ๋ฌธ์๋ ํ๋ก์ ํธ์ ๊ธฐ์ฌํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํฉ๋๋ค.
- ๊ฐ๋ฐ ํ๊ฒฝ ์ค์
- ๋ธ๋์น ์ ๋ต
- ์ฝ๋ฉ ๊ท์น
- Pull Request ํ๋ก์ธ์ค
- ํ ์คํธ ์์ฑ ๊ฐ์ด๋
- ๋ฌธ์ํ ๊ฐ์ด๋
- Issue ์์ฑ ๊ฐ์ด๋
- ์ปค๋ฎค๋ํฐ ํ๋ ๊ฐ๋ น
git clone https://github.com/Soju06/python-kis.git
cd python-kisPoetry๊ฐ ์๋ค๋ฉด ๋จผ์ ์ค์น:
# Windows (PowerShell)
(Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | py -
# Linux/macOS
curl -sSL https://install.python-poetry.org | python3 -ํ๋ก์ ํธ ์์กด์ฑ ์ค์น:
poetry install --with=devpoetry shellpoetry run pre-commit install# ์ ์ฒด ํ
์คํธ
poetry run pytest
# ์ปค๋ฒ๋ฆฌ์ง ํฌํจ
poetry run pytest --cov=pykis --cov-report=html
# ํน์ ํ
์คํธ๋ง
poetry run pytest tests/unit/test_public_api_imports.pyfeature/<๊ธฐ๋ฅ๋ช
> # ์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ
fix/<๋ฒ๊ทธ๋ช
> # ๋ฒ๊ทธ ์์
docs/<๋ฌธ์๋ช
> # ๋ฌธ์ ์์
refactor/<๊ฐ์ ๋ช
> # ๋ฆฌํฉํ ๋ง
test/<ํ
์คํธ๋ช
> # ํ
์คํธ ์ถ๊ฐ
chore/<์์
๋ช
> # ๋น๋/์ค์ ๋ณ๊ฒฝ
# ์ ๊ธฐ๋ฅ ์ถ๊ฐ
git checkout -b feature/add-futures-api
# ๋ฒ๊ทธ ์์
git checkout -b fix/websocket-reconnect
# ๋ฌธ์ ๊ฐ์
git checkout -b docs/update-quickstartmain์์ ์ ๋ธ๋์น ์์ฑ- ๋ณ๊ฒฝ์ฌํญ ์ปค๋ฐ
- Push ํ Pull Request ์์ฑ
- ๋ฆฌ๋ทฐ ๋ฐ ํ ์คํธ ํต๊ณผ
main์ ๋ณํฉ
PEP 8 ์ค์๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ํ๋, ํ๋ก์ ํธ ๊ท์น ์ฐ์ :
# โ
๊ถ์ฅ
def get_quote(symbol: str, market: str = "KRX") -> Quote:
"""์์ธ ์ ๋ณด๋ฅผ ์กฐํํฉ๋๋ค.
Args:
symbol: ์ข
๋ชฉ ์ฝ๋ (์: "005930")
market: ์์ฅ ์ฝ๋ (๊ธฐ๋ณธ๊ฐ: "KRX")
Returns:
์์ธ ์ ๋ณด ๊ฐ์ฒด
Raises:
KisAPIError: API ํธ์ถ ์คํจ ์
"""
return self.kis.api(...)
# โ ์ง์
def getQuote(symbol, market="KRX"): # ์นด๋ฉ์ผ์ด์ค, ํ์
ํํธ ์์
return self.kis.api(...)๋ชจ๋ ๊ณต๊ฐ ํจ์/๋ฉ์๋์ ํ์ ํํธ ์ถ๊ฐ:
from typing import Optional, List, Dict, Any
def process_orders(
orders: List[Order],
filter_func: Optional[Callable[[Order], bool]] = None
) -> Dict[str, Any]:
...๋ชจ๋ ๊ณต๊ฐ API์ Google ์คํ์ผ Docstring ์์ฑ:
def buy_stock(self, symbol: str, quantity: int, price: int) -> Order:
"""์ฃผ์ ๋งค์ ์ฃผ๋ฌธ์ ์คํํฉ๋๋ค.
Args:
symbol: ์ข
๋ชฉ ์ฝ๋ (6์๋ฆฌ)
quantity: ์ฃผ๋ฌธ ์๋
price: ์ฃผ๋ฌธ ๊ฐ๊ฒฉ (์)
Returns:
์ฃผ๋ฌธ ์ ๋ณด ๊ฐ์ฒด
Raises:
KisAPIError: ์ฃผ๋ฌธ ์คํจ ์
ValueError: ์๋ชป๋ ํ๋ผ๋ฏธํฐ
Example:
>>> order = kis.stock("005930").buy(qty=10, price=65000)
>>> print(order.order_number)
"""
...| ํ์ | ๊ท์น | ์์ |
|---|---|---|
| ํด๋์ค | PascalCase | KisQuote, PyKis |
| ํจ์/๋ฉ์๋ | snake_case | get_balance(), place_order() |
| ์์ | UPPER_SNAKE_CASE | MAX_RETRY, API_VERSION |
| ๋ด๋ถ ๋ณ์ | snake_case | order_count, balance_info |
| Private | _์ ๋์ฌ |
_internal_method() |
# 1. ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ
import os
import sys
from typing import Optional
# 2. ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
import requests
from websocket import WebSocket
# 3. ๋ก์ปฌ ๋ชจ๋
from pykis.client.auth import KisAuth
from pykis.types import Quote- ๋ชจ๋ ํ
์คํธ ํต๊ณผ (
poetry run pytest) - ํ์ ์ฒดํฌ ํต๊ณผ (IDE์์ ํ์ธ)
- ์๋ก์ด ๊ธฐ๋ฅ์ ํ ์คํธ ์ฝ๋ ํฌํจ
- ๊ณต๊ฐ API๋ Docstring ์์ฑ
- CHANGELOG.md ์ ๋ฐ์ดํธ (์ฃผ์ ๋ณ๊ฒฝ์ฌํญ)
- ์ปค๋ฐ ๋ฉ์์ง ๊ท์น ์ค์
## ๋ณ๊ฒฝ ์ฌํญ
- ์๋ก์ด ๊ธฐ๋ฅ / ๋ฒ๊ทธ ์์ / ๋ฆฌํฉํ ๋ง ์ค๋ช
## ๊ด๋ จ Issue
Closes #123
## ํ
์คํธ
- [ ] ๋จ์ ํ
์คํธ ์ถ๊ฐ/์์
- [ ] ํตํฉ ํ
์คํธ ์ถ๊ฐ/์์
- [ ] ์๋ ํ
์คํธ ์๋ฃ
## ๋ฌธ์
- [ ] README.md ์
๋ฐ์ดํธ (ํ์์)
- [ ] QUICKSTART.md ์
๋ฐ์ดํธ (ํ์์)
- [ ] API ๋ฌธ์ ์
๋ฐ์ดํธ (ํ์์)
## Breaking Changes
- ์๋ค๋ฉด ๋ช
์, ์์ผ๋ฉด "์์"
## ์คํฌ๋ฆฐ์ท (์ ํ)
(์๊ฐ์ ๋ณ๊ฒฝ์ฌํญ์ด ์๋ค๋ฉด ์ฒจ๋ถ)ํ์: <ํ์
>(<๋ฒ์>): <์ ๋ชฉ>
ํ์ :
feat: ์๋ก์ด ๊ธฐ๋ฅfix: ๋ฒ๊ทธ ์์ docs: ๋ฌธ์ ๋ณ๊ฒฝstyle: ์ฝ๋ ํฌ๋งทํ (๊ธฐ๋ฅ ๋ณ๊ฒฝ ์์)refactor: ๋ฆฌํฉํ ๋งtest: ํ ์คํธ ์ถ๊ฐ/์์ chore: ๋น๋/์ค์ ๋ณ๊ฒฝ
์์:
feat(api): add futures trading API
fix(websocket): resolve reconnection issue
docs(quickstart): update config.yaml example
refactor(helpers): simplify load_config logic
test(unit): add tests for load_config with profiles-
์๋ ๊ฒ์ฌ: GitHub Actions CI ์คํ
- ํ ์คํธ ์คํ
- ์ปค๋ฒ๋ฆฌ์ง ์ฒดํฌ (์ต์ 80%)
- ์ฝ๋ ์คํ์ผ ๊ฒ์ฌ
-
๋ฆฌ๋ทฐ์ด ์ง์ : ๋ฉ์ธํ ์ด๋๊ฐ ๋ฆฌ๋ทฐ
-
ํผ๋๋ฐฑ ๋ฐ์: ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ์ ์๋ต ๋ฐ ์์
-
์น์ธ ํ ๋ณํฉ: ๋ฆฌ๋ทฐ์ด๊ฐ ์น์ธํ๋ฉด
main์ ๋ณํฉ
tests/
โโโ unit/ # ๋จ์ ํ
์คํธ (API ํธ์ถ ์์ด)
โ โโโ test_public_api_imports.py
โ โโโ test_simple_helpers.py
โ โโโ test_load_config.py
โ
โโโ integration/ # ํตํฉ ํ
์คํธ (์ค์ API ํธ์ถ)
โ โโโ test_stock_quote.py
โ โโโ test_account_balance.py
โ โโโ test_websocket.py
โ
โโโ fixtures/ # ํ
์คํธ ๋ฐ์ดํฐ
โโโ config_sample.yaml
โโโ mock_responses.json
# tests/unit/test_helpers.py
import pytest
from pykis.helpers import load_config
def test_load_config_single_profile():
"""๋จ์ผ ํ๋กํ ์ค์ ํ์ผ ๋ก๋ ํ
์คํธ"""
cfg = load_config("config.example.virtual.yaml")
assert cfg["id"] == "YOUR_VIRTUAL_ID"
assert cfg["virtual"] is True
def test_load_config_multi_profile_default():
"""๋ค์ค ํ๋กํ ์ค์ ํ์ผ์์ ๊ธฐ๋ณธ ํ๋กํ ๋ก๋"""
cfg = load_config("config.example.yaml")
assert cfg["id"] == "YOUR_VIRTUAL_ID" # default = virtual
def test_load_config_multi_profile_explicit():
"""๋ค์ค ํ๋กํ ์ค์ ํ์ผ์์ ๋ช
์์ ํ๋กํ ์ ํ"""
cfg = load_config("config.example.yaml", profile="real")
assert cfg["id"] == "YOUR_REAL_ID"
assert cfg["virtual"] is False
def test_load_config_profile_not_found():
"""์กด์ฌํ์ง ์๋ ํ๋กํ ์ ํ ์ ์๋ฌ"""
with pytest.raises(ValueError, match="Profile 'unknown' not found"):
load_config("config.example.yaml", profile="unknown")# tests/integration/test_stock_quote.py
import pytest
from pykis import PyKis, KisAuth
@pytest.fixture
def kis_client():
"""์ค์ KIS ํด๋ผ์ด์ธํธ (๋ชจ์ํฌ์)"""
auth = KisAuth(
id=os.environ["KIS_ID"],
account=os.environ["KIS_ACCOUNT"],
appkey=os.environ["KIS_APPKEY"],
secretkey=os.environ["KIS_SECRET"],
virtual=True,
)
return PyKis(auth)
def test_get_quote_samsung(kis_client):
"""์ผ์ฑ์ ์ ์์ธ ์กฐํ"""
quote = kis_client.stock("005930").quote()
assert quote.symbol == "005930"
assert quote.name == "์ผ์ฑ์ ์"
assert quote.price > 0
assert quote.volume >= 0# ์ ์ฒด ํ
์คํธ
poetry run pytest
# ํน์ ํ์ผ๋ง
poetry run pytest tests/unit/test_helpers.py
# ํน์ ํ
์คํธ๋ง
poetry run pytest tests/unit/test_helpers.py::test_load_config_single_profile
# ์ปค๋ฒ๋ฆฌ์ง ํฌํจ
poetry run pytest --cov=pykis --cov-report=htmldocs/
โโโ INDEX.md # ๋ฌธ์ ์ธ๋ฑ์ค
โโโ QUICKSTART.md # ๋น ๋ฅธ ์์ (๋ฃจํธ์๋ ๋ณต์ฌ)
โโโ SIMPLEKIS_GUIDE.md # SimpleKIS ๊ฐ์ด๋
โ
โโโ architecture/ # ์ํคํ
์ฒ ๋ฌธ์
โ โโโ ARCHITECTURE.md
โ
โโโ developer/ # ๊ฐ๋ฐ์ ๊ฐ์ด๋
โ โโโ DEVELOPER_GUIDE.md
โ
โโโ user/ # ์ฌ์ฉ์ ๊ฐ์ด๋
โ โโโ USER_GUIDE.md
โ
โโโ reports/ # ๋ณด๊ณ ์
โโโ ARCHITECTURE_REPORT_V3_KR.md
โโโ CODE_REVIEW.md
๋งํฌ๋ค์ด ์คํ์ผ:
# ์ ๋ชฉ 1 (H1) - ๋ฌธ์ ์ ๋ชฉ์๋ง ์ฌ์ฉ
## ์ ๋ชฉ 2 (H2) - ์ฃผ์ ์น์
### ์ ๋ชฉ 3 (H3) - ํ์ ์น์
#### ์ ๋ชฉ 4 (H4) - ์ธ๋ถ ํญ๋ชฉ
**๊ตต๊ฒ**, *๊ธฐ์ธ์*, `์ธ๋ผ์ธ ์ฝ๋`
- ๋ชฉ๋ก ํญ๋ชฉ 1
- ๋ชฉ๋ก ํญ๋ชฉ 2
1. ์์ ๋ชฉ๋ก 1
2. ์์ ๋ชฉ๋ก 2
[๋งํฌ ํ
์คํธ](URL)
```python
# ์ฝ๋ ๋ธ๋ก
def example():
pass
**์์ ์ฝ๋**:
- ์ค์ ์๋ํ๋ ์ฝ๋ ์์ฑ
- ์ฃผ์์ผ๋ก ์ค๋ช
์ถ๊ฐ
- ๋ฏผ๊ฐ ์ ๋ณด ์ ์ธ (config ์์ ๋ `YOUR_*` ์ฌ์ฉ)
### 3. API ๋ ํผ๋ฐ์ค ์๋ ์์ฑ
```bash
# (ํฅํ ์ถ๊ฐ ์์ )
poetry run sphinx-apidoc -o docs/api pykis
poetry run sphinx-build -b html docs docs/_build
## ๋ฒ๊ทธ ์ค๋ช
(๋ฒ๊ทธ ํ์์ ๋ช
ํํ ์ค๋ช
)
## ์ฌํ ๋ฐฉ๋ฒ
1. ...
2. ...
3. ...
## ์์ ๋์
(์ ์์ ์ผ๋ก ์๋ํ์ ๋์ ๊ฒฐ๊ณผ)
## ์ค์ ๋์
(์ค์ ๋ก ๋ฐ์ํ ํ์)
## ํ๊ฒฝ
- OS: Windows 11 / macOS 14 / Ubuntu 22.04
- Python ๋ฒ์ : 3.11.5
- python-kis ๋ฒ์ : 2.1.7
- ์ค์น ๋ฐฉ๋ฒ: pip / poetry
## ์๋ฌ ๋ก๊ทธ
```python
(์๋ฌ ๋ฉ์์ง ๋๋ ์คํ ํธ๋ ์ด์ค ๋ถ์ฌ๋ฃ๊ธฐ)(์คํฌ๋ฆฐ์ท, ๊ด๋ จ ์ฝ๋ ๋ฑ)
### 2. ๊ธฐ๋ฅ ์ ์
```markdown
## ์ ์ ๋ฐฐ๊ฒฝ
(์ ์ด ๊ธฐ๋ฅ์ด ํ์ํ์ง)
## ์ ์ ๋ด์ฉ
(์ด๋ค ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ณ ์ถ์์ง)
## ์ฌ์ฉ ์์
```python
# ์ ์ํ๋ API ์ฌ์ฉ๋ฒ
result = kis.new_feature(...)
(๋ค๋ฅธ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์๋์ง)
(์ถ๊ฐ ์๊ฒฌ)
---
## ์ปค๋ฎค๋ํฐ ํ๋ ๊ฐ๋ น
### ์ฐ๋ฆฌ์ ์ฝ์
- ๐ค **์กด์ค**: ๋ชจ๋ ๊ธฐ์ฌ์๋ฅผ ์กด์คํฉ๋๋ค
- ๐ **ํฌ์ฉ**: ๋ค์์ฑ์ ํ์ํฉ๋๋ค
- ๐ฌ **๊ฑด์ค์ ํผ๋๋ฐฑ**: ๊ธ์ ์ ์ด๊ณ ๊ฑด์ค์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํฉ๋๋ค
- ๐ **ํ์
**: ํจ๊ป ๋ ๋์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ญ๋๋ค
### ๊ธ์ง ํ๋
- ๐ซ ๊ฐ์ธ ๊ณต๊ฒฉ ๋๋ ๋น๋ฐฉ
- ๐ซ ๊ดด๋กญํ ๋๋ ์ฐจ๋ณ
- ๐ซ ์คํธ ๋๋ ํ๋ณด์ฑ ๊ฒ์๋ฌผ
- ๐ซ ๋ถ์ ์ ํ ์ฝํ
์ธ
### ์๋ฐ ์ ์กฐ์น
๊ฒฝ๊ณ โ ์ผ์ ์ ์ง โ ์๊ตฌ ์ฐจ๋จ
---
## FAQ
### Q1: ์ฝ๋๋ฅผ ์ฒ์ ๊ธฐ์ฌํ๋๋ฐ ์ด๋์๋ถํฐ ์์ํด์ผ ํ๋์?
**A**: [Good First Issue](https://github.com/Soju06/python-kis/labels/good%20first%20issue) ๋ผ๋ฒจ์ด ๋ถ์ ์ด์๋ถํฐ ์์ํ์ธ์.
### Q2: ํ
์คํธ๋ฅผ ์์ฑํ๋ ค๋ฉด ์ค์ API ํค๊ฐ ํ์ํ๊ฐ์?
**A**: ๋จ์ ํ
์คํธ๋ API ํค ์์ด ์์ฑ ๊ฐ๋ฅํฉ๋๋ค. ํตํฉ ํ
์คํธ๋ ๋ชจ์ํฌ์ API ํค๋ฅผ ์ฌ์ฉํ์ธ์.
### Q3: ๋ฌธ์๋ง ์์ ํ๊ณ ์ถ์๋ฐ ๊ฐ๋ฐ ํ๊ฒฝ ์ ์ฒด๋ฅผ ์ค์นํด์ผ ํ๋์?
**A**: ์๋์. GitHub ์น ์ธํฐํ์ด์ค์์ ์ง์ ๋งํฌ๋ค์ด ํ์ผ์ ์์ ํ๊ณ PR์ ์์ฑํ ์ ์์ต๋๋ค.
### Q4: PR์ด ์น์ธ๋๊ธฐ๊น์ง ์ผ๋ง๋ ๊ฑธ๋ฆฌ๋์?
**A**: ์ผ๋ฐ์ ์ผ๋ก 1-3์ผ ๋ด์ ๋ฆฌ๋ทฐ๊ฐ ์งํ๋ฉ๋๋ค. ๋ณต์กํ ๋ณ๊ฒฝ์ฌํญ์ ๋ ์ค๋ ๊ฑธ๋ฆด ์ ์์ต๋๋ค.
### Q5: Breaking Change๋ฅผ ์ ์ํ๊ณ ์ถ์ต๋๋ค.
**A**: Issue๋ฅผ ๋จผ์ ์์ฑํ์ฌ ์ปค๋ฎค๋ํฐ ์๊ฒฌ์ ์๋ ดํ ํ PR์ ์์ฑํ์ธ์.
### Q6: ์ฌ์๋ ๋ฉ์ปค๋์ฆ์ ์ด๋ป๊ฒ ์ฌ์ฉํ๋์?
**A**: 429/5xx ์๋ฌ์ ๋ํ ์๋ ์ฌ์๋๋ฅผ ์ํ๋ฉด ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ์ธ์:
```python
from pykis.utils.retry import with_retry
@with_retry(max_retries=5, initial_delay=2.0)
def fetch_quote(symbol):
return kis.stock(symbol).quote()
A: ํ๋ก๋์ ํ๊ฒฝ์์ ELK/Datadog๊ณผ ์ฐ๋ํ๋ ค๋ฉด:
from pykis.logging import enable_json_logging
enable_json_logging()
# ์ดํ ๋ก๊ทธ๋ JSON ํ์์ผ๋ก ์ถ๋ ฅ๋จA: ์๋ก์ด ์์ธ ํด๋์ค๋ค์ด ์ถ๊ฐ๋์์ต๋๋ค:
from pykis.exceptions import (
KisConnectionError,
KisAuthenticationError,
KisRateLimitError,
KisServerError,
)
try:
quote = kis.stock("005930").quote()
except KisRateLimitError:
# ์๋ ์ ํ - ์ฌ์๋ ๊ฐ๋ฅ
pass
except KisAuthenticationError:
# ์ธ์ฆ ์คํจ - ํน๋ณ ์ฒ๋ฆฌ
pass๊ธฐ์ฌํ ์ฝ๋๋ ํ๋ก์ ํธ์ MIT ๋ผ์ด์ ์ค๋ฅผ ๋ฐ๋ฆ ๋๋ค.
Python-KIS์ ๊ธฐ์ฌํด ์ฃผ์ ๋ชจ๋ ๋ถ๋ค๊ป ๊ฐ์ฌ๋๋ฆฝ๋๋ค! ๐
์ง๋ฌธ์ด ์์ผ์๋ฉด GitHub Discussions ๋๋ Issue๋ฅผ ํตํด ๋ฌธ์ํ์ธ์.