programgarden-core 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.
- programgarden_core-0.1.0/PKG-INFO +16 -0
- programgarden_core-0.1.0/README.md +0 -0
- programgarden_core-0.1.0/programgarden_core/__init__.py +76 -0
- programgarden_core-0.1.0/programgarden_core/bases/__init__.py +61 -0
- programgarden_core-0.1.0/programgarden_core/bases/community.py +324 -0
- programgarden_core-0.1.0/programgarden_core/bases/system.py +273 -0
- programgarden_core-0.1.0/programgarden_core/exceptions.py +135 -0
- programgarden_core-0.1.0/programgarden_core/korea_alias.py +81 -0
- programgarden_core-0.1.0/programgarden_core/logs.py +88 -0
- programgarden_core-0.1.0/pyproject.toml +21 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: programgarden-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 자동화매매 클래스 상속, 로그, 환경 등의 핵심이되는 오픈소스
|
|
5
|
+
Author: 프로그램동산
|
|
6
|
+
Author-email: coding@programgarden.com
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
|
|
File without changes
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
핵심 기능 모듈
|
|
3
|
+
|
|
4
|
+
LS OpenAPI 클라이언트의 핵심 기능을 제공합니다.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .bases import (
|
|
8
|
+
SystemType, StrategyConditionType,
|
|
9
|
+
StrategyType, SystemSettingType,
|
|
10
|
+
DictConditionType,
|
|
11
|
+
SecuritiesAccountType,
|
|
12
|
+
|
|
13
|
+
ExecutionTimingType,
|
|
14
|
+
BaseCondition,
|
|
15
|
+
|
|
16
|
+
BaseBuyOverseasStock,
|
|
17
|
+
BaseSellOverseasStock,
|
|
18
|
+
|
|
19
|
+
BaseConditionResponseType,
|
|
20
|
+
BaseBuyOverseasStockResponseType,
|
|
21
|
+
BaseSellOverseasStockResponseType,
|
|
22
|
+
|
|
23
|
+
SymbolInfo,
|
|
24
|
+
HeldSymbol,
|
|
25
|
+
NonTradedSymbol,
|
|
26
|
+
|
|
27
|
+
NewBuyTradeType,
|
|
28
|
+
NewSellTradeType,
|
|
29
|
+
OrdersType,
|
|
30
|
+
OrderTimeType,
|
|
31
|
+
OrderCategoryType,
|
|
32
|
+
)
|
|
33
|
+
from .korea_alias import EnforceKoreanAliasMeta, require_korean_alias
|
|
34
|
+
from . import logs, exceptions
|
|
35
|
+
from .logs import pg_log_disable, pg_log_reset, pg_logger, pg_log
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
logs,
|
|
40
|
+
exceptions,
|
|
41
|
+
|
|
42
|
+
pg_logger,
|
|
43
|
+
pg_log,
|
|
44
|
+
pg_log_disable,
|
|
45
|
+
pg_log_reset,
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
require_korean_alias,
|
|
49
|
+
EnforceKoreanAliasMeta,
|
|
50
|
+
|
|
51
|
+
SecuritiesAccountType,
|
|
52
|
+
StrategyConditionType,
|
|
53
|
+
StrategyType,
|
|
54
|
+
DictConditionType,
|
|
55
|
+
SystemSettingType,
|
|
56
|
+
ExecutionTimingType,
|
|
57
|
+
SystemType,
|
|
58
|
+
|
|
59
|
+
BaseCondition,
|
|
60
|
+
BaseBuyOverseasStock,
|
|
61
|
+
BaseSellOverseasStock,
|
|
62
|
+
|
|
63
|
+
BaseConditionResponseType,
|
|
64
|
+
BaseBuyOverseasStockResponseType,
|
|
65
|
+
BaseSellOverseasStockResponseType,
|
|
66
|
+
|
|
67
|
+
SymbolInfo,
|
|
68
|
+
HeldSymbol,
|
|
69
|
+
NonTradedSymbol,
|
|
70
|
+
|
|
71
|
+
NewBuyTradeType,
|
|
72
|
+
NewSellTradeType,
|
|
73
|
+
OrdersType,
|
|
74
|
+
OrderTimeType,
|
|
75
|
+
OrderCategoryType,
|
|
76
|
+
]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from .system import (
|
|
2
|
+
SystemType,
|
|
3
|
+
SystemSettingType,
|
|
4
|
+
|
|
5
|
+
StrategyType,
|
|
6
|
+
SecuritiesAccountType,
|
|
7
|
+
StrategyConditionType,
|
|
8
|
+
DictConditionType,
|
|
9
|
+
|
|
10
|
+
ExecutionTimingType,
|
|
11
|
+
|
|
12
|
+
NewBuyTradeType,
|
|
13
|
+
NewSellTradeType,
|
|
14
|
+
OrdersType,
|
|
15
|
+
OrderTimeType,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .community import (
|
|
19
|
+
BaseCondition,
|
|
20
|
+
BaseBuyOverseasStock,
|
|
21
|
+
BaseSellOverseasStock,
|
|
22
|
+
|
|
23
|
+
BaseConditionResponseType,
|
|
24
|
+
BaseBuyOverseasStockResponseType,
|
|
25
|
+
BaseSellOverseasStockResponseType,
|
|
26
|
+
|
|
27
|
+
SymbolInfo,
|
|
28
|
+
HeldSymbol,
|
|
29
|
+
NonTradedSymbol,
|
|
30
|
+
OrderCategoryType,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
SystemType,
|
|
36
|
+
StrategyType,
|
|
37
|
+
|
|
38
|
+
BaseCondition,
|
|
39
|
+
BaseBuyOverseasStock,
|
|
40
|
+
BaseSellOverseasStock,
|
|
41
|
+
|
|
42
|
+
SecuritiesAccountType,
|
|
43
|
+
StrategyConditionType,
|
|
44
|
+
DictConditionType,
|
|
45
|
+
BaseConditionResponseType,
|
|
46
|
+
BaseBuyOverseasStockResponseType,
|
|
47
|
+
BaseSellOverseasStockResponseType,
|
|
48
|
+
|
|
49
|
+
SystemSettingType,
|
|
50
|
+
ExecutionTimingType,
|
|
51
|
+
|
|
52
|
+
SymbolInfo,
|
|
53
|
+
HeldSymbol,
|
|
54
|
+
NonTradedSymbol,
|
|
55
|
+
|
|
56
|
+
NewBuyTradeType,
|
|
57
|
+
NewSellTradeType,
|
|
58
|
+
OrderTimeType,
|
|
59
|
+
OrdersType,
|
|
60
|
+
OrderCategoryType,
|
|
61
|
+
]
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import List, Literal, Optional, TypedDict, Any, Dict, TypeVar, Generic
|
|
3
|
+
|
|
4
|
+
OrderCategoryType = Literal["submitted_new_buy", "submitted_new_sell",
|
|
5
|
+
"filled_new_buy", "filled_new_sell",
|
|
6
|
+
"cancel_request_buy", "cancel_request_sell",
|
|
7
|
+
"modify_buy", "modify_sell", "cancel_complete_buy", "cancel_complete_sell",
|
|
8
|
+
"reject_buy", "reject_sell"]
|
|
9
|
+
"""
|
|
10
|
+
- submitted_new_buy: 신규 매수 접수
|
|
11
|
+
- submitted_new_sell: 신규 매도 접수
|
|
12
|
+
- filled_new_buy: 신규 매수 체결
|
|
13
|
+
- filled_new_sell: 신규 매도 체결
|
|
14
|
+
- cancel_request_buy: 매수 취소 접수
|
|
15
|
+
- cancel_request_sell: 매도 취소 접수
|
|
16
|
+
- modify_buy: 매수 정정 접수
|
|
17
|
+
- modify_sell: 매도 정정 접수
|
|
18
|
+
- cancel_complete_buy: 매수 취소 완료
|
|
19
|
+
- cancel_complete_sell: 매도 취소 완료
|
|
20
|
+
- reject_buy: 매수 주문 거부
|
|
21
|
+
- reject_sell: 매도 주문 거부
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SymbolInfo(TypedDict):
|
|
26
|
+
"""
|
|
27
|
+
종목 정보를 담기 위한 타입
|
|
28
|
+
|
|
29
|
+
Args
|
|
30
|
+
----------
|
|
31
|
+
symbol: str
|
|
32
|
+
종목 코드
|
|
33
|
+
exchcd: Literal["81", "82"]
|
|
34
|
+
거래소 코드
|
|
35
|
+
"""
|
|
36
|
+
symbol: str
|
|
37
|
+
"""종목 코드"""
|
|
38
|
+
exchcd: Literal["81", "82"]
|
|
39
|
+
"""
|
|
40
|
+
거래소 코드
|
|
41
|
+
82: NASDAQ
|
|
42
|
+
81: 뉴욕증권거래소
|
|
43
|
+
"""
|
|
44
|
+
mcap: Optional[float] = None
|
|
45
|
+
"""시가총액 (단위: 백만 달러)"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class HeldSymbol(TypedDict):
|
|
49
|
+
"""
|
|
50
|
+
주문해서 보유중인 종목들
|
|
51
|
+
"""
|
|
52
|
+
CrcyCode: str
|
|
53
|
+
"""통화코드"""
|
|
54
|
+
ShtnIsuNo: str
|
|
55
|
+
"""단축종목번호"""
|
|
56
|
+
AstkBalQty: float
|
|
57
|
+
"""해외증권잔고수량"""
|
|
58
|
+
AstkSellAbleQty: float
|
|
59
|
+
"""해외증권매도가능수량"""
|
|
60
|
+
PnlRat: float
|
|
61
|
+
"""손익율"""
|
|
62
|
+
BaseXchrat: float
|
|
63
|
+
"""기준환율"""
|
|
64
|
+
PchsAmt: float
|
|
65
|
+
"""매입금액"""
|
|
66
|
+
FcurrMktCode: str
|
|
67
|
+
"""외화시장코드"""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class NonTradedSymbol(TypedDict):
|
|
71
|
+
"""
|
|
72
|
+
미체결 종목
|
|
73
|
+
"""
|
|
74
|
+
OrdTime: str
|
|
75
|
+
"""주문시각"""
|
|
76
|
+
OrdNo: int
|
|
77
|
+
"""주문번호"""
|
|
78
|
+
OrgOrdNo: int
|
|
79
|
+
"""원주문번호"""
|
|
80
|
+
ShtnIsuNo: str
|
|
81
|
+
"""단축종목번호"""
|
|
82
|
+
MrcAbleQty: int
|
|
83
|
+
"""정정취소가능수량"""
|
|
84
|
+
OrdQty: int
|
|
85
|
+
"""주문수량"""
|
|
86
|
+
OvrsOrdPrc: float
|
|
87
|
+
"""해외주문가"""
|
|
88
|
+
OrdprcPtnCode: str
|
|
89
|
+
"""호가유형코드"""
|
|
90
|
+
OrdPtnCode: str
|
|
91
|
+
"""주문유형코드"""
|
|
92
|
+
MrcTpCode: str
|
|
93
|
+
"""정정취소구분코드"""
|
|
94
|
+
OrdMktCode: str
|
|
95
|
+
"""주문시장코드"""
|
|
96
|
+
UnercQty: int
|
|
97
|
+
"""미체결수량"""
|
|
98
|
+
CnfQty: int
|
|
99
|
+
"""확인수량"""
|
|
100
|
+
CrcyCode: str
|
|
101
|
+
"""통화코드"""
|
|
102
|
+
RegMktCode: str
|
|
103
|
+
"""등록시장코드"""
|
|
104
|
+
IsuNo: str
|
|
105
|
+
"""종목번호"""
|
|
106
|
+
BnsTpCode: str
|
|
107
|
+
"""매매구분코드"""
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class BaseCondition(ABC):
|
|
111
|
+
"""
|
|
112
|
+
기본 전략의 조건 타입을 정의하는 추상 클래스입니다.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
id: str
|
|
116
|
+
"""전략의 고유 ID"""
|
|
117
|
+
description: str
|
|
118
|
+
"""전략에 대한 설명"""
|
|
119
|
+
securities: List[str]
|
|
120
|
+
"""사용 가능한 증권사/거래소들"""
|
|
121
|
+
|
|
122
|
+
@abstractmethod
|
|
123
|
+
def __init__(self, **kwargs):
|
|
124
|
+
self.symbol: Optional[SymbolInfo] = None
|
|
125
|
+
|
|
126
|
+
@abstractmethod
|
|
127
|
+
async def execute(self) -> 'BaseConditionResponseType':
|
|
128
|
+
"""
|
|
129
|
+
전략을 실행하는 메서드입니다.
|
|
130
|
+
구체적인 전략 클래스에서 구현해야 합니다.
|
|
131
|
+
"""
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
def _set_system_id(self, system_id: Optional[str]) -> None:
|
|
135
|
+
"""
|
|
136
|
+
시스템 고유 ID를 설정합니다.
|
|
137
|
+
"""
|
|
138
|
+
self.system_id = system_id
|
|
139
|
+
|
|
140
|
+
def _set_symbol(self, symbol: SymbolInfo) -> None:
|
|
141
|
+
"""
|
|
142
|
+
계산할 종목들을 선정합니다.
|
|
143
|
+
선정된 종목들 위주로 조건 충족 여부를 확인해서 반환해줍니다.
|
|
144
|
+
"""
|
|
145
|
+
self.symbol = symbol
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class BaseConditionResponseType(TypedDict):
|
|
149
|
+
"""기본 응답 데이터"""
|
|
150
|
+
|
|
151
|
+
condition_id: Optional[str]
|
|
152
|
+
"""조건 ID"""
|
|
153
|
+
success: bool
|
|
154
|
+
"""조건 통과한 종목이 1개라도 있으면 True로 처리합니다."""
|
|
155
|
+
symbol: str
|
|
156
|
+
"""종목 코드"""
|
|
157
|
+
exchcd: str
|
|
158
|
+
"""거래소 코드"""
|
|
159
|
+
data: Any
|
|
160
|
+
"""조건 통과한 종목에 대한 추가 데이터"""
|
|
161
|
+
weight: Optional[int] = 0
|
|
162
|
+
"""조건의 가중치는 0과 1사이의 값, 기본값은 0"""
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class BaseBuyOverseasStockResponseType(TypedDict):
|
|
166
|
+
"""주문을 넣기 위한 반환값 데이터"""
|
|
167
|
+
success: bool
|
|
168
|
+
"""전략 통과 성공 여부"""
|
|
169
|
+
ord_ptn_code: Literal["02"] = "02"
|
|
170
|
+
"""주문유형코드 (02: 매수주문)"""
|
|
171
|
+
ord_mkt_code: Literal["81", "82"]
|
|
172
|
+
"""주문시장코드 (81: 뉴욕거래소, 82: NASDAQ)"""
|
|
173
|
+
isu_no: str
|
|
174
|
+
"""종목번호 (단축종목코드 ex.TSLA)"""
|
|
175
|
+
ord_qty: int
|
|
176
|
+
"""주문수량"""
|
|
177
|
+
ovrs_ord_prc: float
|
|
178
|
+
"""해외주문가"""
|
|
179
|
+
ordprc_ptn_code: Literal["00", "M1", "M2"]
|
|
180
|
+
"""호가유형코드 (00: 지정가, M1: LOO, M2: LOC)"""
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class BaseSellOverseasStockResponseType(TypedDict):
|
|
184
|
+
"""주문을 넣기 위한 반환값 데이터"""
|
|
185
|
+
success: bool
|
|
186
|
+
"""전략 통과 성공 여부"""
|
|
187
|
+
ord_ptn_code: Literal["01"] = "01"
|
|
188
|
+
"""주문유형코드 (01: 매도주문)"""
|
|
189
|
+
ord_mkt_code: Literal["81", "82"]
|
|
190
|
+
"""주문시장코드 (81: 뉴욕거래소, 82: NASDAQ)"""
|
|
191
|
+
shtn_isu_no: str
|
|
192
|
+
"""종목번호 (단축종목코드 ex.TSLA)"""
|
|
193
|
+
ord_qty: int
|
|
194
|
+
"""주문수량"""
|
|
195
|
+
ovrs_ord_prc: float
|
|
196
|
+
"""해외주문가"""
|
|
197
|
+
ordprc_ptn_code: Literal["00", "M1", "M2"]
|
|
198
|
+
"""호가유형코드 (00: 지정가, M1: LOO, M2: LOC, 03@시장가, M3@MOO, M4@MOC)"""
|
|
199
|
+
crcy_code: Literal["USD"] = "USD"
|
|
200
|
+
"""통화코드 (USD)"""
|
|
201
|
+
pnl_rat: float
|
|
202
|
+
"""손익률"""
|
|
203
|
+
pchs_amt: float
|
|
204
|
+
"""매입금액"""
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
OrderResGenericT = TypeVar("OrderResGenericType", bound=Dict[str, Any])
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class BaseOrderOverseasStock(Generic[OrderResGenericT], ABC):
|
|
211
|
+
"""
|
|
212
|
+
해외주식 매매 주문을 위한 기본 전략 클래스
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
id: str
|
|
216
|
+
"""전략의 고유 ID"""
|
|
217
|
+
description: str
|
|
218
|
+
"""전략에 대한 설명"""
|
|
219
|
+
securities: List[str]
|
|
220
|
+
"""사용 가능한 증권사/거래소들"""
|
|
221
|
+
|
|
222
|
+
@abstractmethod
|
|
223
|
+
def __init__(
|
|
224
|
+
self,
|
|
225
|
+
):
|
|
226
|
+
self.available_symbols = []
|
|
227
|
+
|
|
228
|
+
@abstractmethod
|
|
229
|
+
async def execute(self) -> 'List[OrderResGenericT]':
|
|
230
|
+
"""
|
|
231
|
+
매수 전략을 실행하는 메서드입니다.
|
|
232
|
+
"""
|
|
233
|
+
pass
|
|
234
|
+
|
|
235
|
+
def _set_system_id(self, system_id: Optional[str]) -> None:
|
|
236
|
+
"""
|
|
237
|
+
시스템 고유 ID를 설정합니다.
|
|
238
|
+
"""
|
|
239
|
+
self.system_id = system_id
|
|
240
|
+
|
|
241
|
+
def _set_available_symbols(self, symbols: List[SymbolInfo]) -> None:
|
|
242
|
+
"""
|
|
243
|
+
매매 전략 계산에 사용하려는 종목들을 전달합니다.
|
|
244
|
+
"""
|
|
245
|
+
self.available_symbols = symbols
|
|
246
|
+
|
|
247
|
+
def _set_held_symbols(self, symbols: List[HeldSymbol]) -> None:
|
|
248
|
+
"""
|
|
249
|
+
현재 보유중인 종목들을 받습니다.
|
|
250
|
+
"""
|
|
251
|
+
self.held_symbols = symbols
|
|
252
|
+
|
|
253
|
+
def _set_non_traded_symbols(self, symbols: List[NonTradedSymbol]) -> None:
|
|
254
|
+
"""
|
|
255
|
+
현재 미체결 종목들을 받습니다.
|
|
256
|
+
"""
|
|
257
|
+
self.non_traded_symbols = symbols
|
|
258
|
+
|
|
259
|
+
@abstractmethod
|
|
260
|
+
async def on_real_order_receive(self, order_type: OrderCategoryType, response: OrderResGenericT) -> None:
|
|
261
|
+
"""
|
|
262
|
+
매매 주문 상태를 받습니다.
|
|
263
|
+
"""
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class BaseBuyOverseasStock(BaseOrderOverseasStock[BaseBuyOverseasStockResponseType]):
|
|
268
|
+
"""
|
|
269
|
+
매수를 하기 위한 전략을 계산하고 매수를 위한 값을 던져줍니다.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
@abstractmethod
|
|
273
|
+
def __init__(
|
|
274
|
+
self,
|
|
275
|
+
):
|
|
276
|
+
super().__init__()
|
|
277
|
+
|
|
278
|
+
self.fcurr_dps = 0.0
|
|
279
|
+
self.fcurr_ord_able_amt = 0.0
|
|
280
|
+
|
|
281
|
+
def _set_available_balance(
|
|
282
|
+
self,
|
|
283
|
+
fcurr_dps: float,
|
|
284
|
+
fcurr_ord_able_amt: float
|
|
285
|
+
) -> None:
|
|
286
|
+
"""
|
|
287
|
+
사용 가능한 잔고를 설정합니다.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
fcurr_dps (float): 외화 예금
|
|
291
|
+
fcurr_ord_able_amt (float): 외화 주문 가능 금액
|
|
292
|
+
"""
|
|
293
|
+
self.fcurr_dps = fcurr_dps
|
|
294
|
+
self.fcurr_ord_able_amt = fcurr_ord_able_amt
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class BaseSellOverseasStock(BaseOrderOverseasStock[BaseSellOverseasStockResponseType]):
|
|
298
|
+
"""
|
|
299
|
+
매도를 하기 위한 전략을 계산하고 매도를 위한 값을 던져줍니다.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
id: str
|
|
303
|
+
"""전략의 고유 ID"""
|
|
304
|
+
description: str
|
|
305
|
+
"""전략에 대한 설명"""
|
|
306
|
+
securities_domains: List[str]
|
|
307
|
+
"""사용 가능한 증권사/거래소들 주소"""
|
|
308
|
+
|
|
309
|
+
@abstractmethod
|
|
310
|
+
def __init__(
|
|
311
|
+
self,
|
|
312
|
+
**kwargs
|
|
313
|
+
):
|
|
314
|
+
"""
|
|
315
|
+
symbols: 종목 정보 리스트
|
|
316
|
+
"""
|
|
317
|
+
self.symbols = []
|
|
318
|
+
|
|
319
|
+
@abstractmethod
|
|
320
|
+
async def on_real_order_receive(self, order_type: OrderCategoryType, response: Dict[str, Any]) -> None:
|
|
321
|
+
"""
|
|
322
|
+
매매 주문 상태를 받습니다.
|
|
323
|
+
"""
|
|
324
|
+
pass
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Literal, Optional, TypedDict, Union
|
|
2
|
+
|
|
3
|
+
from .community import BaseCondition, SymbolInfo, BaseBuyOverseasStock, BaseSellOverseasStock
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
ExecutionTimingType = Literal["once", "interval", "cron"]
|
|
7
|
+
"""
|
|
8
|
+
전략 반복 실행 여부입니다.
|
|
9
|
+
- "once": 한번만 실행
|
|
10
|
+
- "interval": 주기적으로 실행
|
|
11
|
+
- "cron": 크론 표현식에 따라 실행
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
LogicType = Literal["all", "any", "not", "xor", "at_least", "at_most", "exactly", "if_then", "weighted"]
|
|
15
|
+
"""
|
|
16
|
+
다음 설명과 같습니다.
|
|
17
|
+
- "all": 모든 조건 만족 (AND)
|
|
18
|
+
- "any": 하나 이상 조건 만족 (OR)
|
|
19
|
+
- "not": 조건이 만족되지 않아야 함 (NOT)
|
|
20
|
+
- "xor": 정확히 하나만 만족 (XOR)
|
|
21
|
+
- "at_least": 최소 N개 조건 만족
|
|
22
|
+
- "at_most": 최대 N개 조건 만족
|
|
23
|
+
- "exactly": 정확히 N개 조건 만족
|
|
24
|
+
- "if_then": 조건부 논리 (if-then)
|
|
25
|
+
- "weighted": 가중치 기반 조건 (점수 시스템)
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class StrategyConditionType(TypedDict):
|
|
31
|
+
"""
|
|
32
|
+
기본 전략 타입을 정의하는 TypedDict입니다.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
id: str
|
|
36
|
+
"""전략의 고유 ID"""
|
|
37
|
+
description: Optional[str] = None
|
|
38
|
+
"""전략에 대한 설명"""
|
|
39
|
+
logic: LogicType
|
|
40
|
+
"""전략의 논리 연산자"""
|
|
41
|
+
threshold: Optional[int] = None
|
|
42
|
+
"""전략의 임계값"""
|
|
43
|
+
conditions: List[Union['StrategyConditionType', BaseCondition]]
|
|
44
|
+
"""실행할 전략 리스트"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MaxSymbolsLimitType(TypedDict):
|
|
48
|
+
order: Literal["random", "mcap"]
|
|
49
|
+
"""
|
|
50
|
+
종목 선택 방식
|
|
51
|
+
- random: 랜덤 선택
|
|
52
|
+
- mcap: 시가총액 상위 선택
|
|
53
|
+
"""
|
|
54
|
+
limit: int
|
|
55
|
+
"""선택할 종목 수"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class StrategyType(TypedDict):
|
|
59
|
+
timing: ExecutionTimingType
|
|
60
|
+
"""전략 실행 타이밍"""
|
|
61
|
+
schedule: Optional[str] = None
|
|
62
|
+
"""
|
|
63
|
+
### 필드 순서
|
|
64
|
+
- 5-필드: 분 시 일(날짜) 월 요일 → 5-필드는 seconds-first 영향 없음
|
|
65
|
+
- 6-필드: 초 분 시 일(날짜) 월 요일
|
|
66
|
+
- 7-필드: 초 분 시 일(날짜) 월 요일 연도
|
|
67
|
+
|
|
68
|
+
<br>
|
|
69
|
+
|
|
70
|
+
### 허용 값/연산자
|
|
71
|
+
- 초/분: 0–59, 시: 0–23, 일: 1–31 또는 l(마지막 날), 월: 1–12 또는 jan–dec, 요일: 0–6 또는 sun–sat(0 또는 7=일요일), 연도: 1970–2099
|
|
72
|
+
- 와일드카드: *
|
|
73
|
+
- 범위/목록: A-B, A,B,C
|
|
74
|
+
- 간격: A/B 또는 A-B/S (예: */5)
|
|
75
|
+
- 요일 n번째: 요일#n (예: 2#3=셋째 화요일)
|
|
76
|
+
- 요일 마지막: lX (예: l5=마지막 금요일)
|
|
77
|
+
- 일(날짜) l: 해당 달의 마지막 날
|
|
78
|
+
- 일(날짜)과 요일을 함께 쓰면 OR
|
|
79
|
+
|
|
80
|
+
<br>
|
|
81
|
+
|
|
82
|
+
### 6-필드 예시 (초 분 시 일 월 요일)
|
|
83
|
+
- 매초: * * * * * *
|
|
84
|
+
- 5초마다: */5 * * * * *
|
|
85
|
+
- 매분 0초: 0 * * * * *
|
|
86
|
+
- 15분마다(0초): 0 */15 * * * *
|
|
87
|
+
- 매시 정각: 0 0 * * * *
|
|
88
|
+
- 매일 09:30:00: 0 30 9 * * *
|
|
89
|
+
- 매월 1일 09:00:00: 0 0 9 1 * *
|
|
90
|
+
- 매월 마지막 날 18:00:00: 0 0 18 l * *
|
|
91
|
+
- 매주 월요일 10:00:00: 0 0 10 * * mon
|
|
92
|
+
- 평일 10:00:00: 0 0 10 * * mon-fri
|
|
93
|
+
- 1·4·7월 10:00:00: 0 0 10 * jan,apr,jul *
|
|
94
|
+
- 매월 셋째 화요일 10:00:00: 0 0 10 * * 2#3
|
|
95
|
+
- 매월 마지막 금요일 18:00:00: 0 0 18 * * l5
|
|
96
|
+
- 평일 9–18시 매시 정각: 0 0 9-18 * * mon-fri
|
|
97
|
+
- 평일 9–18시 10분 간격(0초): 0 0/10 9-18 * * mon-fri
|
|
98
|
+
- 매 시각 15·30·45분의 10초: 10 15,30,45 * * * *
|
|
99
|
+
- 일요일 09:00:00: 0 0 9 * * 0,7
|
|
100
|
+
|
|
101
|
+
<br>
|
|
102
|
+
|
|
103
|
+
### 7-필드 예시 (초 분 시 일 월 요일 연도)
|
|
104
|
+
- 2025년 동안 매초: * * * * * * 2025
|
|
105
|
+
- 2025–2026년 매월 1일 00:00:00: 0 0 0 1 * * 2025-2026
|
|
106
|
+
- 2025년 평일 09:00:00: 0 0 9 * * mon-fri 2025
|
|
107
|
+
- 2025년 매월 마지막 날 18:30:05: 5 30 18 l * * 2025
|
|
108
|
+
- 2025년 매월 셋째 화요일 10:00:00: 0 0 10 * * 2#3 2025
|
|
109
|
+
- 2025–2030년 격년 1/1 00:00:00: 0 0 0 1 jan * 2025-2030/2
|
|
110
|
+
|
|
111
|
+
<br>
|
|
112
|
+
|
|
113
|
+
### 5-필드 예시 (분 시 일 월 요일, 항상 0초)
|
|
114
|
+
- 매분: * * * * *
|
|
115
|
+
- 5분마다: */5 * * * *
|
|
116
|
+
- 평일 09:00: 0 9 * * mon-fri
|
|
117
|
+
- 매월 마지막 날 18:00: 0 18 l * *
|
|
118
|
+
- 매월 셋째 화요일 10:00: 0 10 * * 2#3
|
|
119
|
+
|
|
120
|
+
<br>
|
|
121
|
+
|
|
122
|
+
### 팁/주의
|
|
123
|
+
- 5-필드는 초 필드가 없고 항상 0초입니다.
|
|
124
|
+
- 일(날짜)과 요일을 같이 쓰면 OR입니다.
|
|
125
|
+
- 일요일은 0 또는 7, 요일/월 이름은 대소문자 무관입니다.
|
|
126
|
+
- 허용 형태:
|
|
127
|
+
- 요일: sun, mon, tue, wed, thu, fri, sat 또는 숫자 0–6
|
|
128
|
+
- 일요일은 0 권장. 7은 일부 설정/버전에서 거부될 수 있으니 사용하지 않는 게 안전합니다.
|
|
129
|
+
- 월: jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec 또는 숫자 1–12
|
|
130
|
+
- 예시:
|
|
131
|
+
- 0 0 10 * * Mon-Fri → 평일 10:00:00
|
|
132
|
+
- 0 0 9 * Jan,Apr,Jul * → 1·4·7월 09:00:00
|
|
133
|
+
- 0 0 9 * * sun → 일요일 09:00:00
|
|
134
|
+
- 0 0 9 * * 0 → 일요일 09:00:00 (권장 숫자 표기)
|
|
135
|
+
- 연도 제한은 7-필드에서만 가능합니다(1970–2099).
|
|
136
|
+
- 시간대는 strategies.timezone을 따릅니다.
|
|
137
|
+
"""
|
|
138
|
+
timezone: Optional[str] = "Asia/Seoul"
|
|
139
|
+
id: str
|
|
140
|
+
"""전략의 고유 ID"""
|
|
141
|
+
description: Optional[str] = None
|
|
142
|
+
"""전략에 대한 설명"""
|
|
143
|
+
symbols: Optional[List[SymbolInfo]] = None
|
|
144
|
+
"""분석할 종목들, 빈값이면 전체 종목에서 분석한다."""
|
|
145
|
+
logic: LogicType
|
|
146
|
+
"""전략의 논리 연산자"""
|
|
147
|
+
threshold: Optional[int] = None
|
|
148
|
+
"""전략의 임계값"""
|
|
149
|
+
order_id: Optional[str] = None
|
|
150
|
+
"""
|
|
151
|
+
buy 또는 sell 영역에서 주문으로 사용될 order_id들을 선택한다.
|
|
152
|
+
"""
|
|
153
|
+
max_symbols: Optional[MaxSymbolsLimitType]
|
|
154
|
+
"""
|
|
155
|
+
전체 종목중에 몇개까지만 확인할지 지정한다. None이면 전체 종목을 다 확인한다.
|
|
156
|
+
"""
|
|
157
|
+
conditions: List[Union['StrategyConditionType', 'DictConditionType', BaseCondition]]
|
|
158
|
+
"""실행할 전략 리스트"""
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class DictConditionType(TypedDict):
|
|
162
|
+
"""
|
|
163
|
+
DictConditionType은 문자열로 표현된 조건 타입입니다.
|
|
164
|
+
이 타입은 BaseCondition의 문자열 표현을 사용하여 조건을 정의합니다.
|
|
165
|
+
"""
|
|
166
|
+
condition_id: str
|
|
167
|
+
"""전략의 고유 ID"""
|
|
168
|
+
params: Optional[Dict[str, Any]]
|
|
169
|
+
"""조건에 필요한 매개변수들"""
|
|
170
|
+
weight: Optional[int] = 0
|
|
171
|
+
"""조건의 가중치, 기본값은 0"""
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class SystemSettingType(TypedDict):
|
|
175
|
+
"""시스템 설정 정보"""
|
|
176
|
+
name: str
|
|
177
|
+
"""시스템 이름"""
|
|
178
|
+
description: str
|
|
179
|
+
"""시스템 설명"""
|
|
180
|
+
version: str
|
|
181
|
+
"""시스템 버전"""
|
|
182
|
+
author: str
|
|
183
|
+
"""시스템 작성자"""
|
|
184
|
+
date: str
|
|
185
|
+
"""시스템 생성일"""
|
|
186
|
+
debug: str
|
|
187
|
+
"""
|
|
188
|
+
디버그 모드 여부
|
|
189
|
+
"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL" 단계로 설정
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class SecuritiesAccountType(TypedDict):
|
|
194
|
+
"""계좌와 증권사 및 상품 정보"""
|
|
195
|
+
company: Literal["ls"]
|
|
196
|
+
"""증권사 이름"""
|
|
197
|
+
product: Literal["overseas_stock", "overseas_future"]
|
|
198
|
+
"""상품 이름"""
|
|
199
|
+
appkey: Optional[str]
|
|
200
|
+
"""앱 키"""
|
|
201
|
+
appsecretkey: Optional[str]
|
|
202
|
+
"""앱 시크릿 키"""
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class OrderTimeType(TypedDict, total=False):
|
|
206
|
+
start: str
|
|
207
|
+
"""시작 시간, HH:MM:SS 형식"""
|
|
208
|
+
end: str
|
|
209
|
+
"""종료 시간, HH:MM:SS 형식"""
|
|
210
|
+
days: List[Literal["mon", "tue", "wed", "thu", "fri", "sat", "sun"]]
|
|
211
|
+
"""주문 실행 요일, 예: ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']"""
|
|
212
|
+
timezone: Optional[str]
|
|
213
|
+
"""시간대, 예: 'Asia/Seoul'"""
|
|
214
|
+
behavior: Optional[Literal["defer", "skip"]]
|
|
215
|
+
"""
|
|
216
|
+
- defer: 전략이 윈도우 밖에서 트리거되면 다음 윈도우 시작 시 주문을 실행(대기). 윈도우 안에서 트리거되면 즉시 실행.
|
|
217
|
+
- skip: 윈도우 밖에서 트리거되면 주문을 실행하지 않음(스킵). 윈도우 안에서는 즉시 실행.
|
|
218
|
+
"""
|
|
219
|
+
max_delay_seconds: Optional[int] = 86400
|
|
220
|
+
"""최대 지연 시간(초), 기본값 86400초(24시간)"""
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class NewBuyTradeType(TypedDict):
|
|
224
|
+
order_id: str
|
|
225
|
+
"""매수 거래의 고유 ID"""
|
|
226
|
+
description: Optional[str]
|
|
227
|
+
"""매수 거래에 대한 설명"""
|
|
228
|
+
block_duplicate_trade: bool
|
|
229
|
+
"""중복거래 방지 여부, True:방지, False:허용"""
|
|
230
|
+
available_balance: Optional[float] = None
|
|
231
|
+
"""빈 값으로 두면, securities에서 지정된 증권사 예수금으로 대체된다."""
|
|
232
|
+
condition: Optional[Union[DictConditionType, BaseBuyOverseasStock]]
|
|
233
|
+
""" 매수 전략 정보"""
|
|
234
|
+
order_time: Optional[OrderTimeType] = None
|
|
235
|
+
"""주문 실행 시간 설정"""
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class NewSellTradeType(TypedDict):
|
|
239
|
+
order_id: str
|
|
240
|
+
"""매도 거래의 고유 ID"""
|
|
241
|
+
description: Optional[str]
|
|
242
|
+
"""매도 거래에 대한 설명"""
|
|
243
|
+
condition: Optional[Union[DictConditionType, BaseSellOverseasStock]]
|
|
244
|
+
""" 매도 전략 정보"""
|
|
245
|
+
order_time: Optional[OrderTimeType] = None
|
|
246
|
+
"""주문 실행 시간 설정"""
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class OrdersType(TypedDict):
|
|
250
|
+
"""
|
|
251
|
+
매수 또는 매도용 전략, 빈값이면 전략만 확인하는 용도
|
|
252
|
+
주식 new_buys, new_sells만 지원합니다.
|
|
253
|
+
|
|
254
|
+
선물옵션("shortbuy", "shortsell", "longbuy", "longsell") 거래는 준비중입니다.
|
|
255
|
+
"""
|
|
256
|
+
new_buys: List[NewBuyTradeType]
|
|
257
|
+
"""신규매수"""
|
|
258
|
+
new_sells: List[NewSellTradeType]
|
|
259
|
+
"""신규매도"""
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class SystemType(TypedDict):
|
|
263
|
+
"""자동화 시스템 데이터 타입"""
|
|
264
|
+
id: str
|
|
265
|
+
"""시스템의 고유 ID"""
|
|
266
|
+
settings: SystemSettingType
|
|
267
|
+
"""시스템 설정 정보"""
|
|
268
|
+
securities: SecuritiesAccountType
|
|
269
|
+
"""증권사 인증 정보"""
|
|
270
|
+
strategies: List[StrategyType]
|
|
271
|
+
"""시스템 실행 바디"""
|
|
272
|
+
orders: OrdersType
|
|
273
|
+
"""주문 정보"""
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
커스텀 예외 클래스들
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BasicException(Exception):
|
|
7
|
+
"""오픈소스의 기본 에러 클래스"""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
message: str = "알 수 없는 오류가 발생했습니다.",
|
|
12
|
+
code: str = "UNKNOWN_ERROR",
|
|
13
|
+
):
|
|
14
|
+
super().__init__(message, code)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AppKeyException(BasicException):
|
|
18
|
+
"""앱키가 존재하지 않음"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
message: str = "appkey 또는 secretkey가 존재하지 않습니다.",
|
|
23
|
+
code: str = "APPKEY_NOT_FOUND",
|
|
24
|
+
):
|
|
25
|
+
super().__init__(message, code)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class LoginException(BasicException):
|
|
29
|
+
"""로그인 실패"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
message: str = "로그인에 실패했습니다.",
|
|
34
|
+
code: str = "LOGIN_ERROR",
|
|
35
|
+
):
|
|
36
|
+
super().__init__(message, code)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TokenException(BasicException):
|
|
40
|
+
"""토큰 발급 실패"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
message: str = "토큰 발급 실패했습니다.",
|
|
45
|
+
code: str = "TOKEN_ERROR",
|
|
46
|
+
):
|
|
47
|
+
super().__init__(message, code)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TokenNotFoundException(BasicException):
|
|
51
|
+
"""토큰이 존재하지 않음"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
message: str = "토큰이 존재하지 않습니다.",
|
|
56
|
+
code: str = "TOKEN_NOT_FOUND",
|
|
57
|
+
):
|
|
58
|
+
super().__init__(message, code)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TrRequestDataNotFoundException(BasicException):
|
|
62
|
+
"""TR 요청 데이터가 존재하지 않음"""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
message: str = "TR 요청 데이터가 존재하지 않습니다.",
|
|
67
|
+
code: str = "TR_REQUEST_DATA_NOT_FOUND",
|
|
68
|
+
):
|
|
69
|
+
super().__init__(message, code)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class SystemException(BasicException):
|
|
73
|
+
"""시스템 오류"""
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
message: str = "시스템 오류가 발생했습니다.",
|
|
78
|
+
code: str = "SYSTEM_ERROR",
|
|
79
|
+
):
|
|
80
|
+
super().__init__(message, code)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class NotExistSystemException(SystemException):
|
|
84
|
+
"""존재하지 않는 시스템"""
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
message: str = "존재하지 않는 시스템입니다.",
|
|
89
|
+
code: str = "NOT_EXIST_SYSTEM",
|
|
90
|
+
):
|
|
91
|
+
super().__init__(message, code)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class NotExistSystemKeyException(SystemException):
|
|
95
|
+
"""존재하지 않는 키"""
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
message: str = "존재하지 않는 키입니다.",
|
|
100
|
+
code: str = "NOT_EXIST_KEY",
|
|
101
|
+
):
|
|
102
|
+
super().__init__(message, code)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class NotExistConditionException(SystemException):
|
|
106
|
+
"""존재하지 않는 조건"""
|
|
107
|
+
|
|
108
|
+
def __init__(
|
|
109
|
+
self,
|
|
110
|
+
message: str = "존재하지 않는 조건입니다.",
|
|
111
|
+
code: str = "NOT_EXIST_CONDITION",
|
|
112
|
+
):
|
|
113
|
+
super().__init__(message, code)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class OrderException(SystemException):
|
|
117
|
+
"""주문 관련 오류"""
|
|
118
|
+
|
|
119
|
+
def __init__(
|
|
120
|
+
self,
|
|
121
|
+
message: str = "주문 처리 중 오류가 발생했습니다.",
|
|
122
|
+
code: str = "ORDER_ERROR",
|
|
123
|
+
):
|
|
124
|
+
super().__init__(message, code)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class NotExistCompanyException(SystemException):
|
|
128
|
+
"""존재하지 않는 증권사"""
|
|
129
|
+
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
message: str = "증권사가 존재하지 않습니다.",
|
|
133
|
+
code: str = "NOT_EXIST_COMPANY",
|
|
134
|
+
):
|
|
135
|
+
super().__init__(message, code)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
한글 별칭을 강제하기 위한 데코레이터와 메타클래스
|
|
3
|
+
|
|
4
|
+
이 모듈은 함수에 한글 별칭이 필요한 경우 이를 강제하기 위한
|
|
5
|
+
데코레이터와 메타클래스를 제공합니다.
|
|
6
|
+
이 기능은 함수가 한글 별칭을 갖도록 보장하며, 이를 통해
|
|
7
|
+
프로그램의 일관성을 유지합니다.
|
|
8
|
+
|
|
9
|
+
사용방법은
|
|
10
|
+
1. 함수에 `@require_korean_alias` 데코레이터를 적용합니다.
|
|
11
|
+
2. 클래스에 `EnforceKoreanAliasMeta` 메타클래스를 적용합니다.
|
|
12
|
+
이렇게 하면 해당 클래스의 모든 메서드가 한글 별칭을 갖도록 강제됩니다.
|
|
13
|
+
|
|
14
|
+
예시:
|
|
15
|
+
```python
|
|
16
|
+
from programgarden_core.korean_alias import require_korean_alias, EnforceKoreanAliasMeta
|
|
17
|
+
|
|
18
|
+
class OverseasStock(metaclass=EnforceKoreanAliasMeta):
|
|
19
|
+
|
|
20
|
+
@require_korean_alias
|
|
21
|
+
def accno(self) -> Accno:
|
|
22
|
+
return Accno()
|
|
23
|
+
|
|
24
|
+
계좌 = accno
|
|
25
|
+
계좌.__doc__ = "계좌 정보를 조회합니다."
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
이렇게 하면 `accno`는 한글 별칭을 반드시 가져야 합니다.
|
|
30
|
+
만약 한글 별칭이 정의되지 않으면 `ValueError`가 발생합니다.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from functools import wraps
|
|
34
|
+
import inspect
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def require_korean_alias(func):
|
|
38
|
+
"""한글 별칭이 필요한 함수임을 표시하는 데코레이터"""
|
|
39
|
+
@wraps(func)
|
|
40
|
+
def wrapper(*args, **kwargs):
|
|
41
|
+
return func(*args, **kwargs)
|
|
42
|
+
wrapper._requires_korean_alias = True
|
|
43
|
+
return wrapper
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class EnforceKoreanAliasMeta(type):
|
|
47
|
+
"""한글 별칭 강제를 위한 메타클래스"""
|
|
48
|
+
def __new__(cls, name, bases, attrs):
|
|
49
|
+
korean_aliases = set()
|
|
50
|
+
# 한글 별칭 수집
|
|
51
|
+
for key, value in attrs.items():
|
|
52
|
+
if key.startswith('__') or not key.encode().isalnum() or key.isascii():
|
|
53
|
+
continue
|
|
54
|
+
korean_aliases.add(value.__name__ if callable(value) else key)
|
|
55
|
+
|
|
56
|
+
# @require_korean_alias가 붙은 메서드만 검사
|
|
57
|
+
for key, value in attrs.items():
|
|
58
|
+
if not inspect.isfunction(value) or key.startswith('__'):
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
# 데코레이터가 붙은 경우에만 한글 별칭 검사
|
|
62
|
+
if getattr(value, '_requires_korean_alias', False):
|
|
63
|
+
|
|
64
|
+
has_korean_alias = False
|
|
65
|
+
for alias_key, alias_value in attrs.items():
|
|
66
|
+
if (
|
|
67
|
+
not alias_key.startswith('__')
|
|
68
|
+
and not alias_key.isascii()
|
|
69
|
+
and callable(alias_value)
|
|
70
|
+
and alias_value.__name__ == value.__name__
|
|
71
|
+
):
|
|
72
|
+
has_korean_alias = True
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
if not has_korean_alias:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"메서드 '{key}'는 @require_korean_alias 데코레이터가 적용되었으나 "
|
|
78
|
+
"한글 별칭이 정의되지 않았습니다."
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return super().__new__(cls, name, bases, attrs)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
# ANSI 색상 코드
|
|
4
|
+
LOG_COLORS = {
|
|
5
|
+
"INFO": "\033[92m", # 초록색
|
|
6
|
+
"WARNING": "\033[93m", # 노란색
|
|
7
|
+
"ERROR": "\033[91m", # 빨간색
|
|
8
|
+
"CRITICAL": "\033[41m", # 배경 빨간색
|
|
9
|
+
"RESET": "\033[0m", # 색상 초기화
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
pg_logger = logging.getLogger("pg")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _ColoredFormatter(logging.Formatter):
|
|
16
|
+
"""메시지를 제외한 모든 항목에 로그 레벨별 색상을 입히는 포매터"""
|
|
17
|
+
|
|
18
|
+
def format_time(self, record, datefmt=None):
|
|
19
|
+
# 원본 levelname을 사용 (format()에서 _orig_levelname에 저장)
|
|
20
|
+
orig_level = getattr(record, "_orig_levelname", record.levelname)
|
|
21
|
+
log_color = LOG_COLORS.get(orig_level, LOG_COLORS["RESET"])
|
|
22
|
+
t = super().formatTime(record, datefmt or "%Y-%m-%d %H:%M:%S")
|
|
23
|
+
return f"{log_color}{t}{LOG_COLORS['RESET']}"
|
|
24
|
+
|
|
25
|
+
def format(self, record):
|
|
26
|
+
# record의 원본 levelname을 저장 (나중에 formatTime에서 사용)
|
|
27
|
+
orig_levelname = record.levelname
|
|
28
|
+
record._orig_levelname = orig_levelname
|
|
29
|
+
|
|
30
|
+
# 원본 levelname을 바탕으로 색상 결정
|
|
31
|
+
color = LOG_COLORS.get(orig_levelname, LOG_COLORS["RESET"])
|
|
32
|
+
record.levelname = f"{color}{orig_levelname}{LOG_COLORS['RESET']}"
|
|
33
|
+
record.name = f"{color}{record.name}{LOG_COLORS['RESET']}"
|
|
34
|
+
record.filename = f"{color}{record.pathname}{LOG_COLORS['RESET']}"
|
|
35
|
+
|
|
36
|
+
# 숫자형 필드를 변경하지 않고, 새 필드에 색상 적용
|
|
37
|
+
record.colored_lineno = f"{color}{record.lineno}{LOG_COLORS['RESET']}"
|
|
38
|
+
|
|
39
|
+
return super().format(record)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def pg_log(level=logging.DEBUG):
|
|
43
|
+
"""
|
|
44
|
+
로그 레벨을 설정합니다.
|
|
45
|
+
설정된 레벨부터 표시됩니다.
|
|
46
|
+
|
|
47
|
+
.. code-block:: python
|
|
48
|
+
logger.debug("디버그 메시지")
|
|
49
|
+
logger.info("정보 메시지")
|
|
50
|
+
logger.warning("경고 메시지")
|
|
51
|
+
logger.error("에러 메시지")
|
|
52
|
+
logger.critical("치명적인 메시지")
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
handler = logging.StreamHandler()
|
|
56
|
+
pg_logger.setLevel(level)
|
|
57
|
+
formatter = _ColoredFormatter(
|
|
58
|
+
"%(name)s | %(asctime)s | %(levelname)s | %(filename)s:%(colored_lineno)s\n %(message)s" # noqa: E501
|
|
59
|
+
)
|
|
60
|
+
handler.setFormatter(formatter)
|
|
61
|
+
pg_logger.addHandler(handler)
|
|
62
|
+
|
|
63
|
+
# 루트 로거에 영향을 주지 않도록 설정
|
|
64
|
+
pg_logger.propagate = False # 루트 로거로 전파 방지
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def pg_log_disable():
|
|
68
|
+
"""로그를 완전히 비활성화합니다."""
|
|
69
|
+
pg_logger.handlers.clear()
|
|
70
|
+
pg_logger.setLevel(logging.CRITICAL + 1) # 모든 로그 차단
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def pg_log_reset():
|
|
74
|
+
"""로그 설정을 초기화합니다."""
|
|
75
|
+
pg_logger.handlers.clear()
|
|
76
|
+
pg_logger.setLevel(logging.NOTSET)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# 테스트 코드
|
|
80
|
+
if __name__ == "__main__":
|
|
81
|
+
# 자동 실행 제거 - 명시적으로 호출해야 함
|
|
82
|
+
# pg_log(level=logging.DEBUG) # 이 줄 제거
|
|
83
|
+
pg_log()
|
|
84
|
+
pg_logger.debug("디버그 메시지")
|
|
85
|
+
pg_logger.info("정보 메시지")
|
|
86
|
+
pg_logger.warning("경고 메시지")
|
|
87
|
+
pg_logger.error("에러 메시지")
|
|
88
|
+
pg_logger.critical("치명적인 메시지")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
authors = [
|
|
3
|
+
{"name" = "프로그램동산","email" = "coding@programgarden.com"}
|
|
4
|
+
]
|
|
5
|
+
homepage = "https://programgarden.com"
|
|
6
|
+
requires-python = ">=3.9"
|
|
7
|
+
name = "programgarden-core"
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
description = "자동화매매 클래스 상속, 로그, 환경 등의 핵심이되는 오픈소스"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
|
|
12
|
+
[tool.poetry]
|
|
13
|
+
packages = [
|
|
14
|
+
{ include = "programgarden_core" }
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[tool.poetry.dependencies]
|
|
18
|
+
|
|
19
|
+
[build-system]
|
|
20
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
21
|
+
build-backend = "poetry.core.masonry.api"
|