From 4e9f912e5f4ebe6d21a1733e2b8668bc8c103ab7 Mon Sep 17 00:00:00 2001 From: gerd Date: Fri, 17 Jul 2026 00:37:58 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9C=A0=ED=9A=A8=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20KEY=20=EA=B0=80=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=EB=90=98=EC=96=B4=20=EC=9E=88=EC=96=B4=EB=8F=84,=20=EC=9D=BC?= =?UTF-8?q?=EB=8B=A8=20UI=20=ED=8E=98=EC=9D=B4=EC=A7=80=EA=B0=80=20?= =?UTF-8?q?=EB=82=98=EC=98=A4=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 148 bytes app/__pycache__/main.cpython-313.pyc | Bin 0 -> 4992 bytes app/core/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 153 bytes app/core/__pycache__/auth.cpython-313.pyc | Bin 0 -> 8183 bytes app/core/__pycache__/config.cpython-313.pyc | Bin 0 -> 6516 bytes app/core/__pycache__/database.cpython-313.pyc | Bin 0 -> 2160 bytes app/core/__pycache__/logger.cpython-313.pyc | Bin 0 -> 1572 bytes .../__pycache__/rate_limiter.cpython-313.pyc | Bin 0 -> 2152 bytes app/core/auth.py | 26 ++++++++++++++++-- .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 155 bytes .../__pycache__/collector.cpython-313.pyc | Bin 0 -> 4835 bytes .../__pycache__/scheduler.cpython-313.pyc | Bin 0 -> 3663 bytes .../strategy_engine.cpython-313.pyc | Bin 0 -> 7971 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 166 bytes .../__pycache__/base.cpython-313.pyc | Bin 0 -> 2391 bytes .../__pycache__/conditional.cpython-313.pyc | Bin 0 -> 2632 bytes .../__pycache__/periodic.cpython-313.pyc | Bin 0 -> 3347 bytes .../__pycache__/technical.cpython-313.pyc | Bin 0 -> 7703 bytes app/main.py | 17 +++++++++--- .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 155 bytes app/models/__pycache__/stock.cpython-313.pyc | Bin 0 -> 5198 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 156 bytes .../__pycache__/dashboard.cpython-313.pyc | Bin 0 -> 4320 bytes .../__pycache__/stocks.cpython-313.pyc | Bin 0 -> 4948 bytes .../__pycache__/strategies.cpython-313.pyc | Bin 0 -> 5430 bytes .../__pycache__/trading.cpython-313.pyc | Bin 0 -> 4760 bytes .../__pycache__/websocket.cpython-313.pyc | Bin 0 -> 3899 bytes app/routers/dashboard.py | 2 ++ .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 157 bytes .../__pycache__/account.cpython-313.pyc | Bin 0 -> 6652 bytes .../__pycache__/market_data.cpython-313.pyc | Bin 0 -> 8272 bytes .../__pycache__/realtime.cpython-313.pyc | Bin 0 -> 11014 bytes .../__pycache__/trading.cpython-313.pyc | Bin 0 -> 8542 bytes app/services/account.py | 14 ++++++++-- app/services/market_data.py | 18 ++++++++++-- app/services/trading.py | 15 ++++++++++ app/templates/index.html | 13 +++++++++ data/stock.db | Bin 0 -> 49152 bytes data/stock.db-shm | Bin 0 -> 32768 bytes data/stock.db-wal | 0 40 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 app/__pycache__/__init__.cpython-313.pyc create mode 100644 app/__pycache__/main.cpython-313.pyc create mode 100644 app/core/__pycache__/__init__.cpython-313.pyc create mode 100644 app/core/__pycache__/auth.cpython-313.pyc create mode 100644 app/core/__pycache__/config.cpython-313.pyc create mode 100644 app/core/__pycache__/database.cpython-313.pyc create mode 100644 app/core/__pycache__/logger.cpython-313.pyc create mode 100644 app/core/__pycache__/rate_limiter.cpython-313.pyc create mode 100644 app/engine/__pycache__/__init__.cpython-313.pyc create mode 100644 app/engine/__pycache__/collector.cpython-313.pyc create mode 100644 app/engine/__pycache__/scheduler.cpython-313.pyc create mode 100644 app/engine/__pycache__/strategy_engine.cpython-313.pyc create mode 100644 app/engine/strategies/__pycache__/__init__.cpython-313.pyc create mode 100644 app/engine/strategies/__pycache__/base.cpython-313.pyc create mode 100644 app/engine/strategies/__pycache__/conditional.cpython-313.pyc create mode 100644 app/engine/strategies/__pycache__/periodic.cpython-313.pyc create mode 100644 app/engine/strategies/__pycache__/technical.cpython-313.pyc create mode 100644 app/models/__pycache__/__init__.cpython-313.pyc create mode 100644 app/models/__pycache__/stock.cpython-313.pyc create mode 100644 app/routers/__pycache__/__init__.cpython-313.pyc create mode 100644 app/routers/__pycache__/dashboard.cpython-313.pyc create mode 100644 app/routers/__pycache__/stocks.cpython-313.pyc create mode 100644 app/routers/__pycache__/strategies.cpython-313.pyc create mode 100644 app/routers/__pycache__/trading.cpython-313.pyc create mode 100644 app/routers/__pycache__/websocket.cpython-313.pyc create mode 100644 app/services/__pycache__/__init__.cpython-313.pyc create mode 100644 app/services/__pycache__/account.cpython-313.pyc create mode 100644 app/services/__pycache__/market_data.cpython-313.pyc create mode 100644 app/services/__pycache__/realtime.cpython-313.pyc create mode 100644 app/services/__pycache__/trading.cpython-313.pyc create mode 100644 data/stock.db create mode 100644 data/stock.db-shm create mode 100644 data/stock.db-wal diff --git a/app/__pycache__/__init__.cpython-313.pyc b/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d9cf1ebbd7feac7d7c03238f4d01d29bbb7ac515 GIT binary patch literal 148 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 br8Fniu80+A5Xhop5aS~=BO_xGGmr%Um8>Q# literal 0 HcmV?d00001 diff --git a/app/core/__pycache__/auth.cpython-313.pyc b/app/core/__pycache__/auth.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..642a478a687bd2f2f03d3bd335963e364c2c49e4 GIT binary patch literal 8183 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 G{rv~1S!o*p literal 0 HcmV?d00001 diff --git a/app/core/__pycache__/database.cpython-313.pyc b/app/core/__pycache__/database.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08551a6b1c47adfde8a13635edd96b9e06f48ffb GIT binary patch literal 2160 zcmah~&2JM&6rb50uh-wP%|{d&FdqqZ1EdvAo1jt@glOdi7kP`Q6ici1dh9H*cg^e? z5l*2}3-k+GDQb^M^wLYkrMDiC`VS<4lxWndNEND(dI&PAdg!5Vc5U;~Rvl~K{N}y4 zvor7g-g7sRh$0x@{r>*BA7zAo6^s5*0)V@J09Zj9((o)QVuteqOS4i@X0ngvSyBwK zP*Guuzb3QcqRP}_ghl*yXf|4mu~;4rqdiDdMvxY+sFUzLF<8730pZIKEvMFL3LMw<^Btqvv!ryrZZX$u!Xa%3 z%v*Kc6}K^U!}FL`?ReDHb(Vl#yW$Ld>co-CS%)pUZNsFM(@WlhV^=iKF&Ae#p40LJ zQ8C)>is>*~sTrP8g<~CRFEMd4UkKoaKzxKaQCYUHzZ7-PH!Y7InV6V3$XHdBpLSgKWe#UL- zj8yKiu5S~wh9`S(<>7_4K!$p=<1X;1fTq)GS)K`7 z4~B@9pP?TBtf2Wpe&z>xER%uv0F-;(XP^Mk&p}yzRvX~5VA~5r!|;=)r97IC_f6$w zA3dGR>8H4l<}-by$CWaJ5A}L(~nA_IilXY;q-NBz_?V72{_zY}uBl*Q)FwY`z;F z_ZWy}^b=7(9loBsmRpbPSc~kqM|Q50(KRxhh^|l3OFW zZ^l+h?k*|#`#+Z}asIucnmixmpUYRD;WpX{b#?)*V5k=KZ}rso6j9s#zdm(zRNjT^ z_zk&^3%CR!j%z`$oO4X0$;2N3G)$>hbp>~N+*TSuZ3xJ#pc;e6eF?-P5t7K(Pj)UJ z-<0G~b|X3TvHC!bURSTFpGE(?Dh1##R|NHg*cASp>_uOur|_-Jr2M@+<$qma)PtJk zq~zGNuvK)`dIvPnRT&T&2?lrzO{?msn~P>G%O-?PP{6hdVjYx7MGkUYLnm+hAOrp}VvYA9ucpYc%;>?ZLzn;E_N4pZi*{+PFu}y&n#jM~s9qNNB@o$=IAMaqMDMY{ec%vt`GLEtw~| zv}TO#wCt3_Ht=DGjtkF7F>n({iw9Jg6W`Sgkj20>Qk}s~qYrj6;=QLBm-(KY0ev zKoS)$dXCDd>~+@U&V3Q}a=uL>PpipmeEv;8;~|ZEMc-vF?puvN$6!6RdJ)u4dn}Fh zIA%}_=C62h-{xn0>>)J(Kkho4IKS>$FY8-4JywNF2J00x;eW0|@D$Q>m`93`?Oc-Y zEXvJAxss4KR^{zgS%n)aRCLFvHl5oFON5q=4b!oSXxyvYPRZKq@NK8Mcnx@>VV12* z-K;4WiO{fWHGm{kGn@=y0~65(NUt>!AgDVICVacqf(-XjHk~8lj&4>O2DV8kZQ&*m zAQjH!)=TMJetYdUiKq@X^ybEhMkE;T98s@S#u?1PPLbe9D&d=Zl{zL9>3YK$6BLnf zw44a%O~~cB?cB;3N!5%gp{0x_xvK zi47vNN0HgCa~zr9PY=29SGg~9->!eZ{y_Vw@I&F@r@yWLp7|wn99!#i??0Qkba?ad z=1D9*h{;DW`N!QStJ}R;_8|0Ebb1g?9Ys^eQRP4wiWdgr)t-2@d#5j^hT`NvobHL! z-Be#p{yp)=$>n%=bRf<2q?vA`FTD+-_P0-AIY?pg1u$7f#I#&Uz;-aE1%l^k z7l*DBh8VnqS<8eo!G_buW@nOa4&14wjC)3kK*FU`wGBC=RKoOa!SvCgchuQw)IW2t z0j6(&yPrQJ{1(M?lq0^^v?}d}aRV#h(s$6Fg6^LH!!S=#@;RD(il&}LKHLxg8;mkh H_sjnP6%kjt literal 0 HcmV?d00001 diff --git a/app/core/__pycache__/rate_limiter.cpython-313.pyc b/app/core/__pycache__/rate_limiter.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b036b5770e824b42eee6ccad34140223ba83602b GIT binary patch literal 2152 zcma(STWB0r^xS#vZg$#io-J|HP9Bg>O(H~-=+cL^8>MYrbeF_JmJZ`&Hl1{KW^-q@ zrGKIz{Sivj67fe7(TYEQ_DjSz|6LN1$e3L$jBEM0$f=L91qja)4 zj|kq0Op&2Dz!cc*G#oQ^?Es_o9X#EZF(cJCAgQJP`S(>wbjOHRSFJh zMa$7F2Ti=H+mtyvh-{Vf+w*#-Wic0*W=cCD+!KOAD%;q{K> zQvOzc@zM?9v!+cX1%ohH9}%2CVgCOK?d<~vV=x8T#_6t^z&VWhWsJpDa)-}|GSbd$ zMhL`E%GWo{r?+-p{t|@Hem*jd?>?*qcyaqxYP{9`&^DIf%kLB%?+l^EUoX zJunC%73Nee*Pa?zJFxO~>jB8v$lM*0VyH zPp}^t3uTL%LEG-KtZy>y-d_lwu)1tJw3J#(EvD96JC}xU4X?EJ-Z;C~+F5Pw#qTS> zIxt=t7{5m<1F3sPH9cLy@6`0l!1P+8y_!%e38k7iR!JONO&s6gq3zJ~`Fs>ux4t+6nH@4C> zQf(i3*go>RoT$plikz&HN?TY4)o!SlbUXPR-k|2j#ZmhQhXg7|}%<9PgFj7$*W!MMcm z(**aI2|;|BmnOO*%P}$`%gaqN!#g9mkFjCS4TBhl6W=S!nxHDs1WtiiuwC5`Bx?F) z>I6c@bxf6`3S;VFbP?849ldB`OKsA$qB~o*87^zuLRq)#C(W8xu&C$TmSZ}urqNa= zpc_HZp=tOg_?DsRzE7GMb7eP?lg}Lefseg1F(@wrPaVa`+s9oU{J&p%h8{oZ!V1p-w ZJc8cGaPT*XKZ5QpsfCRF0|-Lq{{ag|p`8E# literal 0 HcmV?d00001 diff --git a/app/core/auth.py b/app/core/auth.py index 10bcd41..f810b1b 100644 --- a/app/core/auth.py +++ b/app/core/auth.py @@ -28,12 +28,21 @@ class TokenManager: self._rate_limiter = RateLimiter(requests_per_second=1.0) self._lock = asyncio.Lock() self._client = httpx.AsyncClient(timeout=10.0) + self._authenticated: bool = False + + @property + def is_authenticated(self) -> bool: + return self._authenticated @property def is_vps(self) -> bool: return settings.kis.server_mode == "vps" async def _request_token(self) -> None: + if not settings.kis.app_key or not settings.kis.app_secret: + logger.debug("API 키가 설정되지 않음") + return + url = f"{get_base_url()}{_TOKEN_URL}" payload = { "grant_type": "client_credentials", @@ -41,15 +50,22 @@ class TokenManager: "appsecret": settings.kis.app_secret, } resp = await self._client.post(url, json=payload) - resp.raise_for_status() data = resp.json() + if resp.status_code != 200 or "access_token" not in data: + logger.warning("토큰 발급 실패: %s", data.get("message", resp.status_code)) + return + self._access_token = data["access_token"] expires_in = int(data.get("expires_in", 7776000)) self._token_expires_at = datetime.now() + timedelta(seconds=expires_in) + self._authenticated = True logger.info("REST access_token 발급 완료 (만료: %s)", self._token_expires_at) async def _request_approval_key(self) -> None: + if not settings.kis.app_key or not settings.kis.app_secret: + return + url = f"{get_base_url()}{_APPROVAL_URL}" payload = { "grant_type": "client_credentials", @@ -57,9 +73,12 @@ class TokenManager: "secretkey": settings.kis.app_secret, } resp = await self._client.post(url, json=payload) - resp.raise_for_status() data = resp.json() + if resp.status_code != 200 or "approval_key" not in data: + logger.warning("approval_key 발급 실패: %s", data.get("message", resp.status_code)) + return + self._approval_key = data["approval_key"] self._approval_expires_at = datetime.now() + timedelta(hours=23, minutes=50) logger.info("WebSocket approval_key 발급 완료 (만료: %s)", self._approval_expires_at) @@ -85,7 +104,8 @@ class TokenManager: "appsecret": settings.kis.app_secret, } resp = await self._client.post(url, json=data, headers=headers) - resp.raise_for_status() + if resp.status_code != 200: + raise RuntimeError(f"hashkey 생성 실패: {resp.status_code}") return resp.json()["HASH"] def get_auth_headers(self, tr_id: str) -> dict[str, str]: diff --git a/app/engine/__pycache__/__init__.cpython-313.pyc b/app/engine/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..809df2308156cd968f03e862a776e7c147fddc36 GIT binary patch literal 155 zcmey&%ge<81l?~UvOx4>5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iAi&ac`eo=OD zL1J=hOh9ExMt)vQa7li0wqt2Yer{q(CXk<4P!N-vm!6rI8WSI%nU`4-AFo$Xd5gm) dH$SB`C)KWq6=)R5s$vl1BQql-V-Yiu1pxDnCj0;Z literal 0 HcmV?d00001 diff --git a/app/engine/__pycache__/collector.cpython-313.pyc b/app/engine/__pycache__/collector.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e5f3074ed4cee8180f1ee1cdd10b7b314111ea1 GIT binary patch literal 4835 zcma)AU2Gf25#HnR`2Q=36h%oEC0ns+C90#ui7eNSt;m8bI)uwJ8zUlepve=(h`Lku zNV^K!)J=gxYSKDN;!0_P#t$uO_oYVLhddN$0yytsS%AdVZc;Qs;nW3W$Y@%$51qN= zQ4+({T@ZUSyR);qb2Hz}antQ~5NI#{{p@oYCn0}9#%N}pU^agU#AU)0o{5oZhB6VP zO)=B7iJGQa%4$3tGf#7r(`a+dGHs<+jpkyuX*;!Rv?b=4c2cKCTVt+iH+4ry%057N z+d)R(je0@^czc-ej)SK9m^bAeAP04ZP+!g&b=^iT%Baq?B+12eF)vF>)TG+7>7rQ7 zFNmrcdl0blqN2bokTyju7V}b00onz0X;CQ1xtvImKTY!)aVoDAWx51>`$C$|i$wwS zr3FQ#=V4a0XXHXb%z#Lyei-dhKPIy|3G8K(B9!3?HSr8(c{^_kljqFT%sY5iqd4Bl zn>EV9yLe8cth}4IXq1h&QhUx8^{5{0L{Az!-u8m>Ox;S2ZCOH?%{FaYo=K8|Hi2D^ zL{0I@YjA0SmLv&o4VN-2VqsQ69TD~L#8~QSna(SV>5P~uF{!bcGbM$cjn%ld+WKX zceLUiy?Uzb9sSTYs`oSqduPCL8x5W3yBLW-0SoVj7jT&*Ndg?Oxfw6ccwJd^Qf3#K zX*hfWEz{HnNRlz|@g!jH9PE9bxy;5n7m?T`yHneY#aNzbxV$m4BaF;5_^!2y-)My7oHi0y`^gMCkk+gueSi?jvh5@oIZcgs zoedtW`d!&4-f>**U>Xon%tI;*5-OX{W>rf@ zURcN%Rg0eeRW2)@E9F%CiEm`YMXV%LPNY<(s*OVSJ(teRN3A-nVWg?1>^aq;*-EPp zbgZ*bF(cEg%7_ZWC}JBl9p2pa+*vncPZPTBnC*eZiXUL%Ho#lW+g|k!l)VF0@1C-E z4}jW+x!2xxvu~u@H}U(viT9qV^qsDDonD^)Q+sc%v*(kx@Y;i~J^1?Y@{>0M{k87F zT31gk(6{#N>a$l)UGJ|19&O?}cCE!%<5vz}XDS^dwP0jTT9vK{*VC2YXsxq zugt+*=g3{JE$CUE+-M_i@1@C=$yXnHC0=uRE}dLC`AX~)SKFmiE2nC{gEe0merujp z&-%Avoo~e8zI8QS@g2Bl=iHtR7wHJy{R#;F?0+ zv6a{>Pu*+}{d>a-0{>B_!89**o^Ufi?watCUo#UP^KYC${f>dL_sNrPpl`U5x#3}u z_Bl>C*c;&oPH^l8goE)9I1WZWaIipU8juTWG5u)(?f}c{0M0W|;zGGAnIPm^)*DHa z0X401G=NVF_~qHLDG1hjYDhEy81}C>BakJQXCwgSb~5z{Km-Q~r3E}ExkQVK1JXtP zcFpMm9T-~{=ARlSBx&Z&4}o6E{1j~LByd;r9s)2G)WCv^>oo(Ta$0G4<~E)@YEyks z84fQ%_!pF64GZW|m{7UrOCnuTEwlMTQKYIJ5_dXN%%2w_@CpTb7-h_e8c~ZLP>;ii zzSN?rQ2;pPl2lYVoyAOuEW&hr*+%LV?yzf3*-uFbsf2``CDElWW{>f!a&F5d{{ut<9K2ZyFRRep=0hD!I zb6g*<1Rk$?@mKQ&*8QuF*SNbJ3HEGQh%d100@^qFFC_$iN;H!HmPnG>;d2OvO>NtH1kZJeq_DmG-kP@+J6+VY%iqD3tXvW+yobubw1*y0QdMjNTs7y};FitJTq*CYT6WsY=Ch@AHXh(-G1zbu)~C>Wgf^*$B{;J({b!@jcS7=AyVPoQax{G zMJ1zhvw5l%>4PZMLLbJFJS$W9C^PD7ZGSCz4#A?Pa@vQvumDL^F*0{wQ=~KtoJ1u* zg6<;u7?5{rW$KMo#WnDeyM5*BZ=9-j43#^EuD($2I8jYSzJU&;;lipul=NM^!d273-^L5@mPJzu?T~GsVa^ zBidFD{O*;~3nF~?Is^gWuT((VB?!-#(uMkrR}i$%(7bMXD72*Nd@Ul%`fC_=v0VSFiA9DI-5Gcnw*dloBq=w4et*L81_ zv2lUVJN#VTPPir>_2Us|OJFHDA_bt)-BSC~Q$5P_g>)e^CoU`vgI`fJlj=oGhayIV zPMXc_>^G+2I8%}FIPhB;$4TT*S=&@3+IES(3l$%1&xObx|=Ev?SChksSvVqYD-O9rJ%I7zQ`o$0HH{KKK zA6;0z_Rgpj+R!Q^r!Rg>c` z)0IC91JzJ;jqD=`=JB76PX1EG=e1cWCr`dGr%$WOq^PU8^GQQjXDDJOrP z{o!|qFDGh&=#`U~PA>I4bKAHdc<%3f+ZFy2|C_dNd?<4CyEPv9{1)ST?I0+A!=F6N z{I(~#9o=A(J?y6fP>Xy7n2S9Op|?kqhun*Y+`!oyO#cQg|35Gdf_BoRT>vK=EC^*9 zz_MmZ--libJ8)`+C56`(5Nv_hC_ro*h1h?U-2^cv9p^0AkhEt8WQceJqLkt$B)=-> z3>av|f+CePOdJ|_;V2RH+DscdxRb7L#5ln~FR-@5t^ESJ$1u>nuhMywdkHobog#`cwyhY=vXB%wiFm^20=&SL165=2IqTi3{(ROjuRB@B`D}) zKkWx3{~fD{brnsrn!8z$L8SxDJ<4|Ld*#?{7VP`?gTOb#dkP z!o!c=kF%J%CxT_jv)Q~I60LoCOG6_a>iEfbeuc-DYEO(={Re8b?bm&x{7&|Riqi&3}bn=1O+53zgpDD ziGiZmT7LDy>L-5@>X)xS{M{v!&0$rcHO}-~pr)`jNaC=AVykHI;HN;qWT#aFo1U4X z0yzrz0}B&utAG3qw*0vM$$P>f`_W15g!mFx7A`|cx%1h_!~`P7 zX}W~1SjIbnfk}Ac-K3*wHALsLjcSXBVQN3z+85A4ZDoTMwyVr`)%cEDc=u;~WpK1S zI9eG@mEj7XtonCUgFC7{JJ+%gSToll7|48%=$bKn^Mje>9|pF<4|xLDl)`pElq@^l3*Y}8^w6b zQ=YpyX+v1B>E|IX*jJ>YZghak%@~7&*tK_{qFS)QhN1xa0R_{Bo>yN{Xtv(c!|j@` z&NeytD5(B9+}aRy;9zgK;_Zg3#z!m*UgegECHL0&_ACBN{-w_Nm;C-U4)HzIf@wJ3 zc`%&Gv0}1mI7-L2nQhtZlmQ$$o5dv3&0W%L15!f`N*j{M=Ri@##Pa6OW+6M)wIK(` zG#bnh90`YVDwd&BriukCJR%KlUecwrk|xuWMw7I4F2$rIn4Lu0I#z-f`?JA1%=R`- zIRRyVM9|D)nU&a_9y{r|gO)lPCz#l>HKO>R%&6p3XACD{mV8ENDd^VR48ts=zOT^n zWt8{|^)I9S%V=;J9bQI9ANac7J%7*FH}826*m1Q~4m>^Yd*tyl9gjL`=VC@4wF!*p M+vgcKbAYS;a09g__cUg~AIbFy)wi8cvj#@azd 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 Y>Lrfryh|8N8%b?@&eQSZY9_(|04iUYzW@LL literal 0 HcmV?d00001 diff --git a/app/engine/strategies/__pycache__/__init__.cpython-313.pyc b/app/engine/strategies/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5771c35684cf2d3be595be6840aaefa9e9168af8 GIT binary patch literal 166 zcmey&%ge<81U+vevOx4>5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iWi&ac`eo=OD zL1J=hOh9ExMt)vQa7li0wqt2Yer{q(CXk<4P!N-vm!6rI8dF?Slvt9Qo|#%46Ca2KczG$)vkyYXe!9oVi4maGb1Bo5i^hl034kwp8x;= literal 0 HcmV?d00001 diff --git a/app/engine/strategies/__pycache__/base.cpython-313.pyc b/app/engine/strategies/__pycache__/base.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7aa2d8ca38cc848655ca895456613cccae72c61f GIT binary patch literal 2391 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 literal 0 HcmV?d00001 diff --git a/app/engine/strategies/__pycache__/periodic.cpython-313.pyc b/app/engine/strategies/__pycache__/periodic.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4b87641cd516e9c6a1534cd4c17487e65251151c GIT binary patch literal 3347 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 literal 0 HcmV?d00001 diff --git a/app/models/__pycache__/stock.cpython-313.pyc b/app/models/__pycache__/stock.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0726163745e68b2797326b0d098144ef70c3a8fc GIT binary patch literal 5198 zcmcgwO>7&-6<(6d|9?u9C|aie71MT9=f{Z?|4TAuk+tJ!>BNO8ELP--qD1Y=vrETv z6M*QYI{K1JPA*&&QgzXhN1Y0x1zK+jIMo70Q3OFDAcq20fx@T0H%l!knT`viC=I`z z{pQW=?Ckr#_eRhCeh&k`KmKrM@fQJx`3`Rmes-f_dHy?Se9dqSXUQ;W3$gTL+nTYa zt;9;(wu~*!5;koo_OydI(oW(`yNE08CT`l#W;|&x@uq#mNAK+!e>y+{{Y;D*WH?8h z;hed^S)1`Z5~QdLP4q6=nhULC8?;h^A#zd zwCauBWm$rG*>OP;^TvxA)?r*M&!0i#YbM7K3&#*EXCbx`*j2*j z?1N0s0sCS*YcYH|ma~6kB~H$fb8*hM05pAErjMKRfRCH{cn|qB=W%mB&i|2(c&Jyv z^zyKrwS&yr#w{0tQ6a7aMs?ySE=t11u4K1v=TterwC4p( zcPQ9a_At`28?f~&q_ixMReniaec?5B?yjPeS)I+x1#$0P@JoT8ORAL1!dzL@SxJD8 zV9U(hNm@w{&gm?O^FpPp>JFG`NmMs1q!;iPzQ%NO@Cj#icS+%eyjr?1!gK*eB=<$a zH%7h)HCzwA{H*TD6A`|6ffv*b=4LXYyLcX-%=5a3=a=O|rHpMK&)=&E?dA##UBI5JppjUrV_M=9kQ0I(w@({vI3o)%^O*lf_y=i%nD~*G|>hiCq&s ztHrJw;MLvuTN{pbvgxVa-npj5XR4{SOr4$CO`KYvs%5prcy)U1%Q`#0J9K6%wln;v z6Td(4hm+dS?P~gIsQ(#zn~WW-p*>fAfbLgwg|opw7UJ5;*&z}gX8gK=MNX3;F7R^G z==XqElOta4pvTAgpvP~H_d`zrB0K;(;^IOt;X#VG@TK7&qIl#b|1iZnOx(eBa#5H+ zLf74eIM+=&im_ym?z=@wdGUG)J`-8pi$a&~2jb*Ke!;-98&=XuVFA8xNrK>A7A4hW zR7p~2b!(|WBH)w)XG$D2#AIp$IKfSB@%DCz7% zskorq%JLVwJztiAPgWd&3ZXmhqvVM+9&aW)$td>V3y~9Ol4wq%p?q**>q9iB&`^4O zj4c}ZbQw;(y@2K-noDRdqq&0SZ8RABM&$eiTkjkgd^zPCFc`-}5XW6ZkEd##J6Wx3 zraHdnH6n8(we{|1dONyP&=OoV{Rm)gcVKM&!S=|GstshT*PjLwbv6sJy>74BwfH3n z_P6WorI&%8(Z`joiq>f;^&bW&)D$-qcboPh^uh)2!&gAF{yb-|q7CVqI%7=3P-&e?Owr}+kP>Jyw*APjC8Z@`R5#ZLqxzep8neZ?0l)_l=Y~j6aX2}m+p|P~ ztThOj?ohF6)q~CXKl! zn>ln^i>HhXn%W(GZ)?elC z;8wKPnBV-rG{#U5Lt&c96`39|)dN#-5Om1Um}aU)udwMAX{#~l6E%Ihs84|EK~q;8 zT#VvDszXgJ2{o7<=HkHYrhY_n9W$7@j{I75q8D@`${mMM2~#ItJL(OIeb za08^fIG7yLU2x){eBFBwmV^R9Z%b{&rT`8pn#QTt7!~F<9OXAjk3vDEr>Q)peTavL z)}Mc6By{hJK!jz5UsPm?Ov7k$0}WDh4;&ku?ZZ|-n!_3b%cmt@0fhl#KrNg##^rxhG4t zYg%mFVEj0g4IB5j<~AR0U(*KC)fw%X4^q5YU?1QiiZ`p0a4s@~73z^+ zlijcDhqB#9I-9i%7bDSPPcp7MughgjzI&*Qz+N6qys>MlxD6cDa5dbCNv*f zu|+Ef4Cx~Lr#~a(yM_Bj-pKm?e1#CG_Zzp46(Y}5&`KAQe0XK?Xw#KFG_%vaP3fxih58({SK1j>A`ER^#L;_|9-qNf`} zYa6N>mMm-PK7-f&ga_{m{{qJ)3ssZ<07hW5(EtDd literal 0 HcmV?d00001 diff --git a/app/routers/__pycache__/__init__.cpython-313.pyc b/app/routers/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65ea5075ad50f74462034bfb38ec707d060e01b6 GIT binary patch literal 156 zcmey&%ge<81buHJvOx4>5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~igi&ac`eo=OD zL1J=hOh9ExMt)vQa7li0wqt2Yer{q(CXk<4P!Lm;Us{q{R2&l@pP83g5+AQuPVymr&&av*p<*?jHVH+F>nK^NL%Rsm-7&j8#)9O9TLiZGZ7QrHkR zM2y%NF=3OkHb%`63$`e*DQb<_uuXx@QG3LJ9SUrTIwLi>CgQ@bNG-07xUoA@hwGF* zYqUPH2k!}@abp|eY@EH9;T*>e*fZ{lg;Ytlj-2xdqprtIZD`z~tqwC>Emspj*G$)# zBb66$!;~xJeoR*(hB$dq%*ni*$%#_PK5%rmWfsFQ^V6)vyjN_!o{Ld>s-J~e)uGGs+i(U z+xKIx?Q6Qy&egfPpQqs%efLF@8+MVrPUEO^@o1RpoY1La-YQmuIKzn%khNmn7uwIb zixfVMm(FD;DRip;Q3_MSubsyk11QdTp0l)Q-a=#Ag~qal#=HxSwHJxDL_j+))}%qz zxw8WD*TEmFl+9HN4QkWn^q`R#LZUrxi?wRI)j4jDwN+Od1lSG@uFilxP+e(|E>i=@ zr012LX|+4Mz(BsUgpJxdh(<3X2V^+cs=0daFTV)Tl$Gxa3)O(&K|bX8@}R@482G)faN%_oFndty`-I6odMfl zU1<5NC zvwTVzzqlYz=frWPh7IQB+>BB?$NAaWajX^6%##`nx}! zezs`?>Nh>dnWfaMUB?@sG-?^5p{20uQ#diG(U@AG5NFs%R~BQa#s#3b@F0D?>k*O-`%Lm_%ySwd&m3ZbqCFBw0ukx4J9L@!33dX=&nA36r#EBlUbr&ddo( zNp0aWf<#<83Ys_x&T~MUm%5%z<)F2zz*IWb0xi@G?R22mj%is4S#bx2X*nP!DU()7 ziCOg?iv-5`~Lx(UOXxw4y zclZzt#H2`Tgb~wTi7>K6trFbwsL@M1QcMS`u`~uzzlgqd*S|CW_WWII(H$(gI|}ZO zqWi$&NZISVJ#us8Zu~bFO5W~*xBH`bsppkKν#GliZr#h&4!_iV`q!|9p`hEwPsh?4=^ht(h9O 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 literal 0 HcmV?d00001 diff --git a/app/routers/__pycache__/strategies.cpython-313.pyc b/app/routers/__pycache__/strategies.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..368de5a2d1be4b33299bb515772c212726898dbb GIT binary patch literal 5430 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 TKXjj%n1c#i*IgDtCCmI1C2JSy literal 0 HcmV?d00001 diff --git a/app/routers/__pycache__/trading.cpython-313.pyc b/app/routers/__pycache__/trading.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..882fd04594a2f9901e70823ea288202216007c80 GIT binary patch literal 4760 zcmcf^OKcn0@$Hv?i64DQ%hoq75t|eoGqGhmuHsmbC|DM_W^Gpt2#Q=uR49^uy9z9| z2iXd0yFJ)KYe+8Ax`!T;o6(Dl=92VO6h#q5LABY!g^M;Qd?-pqDhl5^v&*GqR!-4F z`veW=&CHvf_vZKBw#VZnFn;p4%d>xQ5b|f7SVJ@sX8T7BA@_(xBxaPv8On4b&W^Hi zj&gCH@^OI*28|mP<2GtDaDLPtcTk6c3!~1si@FS49CgP%)MMbb(S~>MNS+iq| z)Gm30WR{<0Vx|D0O)t>lvsOt#$&YJlE1F@&k?Qh2KT@E!qDgA5`!aWs?ERBMQcIm3 ztuN5pTGM(!YJ1L(w&|vD$5XT*%;>I6u~b#N%$mNTw&Rta(D2q};nZlNIw!dF@mQY7pT`0>Rkg>2`x2My^%$%G~ z>&|p~u9TZEAns15ug+%*RZe3%J(Z`5TF4jWVhPwl*yE0Lnzq7cXghwZIh|I}P1$rt zRcU?_T%>?N5O~}0PxOH|2U3?x^lhb_$;zqm1$CxWOi8$d!FjbbXLvZ3DVI~!aIcc8 z9(|;|K)s;A0JwRj7r;%Tc>~KAG^b~IM02>8hc%yn>D;YXmdCV~;8Of9_j^7_yR2pV zyXzRR|9f{$+rtjsb2d}V$_2|OE%+fn0Nrk$bFoANNNYx5aPc6X1P2iu zLePl-rA)KC5yPDt097bH0I=@{-|~5L^A7iShfQe|Toj|m$Ft~rYq$YfCWP7k4OEUE z(g_=$uy`b~SF%d&CP|XuYa%rSsMXq8BLMU1J$U&&vSr^*3=D&IU$Td?VW)v zdWX5kjR|g2WG5Oeu_T+oeb)_%mAF{ddeFO%y%GoX|B1yfu!yhKGhyH|AsMhv5-%{4 zO|qY`7r7*pz=)_DNzPi;Nk9U_rEXZ&8Tf23v6Hd8uW?gM*fACs%n)mW1!joVl99dy zG#&Dvs_AS_XZm$Svjl9xvKW9J)SqEB5YrjTdi@TpxLN^oh&2bbjk)`0O(UU4CP*;cO4{v6ng9 z#eeL3!v~W^TQqAFkZiS|gBT$gtwyeX50L*?u0sBC|CWDDk}+aUG-8b%3v;{CV#FE* zpAiafa~suIz_2p|0_#E!v|qd1#JwVeIKqU}vGW)CTqv zm^0g-fKB%xs}g&CP!*d%dyQe`XF{+jyT7|q`&F6*+&$}umCcq`s0-xlRiO-oeUeMy zxz>$6-`S^vCdt4sA$v4(iCXpCRg`2biT&!SWml0;@&WQqL1Cldvj7?XEg?lADI~DD z)eWFxv8WqB-OolLfgw>hK(?)p1XG}58>|~Z#X?XwfQlWoZUD8jjs#Pn!U@+8pzhNp zYCZu=_LoTX)oTJDR7p_vaTTUR-Kf*MkHmk{D=`uGID7tggt(xaD{N8d1!!VZkaJ^U zx6bBsd-4^p^!t@{8Ucpxf}3PUm8Tcd`JC>|QW+*WxWnlVvt2R((l9C@WQRibbiq_4vqu1i=z z=~)DLTcc+H=%U#!bVgOsb=WehrA7DQ4c}m=s}+DkZjf)lzrsMCFOiCycp9-+{4n|b z3!CoVb$9R2lAC?8^}g6<-@tm`z((Jx4fpA#bAPn?G;i~&d&T`;aT8b* zj|yvDxOy|#zaH%WEO_+Syml=1X!OzOn(smAerU5jvfdu~tiAuEi#zQehjZ%y*sz7x zRESp)pM6OHRcvq+zw(?7oa^DCOlGp#(tJ_P0}V!2;cdKAp@IuwhcP zNx_2MY1|S_rX@ZG^tVs|uh!U8zTl5(zRpeG;dS5P4PRu_75RjZth*x56s%DdDMePPBGeb zdwC(3DZ)dGqAT?Vd?bF@_z%*;__sO2wpLicDn~7uD;rGfHY=LU zwhCeuKQe>YgSX_qhL aIDm$q0&U1WzE|5e%Q8`exrLbVNc$JPxQG}4 literal 0 HcmV?d00001 diff --git a/app/routers/__pycache__/websocket.cpython-313.pyc b/app/routers/__pycache__/websocket.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..523aa4cbbdba1bd07be0f098fbb462da705e7041 GIT binary patch literal 3899 zcmcf^ZA=`;_08_y_Xqgyfb*Haae&Pk8-LUv!3|9>HMtl9j&6H0HB{H*ZXt(Z7tJ32 zk}5grkGlC2)XFifVuh%d;`SG9DEW-sPZwZ#{MqLB$5bQ5bfL4gMflAT74da)CAQfoOR>={u1kN zYM#U@Zz-Kfjb0H z^{;9XzzeTD2O#rx5a(l-(uNWcC&v37LG8ZI1Dqrm_+FC}rS{YVIB<+}$i>gNl=;S7 zB^D(h&KtaNZ=kq$7_88{tpWP22AKttRiJZ?ISgERT+5>#dcFlP1RZ=m43UHU5DD|W zFEf2evN}PfL{d#b+=joI*&J?eiljGxv z*DPt&Zjz?Mf+nPs*`#I}lT0e=DQV!f zGuhV_c|gpf;v*ATWlT4(0TeP&RJ=M+GK;qHGnzdyNR?zdl~l77p9ePKr=G^{6F3Kp zZqM5zZ;ed(u5{+yd(TA|nzo*M^-rFr^X4Uvx3w&I`~^=a=Lt<8%zIi3&eprm)@2iT zn^)}Mtv_#Gv<3>+P|g~fKJYv1_D4J-mo4Dh@no^WzXAjqIQ<1@ea=}wZ7w*sE%Au@ zR6U5o-)wkEAeViS5Oq%Nao2+c!Rw(dFA>xA4jwT#fFOK>F!-!<*C}slPjhSqO37GoI7);E##myE2; z)7ZU#RZ0;0nbqhm<~E@6h@pz>uQcYLV;BFlU3GxY2r;k0uRMO%d&r~?`h5mntlEIe zBL;wyBg7t}TfYW?da?C&iuSfr;4w72k^j-tcw>itWBP^MIQX>leE~Oi z#s$&U#u+y?#*H_Qk{&cDD?~}}PxZbTL-8;0JC9!kKQD&mvQ>pPo*zRsCWIi=V+c3G zFz#tGD9h_d1!KMJWigh&*R{{#x$bB4++n~Im7~S~MW%6AJtbB2PK5E6rSu+r_{P=0 zU;6ODrK=C#x%Tj_YrzK>Xa4r{nPAI9A%Rr+Uet(6mGc73BoxrIenZSl*f# zR8urFC~0<;U3F6EgCUqVc1WOj+R9I7Qa8o93ia zO3Iv+^fXK>*qUh+^Pj5m!%~(8*;!Z$q%5*hR@-S6UE$fro4}Mmu4Lpa{Sq#)Afx^plMze0_wC+- zJ&>~p^7h(-P&+5oE(+BJA&?USQ|=WomVcNaXn&wp)+t8!H>RtGMQ ze1Bxx`vYZ?U)WsxUd<0{ru|p;<~O%Zn(uozPxa@$Eej1zAJn{GbES5M<{J)8b}a-O zKd`)Sx#E~PmJfDJ9$5@*F9i184eXmao)2^veBE=t?u9^OA+R$S*tzJheb4?Q`<2l< z+jEU$dB42Eb6cwa0bEt}GV7MCP+dFq?R?daXA*PZZs5So*?gd<;Om+5^=uF$-|5OV z9?Sdt%3_w7m`BY};eE6TOwT>?fOpSwB~-mimN|9`H&pc&!6%Qc;PV%}+j8D*Sf{wE z?p6A*IB`||>#-S}^@uXL?pd9PGP(YVDBG6RGN*msx^vO$e%tXq$DHrQo8I}(WBEwm zZ>`^W@|b@Hs!!FsXb5lmjqFQvpm}{ zYbJ=FMSAS*dhgVGw=dev&xSZg(?l5DtnY2HNB8ryd%0Kk!0bN4;Qc&)K2ztC=bOxKLRWdKFp1CFVku6;za!8uS2m=-c*0VrT;Cq(p*Z zya&yaRL{t%j8b%3%Mcc<$?*)uYHoEmb2hC43*EMqLZ!B1B%ti~LaSof)!!nu&8QEH zp}|}%iz$nj8jn9k#c)!QNUZrg!$Yv*p?Fs2u|I(o?4jt^))&T<^u(z26&gVtx-qCJ z?9e_4`6Glr2KVp5@mJ``LC0U9G;wMp~Erz*!4qQ;uw!Y|E2|6>cjzPF8r}Ld!Zxzlw7H1CuN!#Q*>R literal 0 HcmV?d00001 diff --git a/app/routers/dashboard.py b/app/routers/dashboard.py index e1e0258..861d629 100644 --- a/app/routers/dashboard.py +++ b/app/routers/dashboard.py @@ -3,6 +3,7 @@ from __future__ import annotations from fastapi import APIRouter, Depends from sqlalchemy.orm import Session +from app.core.auth import token_manager from app.core.database import get_db from app.models.stock import Holding, PriceHistory, Strategy, Trade, Stock from app.engine.scheduler import scheduler @@ -31,6 +32,7 @@ def dashboard(db: Session = Depends(get_db)) -> dict: }) return { + "authenticated": token_manager.is_authenticated, "summary": { "total_holdings": len(holdings), "total_profit": total_profit, diff --git a/app/services/__pycache__/__init__.cpython-313.pyc b/app/services/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6cf3df86ac3d778fd1aab9b4476070cf5a9f47e3 GIT binary patch literal 157 zcmey&%ge<81l?~UvOx4>5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~i2i&ac`eo=OD zL1J=hOh9ExMt)vQa7li0wqt2Yer{q(CXk<4P!Lm`T2z*qoLU?cAD@|*SrQ+wS5SG2 f!zMRBr8Fniu80+A7|60>5aS~=BO_xGGmr%USt}>( literal 0 HcmV?d00001 diff --git a/app/services/__pycache__/account.cpython-313.pyc b/app/services/__pycache__/account.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..84d7e58bb3446a74038369a9cd5ea5c54228d653 GIT binary patch literal 6652 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 literal 0 HcmV?d00001 diff --git a/app/services/__pycache__/market_data.cpython-313.pyc b/app/services/__pycache__/market_data.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9347c6e92fc6b933bac530cb61a40b7ec6a185ba GIT binary patch literal 8272 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 W?KWHw13hg(@ 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 literal 0 HcmV?d00001 diff --git a/app/services/account.py b/app/services/account.py index 24a0135..a9765b3 100644 --- a/app/services/account.py +++ b/app/services/account.py @@ -20,9 +20,17 @@ class AccountService: self._client = httpx.AsyncClient(timeout=10.0) self._rate_limiter = RateLimiter(settings.rate_limit.requests_per_second) + def _check_auth(self) -> bool: + if not token_manager.is_authenticated: + logger.debug("API 인증되지 않음 - 잔고 조회 불가") + return False + return True + async def get_balance(self) -> dict: + if not self._check_auth(): + return {"deposits": [], "stocks": []} + await self._rate_limiter.acquire() - token = await token_manager.get_access_token() tr_id = _BALANCE_TR_IDS.get(settings.kis.server_mode, "VTTC0311R") headers = token_manager.get_auth_headers(tr_id) @@ -71,8 +79,10 @@ class AccountService: return {"deposits": deposits, "stocks": stocks} async def get_orderable_amount(self, stock_code: str, price: int) -> dict: + if not self._check_auth(): + return {"orderable_amount": 0, "orderable_qty": 0} + await self._rate_limiter.acquire() - token = await token_manager.get_access_token() headers = token_manager.get_auth_headers("VTTC0830R" if settings.kis.server_mode == "vps" else "TTTC0830R") params = { diff --git a/app/services/market_data.py b/app/services/market_data.py index 98ce19d..6c9d88a 100644 --- a/app/services/market_data.py +++ b/app/services/market_data.py @@ -22,9 +22,17 @@ class MarketDataService: self._client = httpx.AsyncClient(timeout=10.0) self._rate_limiter = RateLimiter(settings.rate_limit.requests_per_second) + def _check_auth(self) -> bool: + if not token_manager.is_authenticated: + logger.debug("API 인증되지 않음 - 시세 조회 불가") + return False + return True + async def get_current_price(self, stock_code: str) -> dict: + if not self._check_auth(): + return {} + await self._rate_limiter.acquire() - token = await token_manager.get_access_token() headers = token_manager.get_auth_headers("FHKST01010100") params = {_FID_COND_MRKT_DIV_CODE: "J", _FID_INPUT_ISCD: stock_code} url = f"{get_base_url()}{_PRICE_URL}" @@ -54,8 +62,10 @@ class MarketDataService: async def get_daily_chart( self, stock_code: str, start_date: str, end_date: str, count: int = 30 ) -> list[dict]: + if not self._check_auth(): + return [] + await self._rate_limiter.acquire() - token = await token_manager.get_access_token() headers = token_manager.get_auth_headers("FHKST03010200") params = { _FID_COND_MRKT_DIV_CODE: "J", @@ -88,8 +98,10 @@ class MarketDataService: return result async def get_orderbook(self, stock_code: str) -> dict: + if not self._check_auth(): + return {} + await self._rate_limiter.acquire() - token = await token_manager.get_access_token() headers = token_manager.get_auth_headers("FHKST01010200") params = {_FID_COND_MRKT_DIV_CODE: "J", _FID_INPUT_ISCD: stock_code} url = f"{get_base_url()}{_ORDERBOOK_URL}" diff --git a/app/services/trading.py b/app/services/trading.py index 09831f2..7aec2d5 100644 --- a/app/services/trading.py +++ b/app/services/trading.py @@ -23,6 +23,12 @@ class TradingService: self._client = httpx.AsyncClient(timeout=10.0) self._rate_limiter = RateLimiter(settings.rate_limit.requests_per_second) + def _check_auth(self) -> bool: + if not token_manager.is_authenticated: + logger.debug("API 인증되지 않음 - 매매 불가") + return False + return True + def _get_tr_id(self, side: str) -> str: mode = settings.kis.server_mode return _TR_IDS.get(mode, _TR_IDS["vps"]).get(side, "VTTC0802U") @@ -35,6 +41,9 @@ class TradingService: price: int = 0, order_type: str = "00", ) -> dict: + if not self._check_auth(): + return {"rt_cd": "-1", "msg": "API 인증되지 않음", "order_no": ""} + await self._rate_limiter.acquire() tr_id = self._get_tr_id(side) @@ -86,6 +95,9 @@ class TradingService: async def modify_order( self, order_no: str, stock_code: str, qty: int, price: int, order_type: str = "00" ) -> dict: + if not self._check_auth(): + return {"rt_cd": "-1", "msg": "API 인증되지 않음"} + await self._rate_limiter.acquire() tr_id = self._get_tr_id("modify") @@ -123,6 +135,9 @@ class TradingService: return result async def cancel_order(self, order_no: str, stock_code: str, qty: int) -> dict: + if not self._check_auth(): + return {"rt_cd": "-1", "msg": "API 인증되지 않음"} + await self._rate_limiter.acquire() tr_id = self._get_tr_id("cancel") diff --git a/app/templates/index.html b/app/templates/index.html index e293c0b..2806626 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -41,6 +41,9 @@ #ws-status { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; } #ws-status.connected { background: #3fb950; } #ws-status.disconnected { background: #f85149; } + .alert { padding: 12px 16px; border-radius: 6px; margin-bottom: 16px; font-size: 13px; display: none; } + .alert.warning { background: #3a2a00; border: 1px solid #d29922; color: #e3b341; } + .alert.warning.show { display: block; } @@ -53,6 +56,11 @@
+
+ API 키가 설정되지 않았거나 유효하지 않습니다. .env 파일에 KIS_APP_KEY와 KIS_APP_SECRET을 입력하세요. + 현재 UI 표시만 가능하며, 시세 조회 및 매매 기능은 작동하지 않습니다. +
+

