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.
@@ -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"