hermetix 0.10.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 (45) hide show
  1. hermetix-0.10.0/LICENSE +21 -0
  2. hermetix-0.10.0/PKG-INFO +168 -0
  3. hermetix-0.10.0/README.md +142 -0
  4. hermetix-0.10.0/hermetix/__init__.py +73 -0
  5. hermetix-0.10.0/hermetix/broker.py +268 -0
  6. hermetix-0.10.0/hermetix/brokers/__init__.py +5 -0
  7. hermetix-0.10.0/hermetix/brokers/db.py +323 -0
  8. hermetix-0.10.0/hermetix/brokers/db_stream.py +305 -0
  9. hermetix-0.10.0/hermetix/brokers/kb.py +287 -0
  10. hermetix-0.10.0/hermetix/brokers/kis.py +377 -0
  11. hermetix-0.10.0/hermetix/brokers/kis_stream.py +362 -0
  12. hermetix-0.10.0/hermetix/brokers/kiwoom.py +298 -0
  13. hermetix-0.10.0/hermetix/brokers/kiwoom_stream.py +298 -0
  14. hermetix-0.10.0/hermetix/brokers/ls.py +283 -0
  15. hermetix-0.10.0/hermetix/brokers/ls_stream.py +355 -0
  16. hermetix-0.10.0/hermetix/brokers/next.py +349 -0
  17. hermetix-0.10.0/hermetix/brokers/nh.py +351 -0
  18. hermetix-0.10.0/hermetix/brokers/nh_stream.py +312 -0
  19. hermetix-0.10.0/hermetix/brokers/toss.py +271 -0
  20. hermetix-0.10.0/hermetix/brokers/toss_stream.py +352 -0
  21. hermetix-0.10.0/hermetix/engine.py +588 -0
  22. hermetix-0.10.0/hermetix/errors.py +49 -0
  23. hermetix-0.10.0/hermetix/models.py +299 -0
  24. hermetix-0.10.0/hermetix/strategy.py +148 -0
  25. hermetix-0.10.0/hermetix/stream.py +248 -0
  26. hermetix-0.10.0/hermetix/testing.py +174 -0
  27. hermetix-0.10.0/hermetix.egg-info/PKG-INFO +168 -0
  28. hermetix-0.10.0/hermetix.egg-info/SOURCES.txt +43 -0
  29. hermetix-0.10.0/hermetix.egg-info/dependency_links.txt +1 -0
  30. hermetix-0.10.0/hermetix.egg-info/requires.txt +9 -0
  31. hermetix-0.10.0/hermetix.egg-info/top_level.txt +1 -0
  32. hermetix-0.10.0/pyproject.toml +36 -0
  33. hermetix-0.10.0/setup.cfg +4 -0
  34. hermetix-0.10.0/tests/test_conformance.py +114 -0
  35. hermetix-0.10.0/tests/test_db_stream.py +152 -0
  36. hermetix-0.10.0/tests/test_engine.py +182 -0
  37. hermetix-0.10.0/tests/test_engine_stream.py +289 -0
  38. hermetix-0.10.0/tests/test_examples.py +151 -0
  39. hermetix-0.10.0/tests/test_kis_order_events.py +70 -0
  40. hermetix-0.10.0/tests/test_ls_stream.py +158 -0
  41. hermetix-0.10.0/tests/test_nh_stream.py +171 -0
  42. hermetix-0.10.0/tests/test_parsing.py +357 -0
  43. hermetix-0.10.0/tests/test_stream.py +326 -0
  44. hermetix-0.10.0/tests/test_stream_fixture.py +154 -0
  45. hermetix-0.10.0/tests/test_toss_stream.py +181 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tauthdev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.4