총 투자금

@@ -136,6 +144,11 @@ try { const res = await fetch(API + '/api/dashboard/'); const data = await res.json(); + + if (!data.authenticated) { + document.getElementById('auth-alert').classList.add('show'); + } + const s = data.summary; document.getElementById('total-invested').textContent = formatPrice(s.total_invested); diff --git a/data/stock.db b/data/stock.db new file mode 100644 index 0000000000000000000000000000000000000000..e2651fa9e5b2d97601399111ae6c65e9a93994c1 GIT binary patch literal 49152 zcmeI&-)`Gf0LO7V>+-J++ax3_7nM$LXe$t`Y`v^TmU>W;CL>D`6B3q5+}2wXXSP!o zAuhVXE_eyrF5OCF%F!5!GnT<0WlK^5Fs-$+TEhV5J8*l`D@ zFYYO~m0V8wLNtmZG2cmBBbd*#>4 zy|v%Uf0chJUKEGtV+}#$^&{6gw*4g=PFEPN8O-O4 zyB&{u!HS*{%<}s|lRF28y4cqr)s7k+Q9o+8^=8Ki3!ijqhmWNbdfYxZthJwt&-JHb zD{~xNXo+$*uACQ zC-wQx-MLPGyNik8Pew^JK3=F5H#aq4Bhxhpp7G3chL?4A=a1&NJJM>Vg1GdgtV|1v^Ep|l7iMHy zDCgsTzMxgB>YI9$k7=49X`DTq;$=XD4sntBK^ybP8(_d zIXa$i`eX01NsA+3boEY*U;o#h7)hgshqSkew|UVZ3zni1y@>i#h8{1zp33FpGq=;%+6cR$Qmx0cTVk-()3|dF??pZ8n}Z*4UZ8-w3`@6~k{;mGI?u_`(AL1Q0*~0R#|0009IL zKmY**vMNvx8^-VdtadN$LI42-5I_I{1Q0*~0R#|0AQIsIKS4kM0R#|0009ILKmY** z5I`XN0^I-4ejn391Q0*~0R#|0009ILKmY**`29}?5I_I{1Q0*~0R#|0009IL$i4vo z|IdCO(?SFgKmY**5I_I{1Q0*~0R*`JPX-V`009ILKmY**5I_I{1Q5u+0Qdj1-^a8N z0R#|0009ILKmY**5I_I{?*Eek1Q0*~0R#|0009ILKmY**vM<2>|LpfMEkpnT1Q0*~ l0R#|0009ILK!E%IWB>sK5I_I{1Q0*~0R#|00D