2
+ Name: hermetix
3
+ Version: 0.10.0
4
+ Summary: 증권사 모의투자 통합 트레이딩 프레임워크 - Python
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/tauthdev/hermetix-trading-core/tree/main/python
7
+ Project-URL: Repository, https://github.com/tauthdev/hermetix-trading-core
8
+ Project-URL: Issues, https://github.com/tauthdev/hermetix-trading-core/issues
9
+ Keywords: trading,paper-trading,stock,bot,korea,broker,kis,kiwoom,websocket
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Office/Business :: Financial :: Investment
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest>=8; extra == "dev"
20
+ Requires-Dist: websockets>=12; extra == "dev"
21
+ Requires-Dist: cryptography>=42; extra == "dev"
22
+ Provides-Extra: stream
23
+ Requires-Dist: websockets>=12; extra == "stream"
24
+ Requires-Dist: cryptography>=42; extra == "stream"
25
+ Dynamic: license-file
26
+
27
+ # Hermetix Python
28
+
29
+ 증권사 모의투자 통합 트레이딩 프레임워크 — Python 구현. **런타임 의존성 0개** (표준 라이브러리만). 실시간 스트림만 선택 의존성 `websockets`(+ KIS 주문 통보 복호화용 `cryptography`)를 씁니다 (`pip install 'hermetix[stream]'`).
30
+
31
+ Kotlin 구현(레퍼런스)과 같은 동작을 보장합니다: 같은 브로커 어댑터 3종, 같은 전략 규약, 같은 안전장치. 금액은 전부 `Decimal` — float 를 섞지 마세요.
32
+
33
+ ## 설치
34
+
35
+ ```bash
36
+ pip install hermetix # PyPI (실시간 스트림까지: pip install 'hermetix[stream]')
37
+ ```
38
+
39
+ ## 빠른 시작
40
+
41
+ ```python
42
+ from decimal import Decimal
43
+ from hermetix import NextClient, Strategy, StrategySpec, StrategyEngine, Buy, CandleInterval
44
+
45
+ class Ma20Strategy(Strategy):
46
+ spec = StrategySpec(name="ma20", symbols=["AAPL"],
47
+ candle_interval=CandleInterval.DAY_1, candle_limit=20)
48
+
49
+ def decide(self, ctx):
50
+ q = ctx.quote("AAPL")
51
+ candles = ctx.candles_of("AAPL")
52
+ if not q or len(candles) < 20 or ctx.has_position("AAPL") or ctx.has_open_order("AAPL"):
53
+ return []
54
+ ma20 = sum(c.close for c in candles) / 20
55
+ if q.price > ma20:
56
+ return [Buy("AAPL", Decimal(1),
57
+ take_profit_price=q.price * Decimal("1.04"), # 익절/손절은
58
+ stop_loss_price=q.price * Decimal("0.98"))] # 엔진이 자동 실행
59
+ return []
60
+
61
+ broker = NextClient(client_id="pk_test_...", client_secret="sk_test_...")
62
+ StrategyEngine(broker, [Ma20Strategy()]).run() # 정규장 중에만 틱, Ctrl+C 로 종료
63
+ ```
64
+
65
+ 브로커 전환은 클라이언트 교체 한 줄:
66
+
67
+ ```python
68
+ broker = KisClient(appkey=..., appsecret=..., cano=...) # 한국투자 모의 (KRX)
69
+ broker = NhClient(app_key=..., app_secret=...) # NH투자증권 NH PLUG (⚠️ 미검증, 문서 기반)
70
+ broker = DbClient(app_key=..., app_secret=...) # DB증권 (⚠️ 미검증, 문서 기반)
71
+ broker = LsClient(app_key=..., app_secret=...) # LS증권 (⚠️ 미검증, 문서 기반)
72
+ broker = TossClient(client_id=..., client_secret=...) # 토스증권 (⚠️ 실전 전용, 미검증) — live_trading_enabled 필수
73
+ broker = KbClient(app_key=..., app_secret=...) # KB증권 오픈베타 (⚠️ 실전 전용, 미검증) — live_trading_enabled 필수
74
+ broker = KiwoomClient(appkey=..., secretkey=...) # 키움 모의 (KRX)
75
+ ```
76
+
77
+ KRX 브로커는 일봉(DAY_1)만 지원합니다(토스는 1m/1d) — 엔진이 기동 시 비호환 전략을 걸러내고 이유를 알려줍니다.
78
+
79
+ ## 실전투자로 전환
80
+
81
+ 모의에서 검증한 뒤 실제 계좌로 옮길 때는 환경과 명시 동의를 함께 지정합니다. 하나라도 빠지면 엔진이 전략을 스케줄하지 않습니다.
82
+
83
+ ```python
84
+ from decimal import Decimal
85
+ from hermetix import KisClient, StrategyEngine, TradingEnvironment
86
+
87
+ broker = KisClient(appkey=..., appsecret=..., cano=..., environment=TradingEnvironment.LIVE) # 호스트·TR ID 자동 전환
88
+ StrategyEngine(
89
+ broker, [MyStrategy()],
90
+ live_trading_enabled=True, # 실전 명시 동의
91
+ max_order_value=Decimal(1_000_000), # 주문 1건 상한 (브로커 통화)
92
+ max_daily_order_value=Decimal(5_000_000), # 하루(UTC) 누적 상한 — 매수·매도 합산
93
+ ).run()
94
+ ```
95
+
96
+ - 넥스트증권은 키 프리픽스가 환경을 결정합니다 (`pk_test_`=모의, `pk_live_`=실전). `environment` 와 어긋나면 생성 시 `ValueError`
97
+ - 심볼에 시장 접두를 붙일 수 있습니다 (`KRX:005930`, `US:AAPL`). 접두 없는 심볼은 브로커 기본 시장으로 해석됩니다
98
+ - 키는 항상 당신의 기기에서만 쓰입니다. Hermetix 는 어떤 서버로도 키를 보내지 않습니다
99
+
100
+ ## 실시간 스트림 (0.8.0)
101
+
102
+ `kis`·`kiwoom` 은 웹소켓 체결가·호가 스트림(2026-09 모의 실측)과 주문 통보 스트림(문서 기반, 실측 전)을 제공합니다. 전략 코드는 그대로 두고 `trigger` 만 바꾸면 체결 틱마다 `decide()` 가 호출됩니다.
103
+
104
+ ```bash
105
+ pip install 'hermetix[stream]' # websockets + cryptography 선택 의존성 — 코어는 여전히 의존성 0
106
+ ```
107
+
108
+ ```python
109
+ from hermetix import KisClient, Strategy, StrategySpec, StrategyEngine, TickTrigger
110
+
111
+ class Scalper(Strategy):
112
+ spec = StrategySpec(name="scalp", symbols=["005930"],
113
+ trigger=TickTrigger.ON_TRADE, # 체결 틱마다 호출
114
+ min_tick_interval_seconds=1.0, # 연속 호출 사이 최소 간격 (캔들·계좌 REST 폭주 방지)
115
+ poll_interval_seconds=60, # 스트림이 끊겼을 때의 안전망 주기
116
+ order_book=True) # 호가창 스트림도 구독 -> ctx.order_book(symbol)
117
+ ...
118
+
119
+ StrategyEngine(KisClient(appkey=..., appsecret=..., cano=...), [Scalper()]).run()
120
+ ```
121
+
122
+ - 몰려온 틱은 하나로 합쳐지고, 스트림이 끊기면 자동 재접속하는 동안 폴링이 계속 돕니다
123
+ - 스트림 틱이 전략의 모든 심볼을 덮으면 `ctx.quote()` 는 REST 대신 마지막 체결 틱(가격·호가·누적거래량)입니다. 캔들·계좌·미체결은 여전히 REST
124
+ - 스트림을 선언하지 않은 브로커(`next` 는 공개 스펙에 웹소켓 없음, 나머지는 미구현)에서 `ON_TRADE` 를 쓰면 경고 후 폴링으로 동작합니다
125
+ - `order_book=True` 전략은 심볼의 10단계 호가창을 `ctx.order_book(symbol)` 로 받습니다 (`best_ask`/`best_bid`/`asks`/`bids`/총잔량). 호가는 틱을 촉발하지 않습니다
126
+ - 브로커가 주문 통보 채널을 제공하면 엔진이 자동 구독해 진입 주문 체결을 서버 조회 없이 브라켓에 반영하고, KIS 모의처럼 주문 조회가 없는 어댑터의 메모리 추적도 즉시 확정합니다. KIS 는 `KisClient(..., hts_id="HTS아이디")` 가 필요하고(통보 프레임은 AES 암호문이라 `cryptography` 사용), 비우면 경고 후 폴링 판정으로 동작합니다
127
+ - 프레임 파서: `hermetix.brokers.kis_stream.parse_kis_frame / parse_kis_order_book / parse_kis_order_events`, `hermetix.brokers.kiwoom_stream.parse_kiwoom_real / parse_kiwoom_order_book / parse_kiwoom_order_events` — 골든 픽스처 `conformance/fixtures/*.json#stream` 으로 검증
128
+ - **nh · db · ls · toss 스트림 (문서 기반, 실측 전)** — `NhClient`/`DbClient`/`LsClient`/`TossClient` 도 `open_stream()` 으로 체결가·호가·주문 통보를 받습니다. 공식 문서·SDK·AsyncAPI 로 만든 구현이라 계좌로 실측하기 전까지는 미검증이며, 파서는 `hermetix.brokers.{nh,db,ls,toss}_stream` 의 `parse_*` 함수와 픽스처 `stream.measured=false` 섹션으로 검증합니다. NH 는 `market_cd` 에 따라 채널(oc/nc/mc)이 갈리고 모의 서버는 시세 채널이 "미제공" 표기, DB 는 접속 후 10초 안에 첫 구독을 보내야 하며, LS 는 KOSPI/KOSDAQ TR 을 종목마다 둘 다 등록하고, 토스는 Bearer 핸드셰이크·선언형 구독·60초 `PING` 을 씁니다. **넥스트·KB 는 웹소켓이 없어**(공개 스펙에 없음) 폴링만 됩니다.
129
+
130
+ ## 수익률
131
+
132
+ ```python
133
+ from hermetix import pnl_report
134
+ print(pnl_report(broker, initial_capital=Decimal(20000)))
135
+ ```
136
+
137
+ ## 구조
138
+
139
+ ```
140
+ hermetix/
141
+ ├── models.py 공통 도메인 모델 (Quote/Candle/Order/... 전부 Decimal)
142
+ ├── errors.py 타입화된 에러 (RateLimit/MarketClosed/... 엔진이 타입별 반응)
143
+ ├── broker.py BrokerClient ABC + MarketStream/StreamingBrokerClient + HTTP/쓰로틀/KRX 캘린더
144
+ ├── stream.py ReconnectingWebSocket (websockets 선택 의존성, 재접속·유휴 감시·직렬 전송)
145
+ ├── brokers/ next.py · kis.py · kiwoom.py · … (방언 정규화는 어댑터 책임), kis_stream.py · kiwoom_stream.py · nh_stream.py · db_stream.py · ls_stream.py · toss_stream.py
146
+ ├── strategy.py Strategy/StrategySpec(TickTrigger·order_book)/StrategyContext(order_book)/Signal(Buy·Sell·Cancel)
147
+ └── engine.py StrategyEngine(ON_TRADE 트리거·호가·주문통보)/브라켓(통보로 활성화)/비상정지/PnL
148
+ ```
149
+
150
+ ## 공식 전략 예제 (examples/)
151
+
152
+ Kotlin 전략 레포 3종과 동일 로직의 실행 가능한 단일 파일:
153
+
154
+ | 파일 | 전략 | 실행 |
155
+ |---|---|---|
156
+ | `examples/larry.py` | 변동성 돌파 | `HERMETIX_BROKER=next NEXT_CLIENT_ID=... python examples/larry.py` |
157
+ | `examples/trend_breakout.py` | WMA 추세선 돌파 | 동일 |
158
+ | `examples/grid.py` | 목표가 스캘핑 (KRX 호환) | `HERMETIX_BROKER=kis KIS_APPKEY=... python examples/grid.py` |
159
+
160
+ ## 테스트
161
+
162
+ ```bash
163
+ pip install -e './python[dev]'
164
+ pytest python/tests/ # 오프라인 (골든 픽스처 재생)
165
+ python python/tests/smoke.py next|kis|kiwoom # 실서버 (환경변수로 키 주입)
166
+ ```
167
+
168
+ 동작 상세(브로커별 제약, 소프트웨어 브라켓, 상태 지도)는 [코어 문서](../docs/architecture.md)와 동일합니다.
@@ -0,0 +1,142 @@
1
+ # Hermetix Python
2
+
3
+ 증권사 모의투자 통합 트레이딩 프레임워크 — Python 구현. **런타임 의존성 0개** (표준 라이브러리만). 실시간 스트림만 선택 의존성 `websockets`(+ KIS 주문 통보 복호화용 `cryptography`)를 씁니다 (`pip install 'hermetix[stream]'`).
4
+
5
+ Kotlin 구현(레퍼런스)과 같은 동작을 보장합니다: 같은 브로커 어댑터 3종, 같은 전략 규약, 같은 안전장치. 금액은 전부 `Decimal` — float 를 섞지 마세요.
6
+
7
+ ## 설치
8
+
9
+ ```bash
10
+ pip install hermetix # PyPI (실시간 스트림까지: pip install 'hermetix[stream]')
11
+ ```
12
+
13
+ ## 빠른 시작
14
+
15
+ ```python
16
+ from decimal import Decimal
17
+ from hermetix import NextClient, Strategy, StrategySpec, StrategyEngine, Buy, CandleInterval
18
+
19
+ class Ma20Strategy(Strategy):
20
+ spec = StrategySpec(name="ma20", symbols=["AAPL"],
21
+ candle_interval=CandleInterval.DAY_1, candle_limit=20)
22
+
23
+ def decide(self, ctx):
24
+ q = ctx.quote("AAPL")
25
+ candles = ctx.candles_of("AAPL")
26
+ if not q or len(candles) < 20 or ctx.has_position("AAPL") or ctx.has_open_order("AAPL"):
27
+ return []
28
+ ma20 = sum(c.close for c in candles) / 20
29
+ if q.price > ma20:
30
+ return [Buy("AAPL", Decimal(1),
31
+ take_profit_price=q.price * Decimal("1.04"), # 익절/손절은
32
+ stop_loss_price=q.price * Decimal("0.98"))] # 엔진이 자동 실행
33
+ return []
34
+
35
+ broker = NextClient(client_id="pk_test_...", client_secret="sk_test_...")
36
+ StrategyEngine(broker, [Ma20Strategy()]).run() # 정규장 중에만 틱, Ctrl+C 로 종료
37
+ ```
38
+
39
+ 브로커 전환은 클라이언트 교체 한 줄:
40
+
41
+ ```python
42
+ broker = KisClient(appkey=..., appsecret=..., cano=...) # 한국투자 모의 (KRX)
43
+ broker = NhClient(app_key=..., app_secret=...) # NH투자증권 NH PLUG (⚠️ 미검증, 문서 기반)
44
+ broker = DbClient(app_key=..., app_secret=...) # DB증권 (⚠️ 미검증, 문서 기반)
45
+ broker = LsClient(app_key=..., app_secret=...) # LS증권 (⚠️ 미검증, 문서 기반)
46
+ broker = TossClient(client_id=..., client_secret=...) # 토스증권 (⚠️ 실전 전용, 미검증) — live_trading_enabled 필수
47
+ broker = KbClient(app_key=..., app_secret=...) # KB증권 오픈베타 (⚠️ 실전 전용, 미검증) — live_trading_enabled 필수
48
+ broker = KiwoomClient(appkey=..., secretkey=...) # 키움 모의 (KRX)
49
+ ```
50
+
51
+ KRX 브로커는 일봉(DAY_1)만 지원합니다(토스는 1m/1d) — 엔진이 기동 시 비호환 전략을 걸러내고 이유를 알려줍니다.
52
+
53
+ ## 실전투자로 전환
54
+
55
+ 모의에서 검증한 뒤 실제 계좌로 옮길 때는 환경과 명시 동의를 함께 지정합니다. 하나라도 빠지면 엔진이 전략을 스케줄하지 않습니다.
56
+
57
+ ```python
58
+ from decimal import Decimal
59
+ from hermetix import KisClient, StrategyEngine, TradingEnvironment
60
+
61
+ broker = KisClient(appkey=..., appsecret=..., cano=..., environment=TradingEnvironment.LIVE) # 호스트·TR ID 자동 전환
62
+ StrategyEngine(
63
+ broker, [MyStrategy()],
64
+ live_trading_enabled=True, # 실전 명시 동의
65
+ max_order_value=Decimal(1_000_000), # 주문 1건 상한 (브로커 통화)
66
+ max_daily_order_value=Decimal(5_000_000), # 하루(UTC) 누적 상한 — 매수·매도 합산
67
+ ).run()
68
+ ```
69
+
70
+ - 넥스트증권은 키 프리픽스가 환경을 결정합니다 (`pk_test_`=모의, `pk_live_`=실전). `environment` 와 어긋나면 생성 시 `ValueError`
71
+ - 심볼에 시장 접두를 붙일 수 있습니다 (`KRX:005930`, `US:AAPL`). 접두 없는 심볼은 브로커 기본 시장으로 해석됩니다
72
+ - 키는 항상 당신의 기기에서만 쓰입니다. Hermetix 는 어떤 서버로도 키를 보내지 않습니다
73
+
74
+ ## 실시간 스트림 (0.8.0)
75
+
76
+ `kis`·`kiwoom` 은 웹소켓 체결가·호가 스트림(2026-09 모의 실측)과 주문 통보 스트림(문서 기반, 실측 전)을 제공합니다. 전략 코드는 그대로 두고 `trigger` 만 바꾸면 체결 틱마다 `decide()` 가 호출됩니다.
77
+
78
+ ```bash
79
+ pip install 'hermetix[stream]' # websockets + cryptography 선택 의존성 — 코어는 여전히 의존성 0
80
+ ```
81
+
82
+ ```python
83
+ from hermetix import KisClient, Strategy, StrategySpec, StrategyEngine, TickTrigger
84
+
85
+ class Scalper(Strategy):
86
+ spec = StrategySpec(name="scalp", symbols=["005930"],
87
+ trigger=TickTrigger.ON_TRADE, # 체결 틱마다 호출
88
+ min_tick_interval_seconds=1.0, # 연속 호출 사이 최소 간격 (캔들·계좌 REST 폭주 방지)
89
+ poll_interval_seconds=60, # 스트림이 끊겼을 때의 안전망 주기
90
+ order_book=True) # 호가창 스트림도 구독 -> ctx.order_book(symbol)
91
+ ...
92
+
93
+ StrategyEngine(KisClient(appkey=..., appsecret=..., cano=...), [Scalper()]).run()
94
+ ```
95
+
96
+ - 몰려온 틱은 하나로 합쳐지고, 스트림이 끊기면 자동 재접속하는 동안 폴링이 계속 돕니다
97
+ - 스트림 틱이 전략의 모든 심볼을 덮으면 `ctx.quote()` 는 REST 대신 마지막 체결 틱(가격·호가·누적거래량)입니다. 캔들·계좌·미체결은 여전히 REST
98
+ - 스트림을 선언하지 않은 브로커(`next` 는 공개 스펙에 웹소켓 없음, 나머지는 미구현)에서 `ON_TRADE` 를 쓰면 경고 후 폴링으로 동작합니다
99
+ - `order_book=True` 전략은 심볼의 10단계 호가창을 `ctx.order_book(symbol)` 로 받습니다 (`best_ask`/`best_bid`/`asks`/`bids`/총잔량). 호가는 틱을 촉발하지 않습니다
100
+ - 브로커가 주문 통보 채널을 제공하면 엔진이 자동 구독해 진입 주문 체결을 서버 조회 없이 브라켓에 반영하고, KIS 모의처럼 주문 조회가 없는 어댑터의 메모리 추적도 즉시 확정합니다. KIS 는 `KisClient(..., hts_id="HTS아이디")` 가 필요하고(통보 프레임은 AES 암호문이라 `cryptography` 사용), 비우면 경고 후 폴링 판정으로 동작합니다
101
+ - 프레임 파서: `hermetix.brokers.kis_stream.parse_kis_frame / parse_kis_order_book / parse_kis_order_events`, `hermetix.brokers.kiwoom_stream.parse_kiwoom_real / parse_kiwoom_order_book / parse_kiwoom_order_events` — 골든 픽스처 `conformance/fixtures/*.json#stream` 으로 검증
102
+ - **nh · db · ls · toss 스트림 (문서 기반, 실측 전)** — `NhClient`/`DbClient`/`LsClient`/`TossClient` 도 `open_stream()` 으로 체결가·호가·주문 통보를 받습니다. 공식 문서·SDK·AsyncAPI 로 만든 구현이라 계좌로 실측하기 전까지는 미검증이며, 파서는 `hermetix.brokers.{nh,db,ls,toss}_stream` 의 `parse_*` 함수와 픽스처 `stream.measured=false` 섹션으로 검증합니다. NH 는 `market_cd` 에 따라 채널(oc/nc/mc)이 갈리고 모의 서버는 시세 채널이 "미제공" 표기, DB 는 접속 후 10초 안에 첫 구독을 보내야 하며, LS 는 KOSPI/KOSDAQ TR 을 종목마다 둘 다 등록하고, 토스는 Bearer 핸드셰이크·선언형 구독·60초 `PING` 을 씁니다. **넥스트·KB 는 웹소켓이 없어**(공개 스펙에 없음) 폴링만 됩니다.
103
+
104
+ ## 수익률
105
+
106
+ ```python
107
+ from hermetix import pnl_report
108
+ print(pnl_report(broker, initial_capital=Decimal(20000)))
109
+ ```
110
+
111
+ ## 구조
112
+
113
+ ```
114
+ hermetix/
115
+ ├── models.py 공통 도메인 모델 (Quote/Candle/Order/... 전부 Decimal)
116
+ ├── errors.py 타입화된 에러 (RateLimit/MarketClosed/... 엔진이 타입별 반응)
117
+ ├── broker.py BrokerClient ABC + MarketStream/StreamingBrokerClient + HTTP/쓰로틀/KRX 캘린더
118
+ ├── stream.py ReconnectingWebSocket (websockets 선택 의존성, 재접속·유휴 감시·직렬 전송)
119
+ ├── brokers/ next.py · kis.py · kiwoom.py · … (방언 정규화는 어댑터 책임), kis_stream.py · kiwoom_stream.py · nh_stream.py · db_stream.py · ls_stream.py · toss_stream.py
120
+ ├── strategy.py Strategy/StrategySpec(TickTrigger·order_book)/StrategyContext(order_book)/Signal(Buy·Sell·Cancel)
121
+ └── engine.py StrategyEngine(ON_TRADE 트리거·호가·주문통보)/브라켓(통보로 활성화)/비상정지/PnL
122
+ ```
123
+
124
+ ## 공식 전략 예제 (examples/)
125
+
126
+ Kotlin 전략 레포 3종과 동일 로직의 실행 가능한 단일 파일:
127
+
128
+ | 파일 | 전략 | 실행 |
129
+ |---|---|---|
130
+ | `examples/larry.py` | 변동성 돌파 | `HERMETIX_BROKER=next NEXT_CLIENT_ID=... python examples/larry.py` |
131
+ | `examples/trend_breakout.py` | WMA 추세선 돌파 | 동일 |
132
+ | `examples/grid.py` | 목표가 스캘핑 (KRX 호환) | `HERMETIX_BROKER=kis KIS_APPKEY=... python examples/grid.py` |
133
+
134
+ ## 테스트
135
+
136
+ ```bash
137
+ pip install -e './python[dev]'
138
+ pytest python/tests/ # 오프라인 (골든 픽스처 재생)
139
+ python python/tests/smoke.py next|kis|kiwoom # 실서버 (환경변수로 키 주입)
140
+ ```
141
+
142
+ 동작 상세(브로커별 제약, 소프트웨어 브라켓, 상태 지도)는 [코어 문서](../docs/architecture.md)와 동일합니다.
@@ -0,0 +1,73 @@
1
+ """Hermetix - 증권사 모의투자 통합 트레이딩 프레임워크 (Python).
2
+
3
+ 빠른 시작:
4
+
5
+ from decimal import Decimal
6
+ from hermetix import NextClient, Strategy, StrategySpec, StrategyEngine, Buy, CandleInterval
7
+
8
+ class MyStrategy(Strategy):
9
+ spec = StrategySpec(name="my-first", symbols=["AAPL"])
10
+
11
+ def decide(self, ctx):
12
+ q = ctx.quote("AAPL")
13
+ if q and not ctx.has_position("AAPL") and not ctx.has_open_order("AAPL"):
14
+ return [Buy("AAPL", Decimal(1),
15
+ take_profit_price=q.price * Decimal("1.04"),
16
+ stop_loss_price=q.price * Decimal("0.98"))]
17
+ return []
18
+
19
+ broker = NextClient(client_id="pk_test_...", client_secret="sk_test_...")
20
+ StrategyEngine(broker, [MyStrategy()]).run()
21
+
22
+ 브로커 전환은 클라이언트 교체 한 줄:
23
+
24
+ broker = KisClient(appkey=..., appsecret=..., cano=...) # 한국투자 모의
25
+ broker = KiwoomClient(appkey=..., secretkey=...) # 키움 모의
26
+ """
27
+ from .broker import (BrokerClient, MarketStream, OrderBookListener, OrderEventListener, RateLimiter, StreamingBrokerClient,
28
+ TradeListener)
29
+ from .testing import ConformanceReport, ConformanceScenario, verify_broker_conformance
30
+ from .brokers.db import DbClient
31
+ from .brokers.kb import KbClient
32
+ from .brokers.kis import KisClient
33
+ from .brokers.ls import LsClient
34
+ from .brokers.nh import NhClient
35
+ from .brokers.toss import TossClient
36
+ from .brokers.kiwoom import KiwoomClient
37
+ from .brokers.next import NextClient
38
+ from .brokers.db_stream import DbMarketStream
39
+ from .brokers.kis_stream import KisMarketStream
40
+ from .brokers.kiwoom_stream import KiwoomMarketStream
41
+ from .brokers.ls_stream import LsMarketStream
42
+ from .brokers.nh_stream import NhMarketStream
43
+ from .brokers.toss_stream import TossMarketStream
44
+ from .engine import BracketMonitor, MarketCalendar, OrderExecutor, RiskGuard, StrategyEngine, TradingGuard, pnl_report
45
+ from .errors import (
46
+ AuthError, BrokerApiError, InsufficientFundsError, InvalidOrderError,
47
+ MarketClosedError, OrderNotFoundError, RateLimitError,
48
+ )
49
+ from .models import (
50
+ Account, BrokerCapabilities, Candle, CandleInterval, CreateOrderRequest,
51
+ Fill, Holding, MarketDay, Order, OrderBookLevel, OrderBookTick, OrderEvent, OrderEventType, OrderSide, OrderStatus,
52
+ OrderType, Quote, StreamChannel, TimeInForce, TradeTick, TradingEnvironment, normalize_order_id, parse_symbol, symbol_code,
53
+ symbols_match,
54
+ )
55
+ from .strategy import Buy, Cancel, Sell, Signal, Strategy, StrategyContext, StrategySpec, TickTrigger
56
+
57
+ __version__ = "0.10.0"
58
+
59
+ __all__ = [
60
+ "BrokerClient", "RateLimiter", "MarketStream", "StreamingBrokerClient", "TradeListener", "NextClient", "KisClient", "KiwoomClient", "NhClient", "DbClient",
61
+ "LsClient", "TossClient", "KbClient",
62
+ "ConformanceReport", "ConformanceScenario", "verify_broker_conformance",
63
+ "StrategyEngine", "TradingGuard", "BracketMonitor", "OrderExecutor", "RiskGuard", "MarketCalendar", "pnl_report",
64
+ "TradingEnvironment", "parse_symbol", "symbol_code", "symbols_match",
65
+ "Strategy", "StrategySpec", "StrategyContext", "Signal", "Buy", "Sell", "Cancel", "TickTrigger",
66
+ "StreamChannel", "TradeTick", "OrderBookLevel", "OrderBookTick", "OrderEvent", "OrderEventType", "normalize_order_id",
67
+ "OrderBookListener", "OrderEventListener",
68
+ "KisMarketStream", "KiwoomMarketStream", "NhMarketStream", "DbMarketStream", "LsMarketStream", "TossMarketStream",
69
+ "Account", "BrokerCapabilities", "Candle", "CandleInterval", "CreateOrderRequest",
70
+ "Fill", "Holding", "MarketDay", "Order", "OrderSide", "OrderStatus", "OrderType", "Quote", "TimeInForce",
71
+ "BrokerApiError", "AuthError", "RateLimitError", "MarketClosedError",
72
+ "InsufficientFundsError", "InvalidOrderError", "OrderNotFoundError",
73
+ ]