From 5688cfe2a29767e14f650a6caac323b34859c078 Mon Sep 17 00:00:00 2001 From: shinalok Date: Thu, 23 Apr 2026 15:53:04 +0900 Subject: [PATCH] initial commit --- finestock/__init__.py | 12 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 636 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 863 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 984 bytes .../__pycache__/api_factory.cpython-312.pyc | Bin 0 -> 1722 bytes .../__pycache__/api_fcatory.cpython-310.pyc | Bin 0 -> 654 bytes .../__pycache__/api_fcatory.cpython-311.pyc | Bin 0 -> 1244 bytes .../__pycache__/api_fcatory.cpython-312.pyc | Bin 0 -> 1438 bytes finestock/__pycache__/path.cpython-310.pyc | Bin 0 -> 1123 bytes finestock/__pycache__/path.cpython-311.pyc | Bin 0 -> 1685 bytes finestock/__pycache__/path.cpython-312.pyc | Bin 0 -> 2151 bytes finestock/api_factory.py | 37 + finestock/comm/__init__.py | 1 + .../comm/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 169 bytes .../comm/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 193 bytes .../comm/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 178 bytes .../comm/__pycache__/api.cpython-310.pyc | Bin 0 -> 2540 bytes .../comm/__pycache__/api.cpython-311.pyc | Bin 0 -> 7694 bytes .../comm/__pycache__/api.cpython-312.pyc | Bin 0 -> 5014 bytes .../__pycache__/api_interface.cpython-310.pyc | Bin 0 -> 1024 bytes .../__pycache__/api_interface.cpython-311.pyc | Bin 0 -> 3115 bytes .../__pycache__/api_interface.cpython-312.pyc | Bin 0 -> 6435 bytes finestock/comm/api.py | 107 +++ finestock/comm/api_interface.py | 100 ++ finestock/ebest/__init__.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 185 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 215 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 192 bytes .../ebest/__pycache__/ebest.cpython-310.pyc | Bin 0 -> 10931 bytes .../ebest/__pycache__/ebest.cpython-311.pyc | Bin 0 -> 815 bytes .../ebest/__pycache__/ebest.cpython-312.pyc | Bin 0 -> 640 bytes finestock/ebest/ebest.py | 6 + finestock/kis/__init__.py | 2 + .../kis/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 213 bytes .../kis/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 259 bytes .../kis/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 221 bytes finestock/kis/__pycache__/kis.cpython-310.pyc | Bin 0 -> 290 bytes finestock/kis/__pycache__/kis.cpython-311.pyc | Bin 0 -> 11279 bytes finestock/kis/__pycache__/kis.cpython-312.pyc | Bin 0 -> 10342 bytes .../kis/__pycache__/kis_v.cpython-310.pyc | Bin 0 -> 293 bytes .../kis/__pycache__/kis_v.cpython-311.pyc | Bin 0 -> 4314 bytes .../kis/__pycache__/kis_v.cpython-312.pyc | Bin 0 -> 3900 bytes finestock/kis/kis.py | 206 +++++ finestock/kis/kis_v.py | 83 ++ finestock/kiwoom/__init__.py | 4 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 273 bytes .../kiwoom/__pycache__/kiwoom.cpython-312.pyc | Bin 0 -> 37135 bytes .../__pycache__/kiwoom_v.cpython-312.pyc | Bin 0 -> 964 bytes finestock/kiwoom/kiwoom.py | 851 +++++++++++++++++ finestock/kiwoom/kiwoom_v.py | 13 + finestock/ls/__init__.py | 2 + .../ls/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 254 bytes .../ls/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 216 bytes finestock/ls/__pycache__/ls.cpython-311.pyc | Bin 0 -> 36400 bytes finestock/ls/__pycache__/ls.cpython-312.pyc | Bin 0 -> 36815 bytes finestock/ls/__pycache__/ls_v.cpython-311.pyc | Bin 0 -> 1078 bytes finestock/ls/__pycache__/ls_v.cpython-312.pyc | Bin 0 -> 863 bytes finestock/ls/ls.py | 859 ++++++++++++++++++ finestock/ls/ls_v.py | 11 + finestock/model/__init__.py | 5 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 189 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 249 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 438 bytes .../model/__pycache__/flag.cpython-311.pyc | Bin 0 -> 1041 bytes .../model/__pycache__/flag.cpython-312.pyc | Bin 0 -> 905 bytes .../model/__pycache__/price.cpython-310.pyc | Bin 0 -> 1118 bytes .../model/__pycache__/price.cpython-311.pyc | Bin 0 -> 1900 bytes .../model/__pycache__/price.cpython-312.pyc | Bin 0 -> 2932 bytes .../model/__pycache__/trade.cpython-310.pyc | Bin 0 -> 1875 bytes .../model/__pycache__/trade.cpython-311.pyc | Bin 0 -> 2568 bytes .../model/__pycache__/trade.cpython-312.pyc | Bin 0 -> 2797 bytes finestock/model/flag.py | 20 + finestock/model/price.py | 72 ++ finestock/model/trade.py | 65 ++ finestock/path.py | 86 ++ 75 files changed, 2543 insertions(+) create mode 100644 finestock/__init__.py create mode 100644 finestock/__pycache__/__init__.cpython-310.pyc create mode 100644 finestock/__pycache__/__init__.cpython-311.pyc create mode 100644 finestock/__pycache__/__init__.cpython-312.pyc create mode 100644 finestock/__pycache__/api_factory.cpython-312.pyc create mode 100644 finestock/__pycache__/api_fcatory.cpython-310.pyc create mode 100644 finestock/__pycache__/api_fcatory.cpython-311.pyc create mode 100644 finestock/__pycache__/api_fcatory.cpython-312.pyc create mode 100644 finestock/__pycache__/path.cpython-310.pyc create mode 100644 finestock/__pycache__/path.cpython-311.pyc create mode 100644 finestock/__pycache__/path.cpython-312.pyc create mode 100644 finestock/api_factory.py create mode 100644 finestock/comm/__init__.py create mode 100644 finestock/comm/__pycache__/__init__.cpython-310.pyc create mode 100644 finestock/comm/__pycache__/__init__.cpython-311.pyc create mode 100644 finestock/comm/__pycache__/__init__.cpython-312.pyc create mode 100644 finestock/comm/__pycache__/api.cpython-310.pyc create mode 100644 finestock/comm/__pycache__/api.cpython-311.pyc create mode 100644 finestock/comm/__pycache__/api.cpython-312.pyc create mode 100644 finestock/comm/__pycache__/api_interface.cpython-310.pyc create mode 100644 finestock/comm/__pycache__/api_interface.cpython-311.pyc create mode 100644 finestock/comm/__pycache__/api_interface.cpython-312.pyc create mode 100644 finestock/comm/api.py create mode 100644 finestock/comm/api_interface.py create mode 100644 finestock/ebest/__init__.py create mode 100644 finestock/ebest/__pycache__/__init__.cpython-310.pyc create mode 100644 finestock/ebest/__pycache__/__init__.cpython-311.pyc create mode 100644 finestock/ebest/__pycache__/__init__.cpython-312.pyc create mode 100644 finestock/ebest/__pycache__/ebest.cpython-310.pyc create mode 100644 finestock/ebest/__pycache__/ebest.cpython-311.pyc create mode 100644 finestock/ebest/__pycache__/ebest.cpython-312.pyc create mode 100644 finestock/ebest/ebest.py create mode 100644 finestock/kis/__init__.py create mode 100644 finestock/kis/__pycache__/__init__.cpython-310.pyc create mode 100644 finestock/kis/__pycache__/__init__.cpython-311.pyc create mode 100644 finestock/kis/__pycache__/__init__.cpython-312.pyc create mode 100644 finestock/kis/__pycache__/kis.cpython-310.pyc create mode 100644 finestock/kis/__pycache__/kis.cpython-311.pyc create mode 100644 finestock/kis/__pycache__/kis.cpython-312.pyc create mode 100644 finestock/kis/__pycache__/kis_v.cpython-310.pyc create mode 100644 finestock/kis/__pycache__/kis_v.cpython-311.pyc create mode 100644 finestock/kis/__pycache__/kis_v.cpython-312.pyc create mode 100644 finestock/kis/kis.py create mode 100644 finestock/kis/kis_v.py create mode 100644 finestock/kiwoom/__init__.py create mode 100644 finestock/kiwoom/__pycache__/__init__.cpython-312.pyc create mode 100644 finestock/kiwoom/__pycache__/kiwoom.cpython-312.pyc create mode 100644 finestock/kiwoom/__pycache__/kiwoom_v.cpython-312.pyc create mode 100644 finestock/kiwoom/kiwoom.py create mode 100644 finestock/kiwoom/kiwoom_v.py create mode 100644 finestock/ls/__init__.py create mode 100644 finestock/ls/__pycache__/__init__.cpython-311.pyc create mode 100644 finestock/ls/__pycache__/__init__.cpython-312.pyc create mode 100644 finestock/ls/__pycache__/ls.cpython-311.pyc create mode 100644 finestock/ls/__pycache__/ls.cpython-312.pyc create mode 100644 finestock/ls/__pycache__/ls_v.cpython-311.pyc create mode 100644 finestock/ls/__pycache__/ls_v.cpython-312.pyc create mode 100644 finestock/ls/ls.py create mode 100644 finestock/ls/ls_v.py create mode 100644 finestock/model/__init__.py create mode 100644 finestock/model/__pycache__/__init__.cpython-310.pyc create mode 100644 finestock/model/__pycache__/__init__.cpython-311.pyc create mode 100644 finestock/model/__pycache__/__init__.cpython-312.pyc create mode 100644 finestock/model/__pycache__/flag.cpython-311.pyc create mode 100644 finestock/model/__pycache__/flag.cpython-312.pyc create mode 100644 finestock/model/__pycache__/price.cpython-310.pyc create mode 100644 finestock/model/__pycache__/price.cpython-311.pyc create mode 100644 finestock/model/__pycache__/price.cpython-312.pyc create mode 100644 finestock/model/__pycache__/trade.cpython-310.pyc create mode 100644 finestock/model/__pycache__/trade.cpython-311.pyc create mode 100644 finestock/model/__pycache__/trade.cpython-312.pyc create mode 100644 finestock/model/flag.py create mode 100644 finestock/model/price.py create mode 100644 finestock/model/trade.py create mode 100644 finestock/path.py diff --git a/finestock/__init__.py b/finestock/__init__.py new file mode 100644 index 0000000..710e589 --- /dev/null +++ b/finestock/__init__.py @@ -0,0 +1,12 @@ +__version__ = '1.0.1.0' + +from .api_factory import APIFactory, APIProvider +from .model import Price, OrderBook, Hoga, Hold, Account, Order, Trade, Stock, Index, TRADE_FLAG, ORDER_FLAG + +__all__ = ['Price', 'OrderBook', 'Hoga', 'Hold', 'Account', 'Order', 'Trade', 'Stock', 'Index', 'TRADE_FLAG', 'ORDER_FLAG', 'APIFactory', 'APIProvider', 'create_api'] + +def print_version_info(): + print(f"The version of this stock finance API is {__version__}.") + +def create_api(api_provider: APIProvider): + return APIFactory.create_api(api_provider) \ No newline at end of file diff --git a/finestock/__pycache__/__init__.cpython-310.pyc b/finestock/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6e9606659e4b0fe51cf074f33bcd8b1ed3722b14 GIT binary patch literal 636 zcmY*W%}V4z5bmGE$z*0=5g#Deco+p`4Niwvp1eFdU!C-q5MMv$aX=S6)+Z-vG@)Na!+qCtcU=Lj;_+l zR3;bFG)h;AE)qLcP10DM&yqq_S(VEK@DnJuc$ktltc|K{+vEJBiSs99B0%uwbz^ty zQu~YBfh~XwYO36D#ac*R0CC_RF#6{q^o!9bvS<{79(*yGh)T71N=c6<>9jLLM z>nifhvG1oE$0`fuDICP;Of_wX^QQiS$YR)%acZDX@Zm_((Eha&zD zD~KQ-6!Gl;k%b*pDsEa)8NdexIx)K()9LSx8R+d@?t$1o*>X!P*D}BXX&C@=hZrLjXR=i3Z zS^B0)jbMixOa?z!_acu5mdw@Wqh`CqXZahP|6IkuBM>wsxAv{hfSPY8OZ+G_qn?=# z{KQPs$Q_zJKXgKun$V*O&N;R%iK{X5!;~9@EH4rA1Ikmzzn`|xMfL;5$G@64B4%_LwKTOY%# z3L(GHIWh1HJ8Z+SO?<+LPkkk#vBH#CWhzBqjkH*2dR$_q*kDFnX63lTDzVAT*kV>( zW!1RGYH^*_;~_Rg$qMncx0DV0`W9hl{8FH{72nv>*hsJJR5py8jfn=le<hC7Ut9ZL~`SiVy^_IPd?&`8#rmn;lKwzrFk{ z6`ic>dI9hD^3^n1h9p6e?$nI)FbQ+c8{M89De?`Hg-JUV=OG)4lbr=@k{?=Sr+VvC z=fjiFS9Z*)Z`!mt2LV|H8F;sUcHW?82%#2*9@k-VT92m#H6YNp7vw~fCF0OIM>!UJ z7{U$}7huRRw=Kqj0+$Qmab=?bD9{?)v+P~Vesk%o)!5M*MSXM5sK5rKg>|-%@7t-v z)Z^3-qW`=U(%{^UBF+o3j;k8O(omVA(xdVn*kCOR?uyH>hAm{1fPIxx`n^8BY5gV> jKS{J#Ji8=1(1^97Y#Ij|r89KDMCpx}kN*-FilqMleLUWj literal 0 HcmV?d00001 diff --git a/finestock/__pycache__/api_factory.cpython-312.pyc b/finestock/__pycache__/api_factory.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ebbf082ab6e0d5930f92ab67288e20ee910f39b GIT binary patch literal 1722 zcmaJ>&2Jk;6rcU}$8Or#HA!$?0up@9TF> zix3$0e?OK=A|Zd_VmzdgFuV@JF0qKkRfxkG97lL^n^^oKVhJ|4Cj{pVo@qtUN_+fB zCp2YUd5R02^Tl+v;S__N0J+g(cnO4EViSY2h{0Q&Ay~X2T7n^2q9I$7p;)pJvJ@lS z4C!G%`pMn(yVU)zWZBf0(l^q%&wPF(=L;LTyf0+dbG`_W_vOs`7n_@Rd?lFkSv}&1 zOtV^WY}523rs=p=qk^((n%_1Gm0*X;@Eocjgb>0A=qMURPyv1=LqPKK%FXNd9=NpZ z)e1%1tTpRjyVZMJrK;`K-D3F_sZcAKTZLlXrOnH=CPn8mK<|0)06Zc+HQrA5!dm-A zFLI`RGl1x9`_rD5Xy5A1%zb~G&W?FU{5T2$8NLPEQ$N~*#o6K(51|tGUN#oiMGw3z{e;Y8xy_=e7PkszHkD6<`@~46TValIpLPb_!G|^ zd;hsp_%iULEs61^6Yifsg|AdJXQm}HzI+1z!YTX^@Xxjs##g5N`rBiU72X@g`3Fvq zWS`;!Dilfy5?AL~?!$yJ0=1ASd04+N;|8&c+(2=g>uQG3%JQ@?RAu*$dk+LLYhPk#+{x=btGS? zH0(5`F4aRcf%=K*z$`Py>@X^_Jp8A{(V#JAr%3HW-8P{N9ww%Bae1oNkoX%woBSD_ zd-CC7a_Jzs)Qv6;)QkQ2eCO&;wx=yT`SR!ckMDQ2_Xg^Pemv26exD+ z2dUMrwmQ%*_0J_c>-$>j@7VeM)YU&?Yy0XNYZzHwruYjKbDo|7@Kvu~sF#Y44Gqf* zLWI48o<(3y23WwdVwYUGqOo!(K@M literal 0 HcmV?d00001 diff --git a/finestock/__pycache__/api_fcatory.cpython-310.pyc b/finestock/__pycache__/api_fcatory.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..324e9174abd746177a3cd084c904a42061af7297 GIT binary patch literal 654 zcmZ8fO=}cE5Uu{0bsRQ}NCG+LI+qnZcn}da#2~^F2<9*Zns&Muhn+9Gdk7mmx%o5x zrM`OdFXWV}adlS<>QS${=5B(iS;$s2S(M=)Sm4Gk+8qnOvc z5rt@EAsL*55k}sDksDqlCb@)s@(UFqXE8Z@XMM84F?)OQ;k_afab&Z=woD*9 z3Y=KL#y9Lc%aes82}iv+iEw-Psr5tOcP?0SqWkJ3tb3cw|H@)^rR%|-IoCNHH^o9l zg+IWhsB-_}^yT$U=T^SgrB(epeCgWjW!2(Rx^lG_(TB@Y({S^C9kbF|9jwB1f;xVN zkn#yj+1B~((QX~{UG*YQVyaZD8>>{zlxjLN)D%yZ`a0-(douMwhpKEWc4QpAgt6g0 zcuYCEoYvtd2q|zjV`FZSPhw&};~3*YqF7aax4f}rPl{IINH3 za>&t0ARawcq&NSPZRjDa6b13%EhV0M^3A5n8g2V__RX93e(%kj`OPOmh$4`0U%zbM zVi5X47X#tWj9vxqHVT>5`80lOM>HI!a zJ%GP2azqbhkQDxfVJ%^g-*{lyj>LFOD&w)KOcqRbD{p%osH`?fqw}Cf?><|~e6vqv5W0&IiAue_?&Jlu`9@>XHI2hi} zTP%gsl$VXnGeTbgp$7m`HZwrVW+uBk`MhXX%H4^D#+dM`mD z3DG4zn-|ThX2sYbq(p%E2xKZW)23T5o~}N;o7*Xof?ZZMLn&9Cm!)EEJ70w7QPK*3 zSu}53Qz@s*<*FCeh@m=$0`A>JK=*RsrXAQdPT&t%$LgK?@TbOf&xnnWWNH&x8E$U0Og?U#1+*D(wxl&JE>3$Czm zEKWC8o2&IFKjX9R+?~UC@+h8k$CEwi1EnS2izrG_wG2h^qKaab^omLCaYcDmQO&c+ zxb3J;Ub76i44uRw^D`nMOk{#8`VxFa8M1GK+8cfyexl0={7M&rI$=>LUdsgz-`lT5 zi=?G7k2hYxRr~CDjA;i9dTb$Y2Q>C~ouOL~J2{Qq>z`nq%tC+?YF9yZS&VT9&A6kZ dgQnf{Mh9JY&l_FtDu#pnuTH4%_c;N-wLg0B5~TnD literal 0 HcmV?d00001 diff --git a/finestock/__pycache__/api_fcatory.cpython-312.pyc b/finestock/__pycache__/api_fcatory.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3cc48b241f4e66d410909a596e9a0b0117a9b3a3 GIT binary patch literal 1438 zcmZ`(&1(}u6rb6Td~A}|CicVDLJ&b0+n%&q6~SthVhuvHhgDqG%vNKwNjkexA|9&r z(4){pDF}L%{v{$vt%sJSQb9cwFSRKMJ^9{#n6}Ud`{vDi^P4yGdvEqrDy0yR&-Ta1 zzXd{m;3O0|Fq)HKED%N*Efb4sl%g$^1xwUKOVT87E|z60qD8Ez7KORQjn5fB2(jrXOqK$|ebrVkPY37YH1(Q7h?_MZ>A`#!#)n z6SxtoD13Gws9Ca>%*^E0;;EU@HKl##ny(TaudeeUkBOp#GjniQBY zD=k3;MUxw%D!B@1UMs1{IF40*=IxG#Rqi{M=#FNuR*Po4(F4d*ycorbF@ zPS{=~%vsovjO@kuc7Q|*JUIjz3*)UZK*mGQAxQ5rglqgUKw=?1IRuF{J|F?IBXqZr z4%WvLMwbF6UsF}czv#JFvFo*3l{+TO0{PjHa8>bJ!0Rt0`(TqHU0$bgq{mezjB?$~ zabD#r<=x=2F;SKu^`b>?8jh($NndofYCP0(w+XXkGDE`3p7Nx`bF`EzsQhq+e!2fy?WuA~OnGu`vU zbGLt{k1zM1|C+wAoV?&QZ(faXJOV$03Mq7xwqrP@qGiI9VLlnX7)+z`8Xm94yF!H( z2_K5wu_Ywsk@tPu1cs(aBI!Tq@No#qt4UWjAHg%^$gV4t?H0qXI90N_$FN7PWWsNU zV>9{Z@N(`y$8PMh|A)B3v#<`iw@-oE5-FwMNY4i8-XOidNav0u)AXB@I|PQmZEbX9 Jn}G5T_79^{9!3BF literal 0 HcmV?d00001 diff --git a/finestock/__pycache__/path.cpython-310.pyc b/finestock/__pycache__/path.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f5c6fdb9156b32b08d9f1ecf716cdc2957b86e6f GIT binary patch literal 1123 zcmb7DU2obj6iosN1xmZ_d#~HePz#}5A39WZqZFx#jUb@igsMeut~)GBoH_?=dwTsKB z92;`(!Fj9`gNH3G`$#e0}|y zJ;H|}^?I0#d5Q+SyJmCR!@kH|GKPnei*(4P%J#a5agioI@ZgdxO=kLt$xU*AIJy!e z&-3XOp$sq+OOusT;%q9icAVFxYCTF$diPB$GM&<1dpEM0m3^mHiOlkDso9RqYJI2j zHcC3pol0}tacWVbR4zMvbvZSTMX9mEeN}C>#nYP(^(e!L_(SPaANyPy)h{X!#N*M# zr7-^|$=Yh6P`LJE>`p^0{9;c-Yz<@$Y@mI)Iq`B6Pll`Z$$)ai3H3vp_$LFxu(XIA zpXCV0{jr+?BOVs{8S6PiK>YXDix1H;@})le5B=EgoQJ}$crimdJ2fBh=~Qn2@oy;X z$!v4uf6C$0ASaJNHpz5(BbcEera;~vjhPDO6eJZ`3K9yGSghPA-jL5Ijw)a_7MYc8 z>3kHckuZu&*so{forGK2uC&^2$8>AemMgjH_T4DumKs&JQEKnHA2hb8zN+A>T`nGs zDC>m*@~|76@hNs2WQdTW}m{~7;mYh!%Yc>{`+HnHWqG1tuIDe R#(u;N>$_!Ix}~SS{RDokUW@<$ literal 0 HcmV?d00001 diff --git a/finestock/__pycache__/path.cpython-311.pyc b/finestock/__pycache__/path.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..583dbb217329d8a46c3f16ac95c855d6f8a79558 GIT binary patch literal 1685 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 literal 0 HcmV?d00001 diff --git a/finestock/api_factory.py b/finestock/api_factory.py new file mode 100644 index 0000000..3866190 --- /dev/null +++ b/finestock/api_factory.py @@ -0,0 +1,37 @@ +from enum import Enum + +class APIProvider(Enum): + EBEST = "EBEST" + LS = "LS" + LSV = "LSV" + KIS = "KIS" + KISV = "KISV" + KIWOOM = "KIWOOM" + KIWOOMV = "KIWOOMV" + +class APIFactory: + @staticmethod + def create_api(api_provider: APIProvider): + if api_provider == APIProvider.EBEST: + from .ebest import EBest + return EBest() + elif api_provider == APIProvider.LS: + from .ls import LS + return LS() + elif api_provider == APIProvider.LSV: + from .ls import LSV + return LSV() + elif api_provider == APIProvider.KIS: + from .kis import Kis + return Kis() + elif api_provider == APIProvider.KISV: + from .kis import KisV + return KisV() + elif api_provider == APIProvider.KIWOOM: + from .kiwoom import Kiwoom + return Kiwoom() + elif api_provider == APIProvider.KIWOOMV: + from .kiwoom import KiwoomV + return KiwoomV() + else: + raise ValueError("Unsupported API provider") \ No newline at end of file diff --git a/finestock/comm/__init__.py b/finestock/comm/__init__.py new file mode 100644 index 0000000..f77d506 --- /dev/null +++ b/finestock/comm/__init__.py @@ -0,0 +1 @@ +from .api import * \ No newline at end of file diff --git a/finestock/comm/__pycache__/__init__.cpython-310.pyc b/finestock/comm/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb8301b4af0e1abdc8f0c8b7c96913d02ff71934 GIT binary patch literal 169 zcmd1j<>g`kf{!`+X+l8yF^Gc(44TX@8G%BYjJFuI z{D35LVnOCgh9VZA7?}8F;cOLCo?nz*T#%TY8edRZl98Vmla`s6T3nK!oQ=dv&d<$_ hiI30B%PfhH*DI*J#bJ}1pHiBWY6r5Q7~}#T1^@?cDHQ+! literal 0 HcmV?d00001 diff --git a/finestock/comm/__pycache__/__init__.cpython-311.pyc b/finestock/comm/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f436443cb5c3d0d43edaea3679d83c7a5b24a5e3 GIT binary patch literal 193 zcmZ3^%ge<81fO#B(}aNZV-N=hn4pZ$d_cx@h7^Vr#vFzah7_h?22JLdj6gw6##@Y9 zen65ru^@9L!)K6&Ulz_*G3EJ1*~JBk$*J)Ll_eSZc`<33d8x%E`N`QxtmORM+?e?I w%)HE!_;|g7%3mBdx%nxjIjMF<96*CW78HvEi4V+-jEpxJgf5_>A~v8L0Q71xlK=n! literal 0 HcmV?d00001 diff --git a/finestock/comm/__pycache__/__init__.cpython-312.pyc b/finestock/comm/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d71030e4fb78c3293ed32e853de633ac5972a67 GIT binary patch literal 178 zcmX@j%ge<81fO#B(}aNZV-N=hn4pZ$d_cx@h7^Vr#vF#VOpFYbOq$Fu8G(YDjJFuI z{D35LVnOCghR+}kzbu@sV#@Q2vWp86lT+ghDoZl*^J3C6^HPgT@{_ZXSjqXhxiRta snR%Hd@$q^EmA^P_a`RJ4b5iY!IDiI$EGPysJ}@&fGTvnnDq;h206;D+VE_OC literal 0 HcmV?d00001 diff --git a/finestock/comm/__pycache__/api.cpython-310.pyc b/finestock/comm/__pycache__/api.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d55ee93364a0cc98a55efbb36ef5200938d1e823 GIT binary patch literal 2540 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 SlMe-zfgc5Zwlk`#_;o2ZIz3J zu4Q2hT_4tt8d#xEdJ1aY5$^)CByxi+tMf`_nTxZm(sElOA7=10FZU6H?U*6L3BnVE zQ-tRT_p9{`|38D@0ebTcU_cm+DdSxqQ=c|@(Fu34-9enmLO*eSGZ|^N`ropsh Ha?1Y#j3mgA literal 0 HcmV?d00001 diff --git a/finestock/comm/__pycache__/api_interface.cpython-311.pyc b/finestock/comm/__pycache__/api_interface.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed1bd48e1ee366b1581b5a7fc99789388e4b2daa GIT binary patch literal 3115 zcmd^BOK;Oa5Z?78P145c6M4c8;;T6y>Sq)i61Wz@`85hVwVQSqU$d>tp;~^)90*A1I^=& zs)vu3eqO(Qb@{0)>R!vTx!GEWXf~ItwI=s`*RCf8vZ?fKSvpu1Mv zU+v_P*t{HLp7pXIo|7-e?8oeE4>{RUQsCr|qy;`Jav;j87=Gu8S9Lp6l1@K}I|>6u5rnOPh}vHHC?Ox+ck8_A;SLdjakORG zHupSJu6AZ(3Q4#C&m>4_5d-M=NTRESBsAM)Jjhf9vwY}%7kdvk1g~tlCHENJvf8lM zI)k!LJ>_SR65gf z9wyME!=nQ)0k#XBgi3-=0zE!7Iz=}M`foing&rDE>l}=|G_X?cqqdYPV=rO7Hx1$* zT;Rd_yIsnHkizN-N5mf3Atu9!WDrPSjaLzg`)!~W>FkLQ9^MH#R(KAQDAh1^zeBcT|{vi zL^t%de8Y(hq9|~G?+v^xp2xpW`qZXE^~0M4#CMfsrZ*miKnBMD_0n!G8U8xPK<+B9 ziVWFeSqoHa#TIy3!vvAxyoOW!7?C0VnY|XK)D3Y1F#H_w9)Q?XDW#vt+{c~%HZzwf Rm`jUS=e{HQV}~Va@)u#rVweB` literal 0 HcmV?d00001 diff --git a/finestock/comm/__pycache__/api_interface.cpython-312.pyc b/finestock/comm/__pycache__/api_interface.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d8e447eba43167ac0cf29fa395431cee2b350ca9 GIT binary patch literal 6435 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 Z=lP4 Any: + raise NotImplementedError + + @abstractmethod + def set_oauth_info(self, app_key: str, app_secret: str) -> None: + raise NotImplementedError + + @abstractmethod + def set_access_token(self, token: str) -> None: + raise NotImplementedError + +class MarketDataProvider(ABC): + @abstractmethod + def get_price(self, code: str) -> Any: + raise NotImplementedError + + @abstractmethod + def get_ohlcv(self, code: str, frdate: str, todate: str) -> List[Any]: + raise NotImplementedError + + @abstractmethod + def get_ohlcv_min(self, code: str, todate: str = "", exchgubun: str = "K", + cts_date: str = "", cts_time: str = "", tr_cont_key: str = "") -> List[Any]: + raise NotImplementedError + + @abstractmethod + def get_index(self, code: str, frdate: str, todate: str) -> List[Any]: + raise NotImplementedError + + @abstractmethod + def get_index_min(self, code: str, todate: str, cts_date: str = " ", + cts_time: str = "", tr_cont_key: str = "") -> List[Any]: + raise NotImplementedError + + @abstractmethod + def get_orderbook(self, code: str) -> Any: + raise NotImplementedError + +class TradingProvider(ABC): + @abstractmethod + def do_order(self, code: str, buy_flag: Any, price: int, qty: int) -> Any: + raise NotImplementedError + + @abstractmethod + def do_order_cancel(self, order_num: str, code: str, qty: int) -> Any: + raise NotImplementedError + +class RealtimeProvider(ABC): + @abstractmethod + def set_data_queue(self, queue: Any) -> None: + """ + Inject a queue to receive realtime data. + The queue should support a .put(item) method. + """ + raise NotImplementedError + + @abstractmethod + async def recv_price(self, code: str, status: bool = True) -> None: + raise NotImplementedError + + @abstractmethod + async def recv_index(self, code: str, status: bool = True) -> None: + raise NotImplementedError + + @abstractmethod + async def recv_orderbook(self, code: str, status: bool = True) -> None: + raise NotImplementedError + + @abstractmethod + async def recv_trade(self, code: str, status: bool = True) -> None: + raise NotImplementedError + +class AccountProvider(ABC): + @abstractmethod + def get_balance(self) -> Any: + raise NotImplementedError + + @abstractmethod + def get_holds(self) -> List[Any]: + raise NotImplementedError + +class InfoProvider(ABC): + @abstractmethod + def get_stock_list(self, mrkt_tp: str = "0") -> List[Any]: + raise NotImplementedError + + @abstractmethod + def get_index_list(self) -> List[Any]: + raise NotImplementedError + +class BaseProvider(AuthenticationProvider, MarketDataProvider, TradingProvider, RealtimeProvider, AccountProvider, InfoProvider): + """ + Composite interface for backward compatibility or full implementation. + """ + pass diff --git a/finestock/ebest/__init__.py b/finestock/ebest/__init__.py new file mode 100644 index 0000000..5cc7b0c --- /dev/null +++ b/finestock/ebest/__init__.py @@ -0,0 +1 @@ +from .ebest import EBest \ No newline at end of file diff --git a/finestock/ebest/__pycache__/__init__.cpython-310.pyc b/finestock/ebest/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c49ef26e81756ca23cc7fe6883aa082bad81f4de GIT binary patch literal 185 zcmd1j<>g`kf?t8!X%ayCF^Gc(44TX@8G*u@ zjJH@_ol=WS{4|+vv8E;gSw$dyD;bJdfE1YcW$A1cQ=VUxU0jfuoEl$HS(1^T7n7Em r2UMJ&oQ=c+>yL?#&&hhDbkyv1@G4b)4d6^~g@p=W7zc_4i^HWN5QtgU3fF^+~E0zTkAD9^#8E-I1T)>8k H*nlbk;aoRf literal 0 HcmV?d00001 diff --git a/finestock/ebest/__pycache__/__init__.cpython-312.pyc b/finestock/ebest/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..20e3c867191428321f47bf7f19ba8b8f7729534d GIT binary patch literal 192 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 YHNE=J8a5J{zhSiblfA$G5h{!Q0U?~WSpWb4 literal 0 HcmV?d00001 diff --git a/finestock/ebest/__pycache__/ebest.cpython-312.pyc b/finestock/ebest/__pycache__/ebest.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bd8d99fce1cce1c55a1c059512d8818d3ae401e1 GIT binary patch literal 640 zcmYjNv1;5v5S>{`M7B?^0uC{RtCFa>GK3I9urY3o8{=}`-B&Q(27y>bY z9>zF!18V}GF|c>Q7p1eYrc4px0|oBFd-j^(1W%#3FCO6+_(pE0O$MQudneD+Mg?I+ z(%T0skyc@$+8ZCJMSDFPce5#H zRtRnm;kWA@J^JB3`RP6-da?ZCm-})?CGZ`#mxcpH_7uUmYdnIRG5i<8@h^cubEc|q({Nh%-|8er^NXr<2K6-- hymjB!e%|TF6mloSLu7Qr~)g2KOltcgk literal 0 HcmV?d00001 diff --git a/finestock/ebest/ebest.py b/finestock/ebest/ebest.py new file mode 100644 index 0000000..576f8dd --- /dev/null +++ b/finestock/ebest/ebest.py @@ -0,0 +1,6 @@ +from finestock.ls import LS + +class EBest(LS): + def __init__(self): + super().__init__() + print("create Ebest Components") diff --git a/finestock/kis/__init__.py b/finestock/kis/__init__.py new file mode 100644 index 0000000..d178eb8 --- /dev/null +++ b/finestock/kis/__init__.py @@ -0,0 +1,2 @@ +from .kis import Kis +from .kis_v import KisV \ No newline at end of file diff --git a/finestock/kis/__pycache__/__init__.cpython-310.pyc b/finestock/kis/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cea651e7aafbc2c4a2d712ff0c8335ccbca2d2c6 GIT binary patch literal 213 zcmd1j<>g`kf}|qtG+iM57{oyaOhAqU5Esh;i4=wu#vF!R#wbQc5SuB7DVI5l8OUZ1 zX3%7L$p}=U$#{#|JF^%_u>h$sKTVcf%-Na6MIdvcSb?ngvLa?6dnH2=E06*czs#Mj zV#@Q2vWp86lT+ghDoZl*^J3C6^HPgT@{_ZXSU~+T@$s2?nI-Y@dIgoYIBatBQ%ZAE P?Lbx+vj7Pm1_4F@*<3N$ literal 0 HcmV?d00001 diff --git a/finestock/kis/__pycache__/__init__.cpython-311.pyc b/finestock/kis/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1cfc1fea394ad73becf9c8f249d3a65100f6300f GIT binary patch literal 259 zcmZ3^%ge<81Sv(@X}UoAF^B^LOi;#WIUr*?LkdF*V-7u*uC&Da}c> jD*`zmWOcDVkodsN$jEquLF57|y1^iR0UIjf0O|n%wVFaK literal 0 HcmV?d00001 diff --git a/finestock/kis/__pycache__/__init__.cpython-312.pyc b/finestock/kis/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9166cde3e8bc126243faa788c4999e8dc0ef63a1 GIT binary patch literal 221 zcmX@j%ge<81Sv(@X}UoAF^B^LOi;#W86aaiLkdF*V-7)y3RE;sY}yBja5Lk$Vi{MI1mr0NSoMG5`Po literal 0 HcmV?d00001 diff --git a/finestock/kis/__pycache__/kis.cpython-310.pyc b/finestock/kis/__pycache__/kis.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..971ac3a42dcc589786e54953d6bb24ad168c4c79 GIT binary patch literal 290 zcmd1j<>g`k0<9qJG%+Ck7{oya%s`F<5Elyp33rARh7`sYAZ})gVn|^MX3%6#28yEq zMj+1th>LlEL@GlRV+unQQwk$cEz>P#@62L9P3Bu1@$q?yxvBB-x47ctbMsS5b3kmK z`1r!o#2kpsN`@jDFoa28nPufF#U zIpomCW|IOfIvl?5KHvBK-uvD+f8usKD0u$;t=A@E>nZ9#Fi?EV1<%v-;JHQd6i>&f z5pm8P&?!#Yu zMr=GiNChlkVlWt>RU03YB2siZ0)DowzdKyWNE}lk@bo54Q9pr>MW_*)r$(48HBR&N z2R7(N4e_%d*r6-Xz{i z#ki@+Wz}&Z65=C*$O)2aP72Yu6kt@77>SLmc8&|jLZZlVA`YqP>Nk7Z4~$++2vcG* z6pnDo%hH8Jd~`e-kBCwtJXPkKii)G~Z%JMj+)y;_f^M}Gd*Fl^@i>rQU!@+~+}Esg z)-P=Jk8JfhTZ3Y2kZle5#-^-Ev8~N-dv#e{IW9L0!J9SBc_F*(*^;wuRcu@3;`g zI2q;vYpes;aC(H!~xfMaHFA^2wZkd2qB0he6|3akU@5E#R%k+0CY?MK%sy| zwL+xEKJ;kGs%cV8#8oq&nof#>3mR9=iBL+q5HJZ<7_w-r78)QRR09!lnwpxLt_psP z+5jYNM^2GqSVrsw0&IHH(2}ofn2+VxzOuMs)oSuNRw*D^)2|$quVya2(46ybP`n$m zrpMlzYZvD(emHx5cBwf#oAYi{yxU~kHlYS;GHgCXJ+k?Z0WWUB#wqb4;vcm!c~AZ{jT((+nlJGvHgRX9TDqq7Nf7ra|i4)ds7|8WifFOn?>E zfFslcQSI8&fOVAi?IK*=v#YJJ{qHfVwXpfglK653i+QjLr_hY0P2mJ=;B^??h+&%$ zc{c@nT~tlUgea+&j=tWu?x64r<^U;8sZLP}Nhy&FCje7ORc5mGgeJ^_3lA$10iDE% z%7RZszQ7V}Y9eb;wZVEO;G{uDF@z<>+d!^T`KsFa#xJTiJ*wK2t7=xNnzNRdt5Dre5i;fdUt3(A=bvOk*hM-_iGYs)+RigUx#-bc=@vU6*` zy5ZK&n>#<+HNPv{p7*}8ixry6nj({&lww-8}Tsk@+LC zYZF=OY0H+gTc)@0VccWI2bURH^6Gb6@+MgFCZ0J6)$nYF&M+B}F1(qyELvvGFMu#= zQttsQcPlJ^eJlKw`k2j_GUf-iiiK^-SeEonQtUjs6-(GULCxASR^DD(zWO$J58@rA z_*r}TCd=53ph#00dk6JSXx5RjJzwpS5TXjtWNCvqcxR~%m=Oo>DhoS!w;|*)guI53 zv7FD4R#gh!fen9O-}IHEm^+`b!B(ses_w26J%dB7ds}zIuT?dJ)Q<8heNv@6RJ!$^ zMXei;@~}_hJU1;&NgN-YA-hKPV0<*5Oi3JQE4=C}g;D$Dc0UudNA+VKkn+(4U#iH9 zkqKdf3-Oa&QV4%T@4Clm8{3i$3886GH47ktc-5LnNzmRN)gwybDekHI1YAIQ!7&Ltu$|XP zWr+*1@QkRT4yrcch={5OK^u)|m}7?yaU^rvjnYpKz6!a;5Ny@wqE5ackhNqlUh_WL z8pyBTlyBOK@8+%f4VzZo)+&&YK(gjv`6$4kgG>8vzacjrf_J&*=M9?g;f#FxZP`DX z^N%Y2QGg#$-G|fHr>`aE5?S_1l|O6AJ3ZNpa|agyk=D!3^#C?EF3%s%c06`f-Dtkn zGuM;t0g=2)(e6=Jqc_ZXU)?SDP4~yq#l+o+ystxP>3k@y49kOKN-rme#^nh?35jwl zy$S+`KF)kiQ8V-j20|y9K6Lt-w=wc0Gm4=xCX5cxBrtM}Nn%J~E~0aZImSYyz_df_ zR4029ogVfqMs~un>nn=d@HPg3g1>4M{Xij?PM>&dvu;g0(18l(U5Cm~|Nlc}tP(2A z!0@@6goL_T;VEe#0Dk|6!Em_0b?@t~2!=u{bX}!~g+1U3dy(uz@&*!=ZNgzBN01x^ zqI%AYQj&{LM=t@60o0U*2_Th)2`ZI^5t;;4Vudas0SCdq5*P``F-ZfX?qEmf84ZlO zG4>>q9weuLyaYO;aXxaX1RW&UhDApHm!Kp6GUzDaMT+1>ihT)Qgq2=i9-oq98D)GH zw(L=+6E}Sqa~hH0DDxJE&N5@@a2hFU8a#AsxX_Qzn=C(6waSz+eJi!foH6s(lFBh_0X557u0HWL-hKzhcwZkqC{L$R75i!wi=xm9tI=m#LMrE`x%Lr+4yzO6$zHGWLx14yf(-b=7^=opBo! zO;j`Y6tGp;4Q0WMQ`gnq!L|1VJGkC~o*}NI`!s|)I#mzGcL)22hq&&+_Kp(p7B#`$ zUjlMbU*djJO(AhA304T8oFKj%S54=mJVYQR5d^3Us-t*8h-+%AS4v1=V$yS|g$TW# zfcwQ*Z0{TB=o~oK*VjYv81&Rod?F&~u&kO+Bql;C3)U*t-Y39W^H?G=74T?h&0grX zcHrzs-wq_G5egw7U=NGLVxme%VIZl?qSkwRp|>e8h-qHT1@;rHTf~EGnbrtiO>uM+ zO%b!4hvw5&y7E|zIt>NI55A=oST(b#Gy(yo(N?B3w#iLxpfs)r2i}z*$RnKWKbP~L zQ~c*DmB#AY8`1fetTpee&YB-r)!le^Vbe#A^NpY!+RxJ(&9>!TRUe+Ze(K}3i|hZC zy?cE5j$lEK4m4wnYmOI8N$GF_ubIRItIaf$= zg=F$R@m7Jw$Jk1Uv=XAR72k(F*Lyy;FM9r1y!*rD1D|YMKCpa1*>O0x?TE7N$Y=hC zZ>@0h*^w1)g;P4;$+e9sZDYB5PO0Z|u5*g(oJ`&)`k=gZ@*1!wZO@wWHS6TYok~qh z)}F6zlwWC4YIkKFk9}(u-{z%`yMg7AhcypVIbW~h>&=?;-kPlISql{MmB@>4zMH&2 z3lx}mQDp)A*RdGEb4ER<=Fvt|gF9f`{i(waQVm_g?Jn8+OcI1Uq`Kjr8; zS;8}0IZ9@148tfae9enmt;kpVE%VjAC?7}?!#F^StO=_2p`oGn{rmRr8z^Y!L6uUO z);;jw4gc1FQ#G}>1^ZN2TYGSb>mTSC;@UeP)OBKj>pImxz?}`MmcFi2=-Ru3Zw_!B zrw7419E3FDt?C*ENi;Y#bc*Y4Kh=R5YrBH&$GNuNA+BwBsE-@y=pQ2G`v=+wwL+fu zp)*|DKxZ4*)q{4FQaFg=lISAfuALchBZ7sJk0(@nQs5;n9-5A*?qv9a$c1Jm;0zHB ztF{XM zF5oK(Gu0UihZ8BVO)|-?$-PR}W_ld^a}dw`@nx`X+c+*SJu=0Rj2eP$u7w z$W2E;zBPga?@FC~<{jBTmh+D({;>-A2KPVN@HNMrBkOqLt-kR)b5}qVc&cs$=Hd%) zDxM8sHbKFcuU!j*)ZRi!wzDAaKswBO7E(*67iaHAmV19-|tbE3Cil46BB5ja)F zX)sw^coUJ9tZpWTyJtwT&Z1#jmv!LR>je$i`ZCF7NUOMO16gaCwMn(5F-)RPOP-85 zW06WANloHKrIundV*3Mog~YXI>`S^tE0#jHVtyP3>~ z%kLZ~OLB$g-5E6BGa08rraH@I>K&NV`}({NYS+uYAi+%y^jr70?io%y5dXoud$@@R z00BW%$RL>o60itY(EmOV)zsesg1`nkx`xqK^u0NBR*OQw7dB&NV#Pu8j%v{i%tD$J z@5ADx&`@s&Ag5-2Au4|fMTz-Eb&yyPFm1@bK|klgfKwLBlfc?Ax4f0aI-oi#)W>Ys&rr=*G|Z39W}vIxTxY2 zU|a{)U%RL(hHgM+v08`Bb+Ts%ybID|TJvSu=iFcA1H1q7tnzxd@_O&1K(8F=UD&a( zLmuQbc+IbAT%vELZ=|zQR$7v>rd117yS9Y5HTC)WMgkBYU7El2xPIM2=kGff9R?e8 zHNx$oCI8*=U+nwzz{5!HjZ?}Sr(}O`&flx}dtZXG&l;dF%^v~-w7+rw$kM*M)pv)N z+do0TUl?C(T$1i~-+mvEsb)VQQ_X%fN$+1ZQ?3nQyw(s(*OFnP5Pt|lp;Cn(@(fb< z=-@p#x|Z0ceR@!sbf13U;anXa_-sZ}hp0zGNr)A;Kpn&k_ zK#KR}cCAj3;sQBRK7ce4ZPd6Lv{m3O@|_EV1dT<~e(X<0B$g9?j-&>N;SL-}PiCMQ zJjTyYc)AiV$fqGpkeoA{;t8n(PZ9499^M<9-J{^k>p}gQA$@A z=c^R=GsrEXDTsWHA$vCFY?~C@Cb{@Nzt=y40#9*2_W&Ew*0u!xXCEUX(v*awEj-UZ zF&RhwBUlyhv8Sn9?2k=wGmf)Q9-Irpv4C0F2{EeUVg&B3!c!5rowmZYT09a4+kkdK z&Am0KG8aYRZy;mYL6i{sGB9AtB#q{0sP0cj3#dFc_&g4t9Sv#FCYLl;DzpfJz9iH}Pk{`d?p#1+D#7 z8IyS%e82N7aaqmV;o9;Aq}pTNyh`b$iQ2kn>CElXKODO~_QjTikG35A9217$Xm>q{zq$yKV>Tvth2zDqrRtj9=RtvA=MQaahU-VBldVxzW+Bmow~9B literal 0 HcmV?d00001 diff --git a/finestock/kis/__pycache__/kis.cpython-312.pyc b/finestock/kis/__pycache__/kis.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..908a5a85078dd4c78aa1320128aa8cd7119a694b GIT binary patch literal 10342 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 JpfD#R{SSSP=AZxo literal 0 HcmV?d00001 diff --git a/finestock/kis/__pycache__/kis_v.cpython-310.pyc b/finestock/kis/__pycache__/kis_v.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..db0e1ef6d572b95d8e67c33f28e0fe80bdc9aa38 GIT binary patch literal 293 zcmd1j<>g`kf^C7?Y2rZoF^Gc=#N^cYg36MN{Jfa7%)Hd%lKkXsBvy82 zaSRa0m+2K$7J-}s!o@5=f`fsD5iH`T384|jLs)D;MYlL?Ag-|k>tg~62{8Zw_Dww^ literal 0 HcmV?d00001 diff --git a/finestock/kis/__pycache__/kis_v.cpython-311.pyc b/finestock/kis/__pycache__/kis_v.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bbd6234e74a22bc9f5e0ff81003b2f124c8e69b5 GIT binary patch literal 4314 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 literal 0 HcmV?d00001 diff --git a/finestock/kis/kis.py b/finestock/kis/kis.py new file mode 100644 index 0000000..8e49bf3 --- /dev/null +++ b/finestock/kis/kis.py @@ -0,0 +1,206 @@ +import asyncio +from datetime import datetime +import json +import requests +import websockets +import finestock +from finestock.comm import API + + +class Kis(API): + def __init__(self): + super().__init__() + self.approval_key = None + self.headers_rt = {"custtype": "P", "tr_type": "1", "content-type": "utf-8"} + print("create Kis Components") + + def oauth(self, header=None, data=None): + data = { + "grant_type": "client_credentials", + "appkey": self.app_key, + "appsecret": self.app_secret + } + data = json.dumps(data) + return super().oauth(data=data) + + def approval(self): + header = self.headers.copy() + data = { + "grant_type": "client_credentials", + "appkey": self.app_key, + "secretkey": self.app_secret + } + response = requests.post(f"{self.DOMAIN}/oauth2/Approval", headers=header, data=json.dumps(data)) + if response.status_code == 200: + res = response.json() + if "approval_key" in res: + self.approval_key = res['approval_key'] + return res + else: + return response.json() + + def get_price(self, code): + today = datetime.now().strftime('%Y%m%d') + res = self.get_ohlcv(code, frdate=today, todate=today) + return res[0] if res else None + + def get_ohlcv(self, code, frdate=datetime.now().strftime('%Y%m%d'), todate=datetime.now().strftime('%Y%m%d')): + header = self.headers.copy() + header["tr_id"] = "FHKST03010100" + param = { + "fid_cond_mrkt_div_code": "J", + "fid_input_iscd": code, + "fid_input_date_1": frdate, + "fid_input_date_2": todate, + "fid_period_div_code": "D", #D:일봉, W:주봉, M:월봉, Y:년봉, + "fid_org_adj_prc": "0" #0:수정주가, 1: 원주가 + } + response = requests.get(f"{self.DOMAIN}/{self.CHART}", headers=header, params=param) + res = response.json() + + ohlcvs = [] + if res["rt_cd"] == "0": + data = res["output2"] + print(data) + for price in data: + ohlcvs.append(finestock.Price(price["stck_bsop_date"], code, price["stck_clpr"], price["stck_oprc"], price["stck_hgpr"], price["stck_lwpr"], price["stck_clpr"], price["acml_vol"], price["acml_tr_pbmn"])) + + return ohlcvs + + def get_index(self, code, frdate=datetime.now().strftime('%Y%m%d'), todate=datetime.now().strftime('%Y%m%d')): + header = self.headers.copy() + header["tr_id"] = "FHKUP03500100" + param = { + "fid_cond_mrkt_div_code": "U", + "fid_input_iscd": code, + "fid_input_date_1": frdate, + "fid_input_date_2": todate, + "fid_period_div_code": "D", #D:일봉, W:주봉, M:월봉, Y:년봉, + "fid_org_adj_prc": "0" #0:수정주가, 1: 원주가 + } + response = requests.get(f"{self.DOMAIN}/{self.INDEX}", headers=header, params=param) + res = response.json() + ohlcvs = [] + if res["rt_cd"] == "0": + data = res["output2"] + for price in data: + ohlcvs.append(finestock.Price(price["stck_bsop_date"], code, price["bstp_nmix_prpr"], price["bstp_nmix_oprc"], price["bstp_nmix_hgpr"], price["bstp_nmix_lwpr"], price["bstp_nmix_prpr"], price["acml_vol"], price["acml_tr_pbmn"])) + + return ohlcvs + + def get_orderbook(self, code): + header = self.headers.copy() + header["tr_id"] = "FHKST01010200" + param = { + "FID_COND_MRKT_DIV_CODE": "J", + "FID_INPUT_ISCD": code + } + response = requests.get(f"{self.DOMAIN}/{self.ORDERBOOK}", headers=header, params=param) + res = response.json() + + output1 = res['output1'] + sells = [] + for i in range(1, 11): + sells.append(finestock.Hoga(int(output1[f'askp{i}']), int(output1[f'askp_rsqn{i}']))) + + buys = [] + for i in range(1, 11): + buys.append(finestock.Hoga(int(output1[f'bidp{i}']), int(output1[f'bidp_rsqn{i}']))) + + output2 = res['output2'] + code = output2['stck_shrn_iscd'] + total_buy = output1['total_bidp_rsqn'] + total_sell = output1['total_askp_rsqn'] + order = finestock.OrderBook(code, total_buy, total_sell, buys, sells) + return order + + def get_balance(self): + header = self.headers.copy() + header["tr_id"] = "TTTC8434R" # 모의: VTTC8434R, 실전:TTTC8434R + param = { + "CANO": self.account_num, + "ACNT_PRDT_CD": self.account_num_sub, + "AFHR_FLPR_YN": "N", # 시간외단일가여부(N: 기본값, Y: 시간외단일가) + "OFL_YN": "", # 공란 + "INQR_DVSN": "02", # 조회구분(01: 대출일별, 02: 종목별) + "UNPR_DVSN": "01", # 단가구분(01: 기본값) + "FUND_STTL_ICLD_YN": "N", # 펀드결제분포함여부 + "FNCG_AMT_AUTO_RDPT_YN": "N", # 융자금액자동상환여부 + "PRCS_DVSN": "00", # 처리구분(00: 전일매매포함, 01: 전일매매비포함) + "CTX_AREA_FK100": "", # 연속조회검색조건100 + "CTX_AREA_NK100": "" # 연속조회키100 + } + response = requests.get(f"{self.DOMAIN}/{self.ACCOUNT}", headers=header, params=param) + res = response.json() + print(res) + + hold = res["output1"] + acc = res["output2"][0] + + holds = [] + for stock in hold: + holds.append( + finestock.Hold(stock['pdno'], stock['prdt_name'], float(stock['pchs_avg_pric']), int(stock['hldg_qty']), int(stock['pchs_amt']), + int(stock['evlu_amt']))) + + return finestock.Account(self.account_num, self.account_num_sub, int(acc["dnca_tot_amt"]), int(acc["nxdy_excc_amt"]), + int(acc["prvs_rcdl_excc_amt"]), holds) + + def do_order(self, code, buy_flag, price, qty): + url = f"{self.DOMAIN}/{self.ORDER}" + header = self.headers.copy() + header["tr_id"] = "TTTC0802U" if buy_flag == finestock.ORDER_FLAG.BUY else "TTTC0801U " #[실전]매수: TTTC0802U, 매도: TTTC0801U + dvsn = "01" if price == 0 else "00" #00: 지정가, 01:시장가 + + param = { + "CANO": self.account_num, + "ACNT_PRDT_CD": self.account_num_sub, + "PDNO": code, # 종목코드 + "ORD_DVSN": dvsn, # 주문구분(00: 지정가, 01:시장가) + "ORD_QTY": str(qty), # 주문수량(01: 대출일별, 02: 종목별) + "ORD_UNPR": str(price) # 주문단가(01: 기본값) + } + + response = requests.post(url, headers=header, data=json.dumps(param)) + res = response.json() + print(res) + + if res['rt_cd'] == "0": + data = res['output'] + return finestock.Order(code, '', price, qty, buy_flag, + data['ODNO'], data['ORD_TMD']) + + + def get_order_status(self, code): + pass + + def do_order_cancel(self, order_num, code, qty): + pass + + def get_index_list(self): + print("Kis not supported") + + async def connect(self): + self.approval() + self.ws = await websockets.connect(self.DOMAIN_WS) + + async def recv_price(self, code, status=True): + pass + + async def recv_index(self, code, status=True): + pass + + async def recv_orderbook(self, code, status=True): + pass + + async def recv_trade(self, code, status=True): + pass + + ''' + async def connect(self): + print("connecting...") + print(uri) + websocket = await websockets.connect(uri) + print("success connection") + return websocket + ''' diff --git a/finestock/kis/kis_v.py b/finestock/kis/kis_v.py new file mode 100644 index 0000000..4418af9 --- /dev/null +++ b/finestock/kis/kis_v.py @@ -0,0 +1,83 @@ +import json +import requests +import finestock +from finestock.kis import Kis + + +class KisV(Kis): + def __init__(self): + super().__init__() + print("create Kis_Test Components") + + + def get_balance(self): + header = self.headers.copy() + header["tr_id"] = "VTTC8434R" # 모의: VTTC8434R, 실전:TTTC8434R + param = { + "CANO": self.account_num, + "ACNT_PRDT_CD": self.account_num_sub, + "AFHR_FLPR_YN": "N", # 시간외단일가여부(N: 기본값, Y: 시간외단일가) + "OFL_YN": "", # 공란 + "INQR_DVSN": "02", # 조회구분(01: 대출일별, 02: 종목별) + "UNPR_DVSN": "01", # 단가구분(01: 기본값) + "FUND_STTL_ICLD_YN": "N", # 펀드결제분포함여부 + "FNCG_AMT_AUTO_RDPT_YN": "N", # 융자금액자동상환여부 + "PRCS_DVSN": "00", # 처리구분(00: 전일매매포함, 01: 전일매매비포함) + "CTX_AREA_FK100": "", # 연속조회검색조건100 + "CTX_AREA_NK100": "" # 연속조회키100 + } + print(header) + response = requests.get(f"{self.DOMAIN}/{self.ACCOUNT}", headers=header, params=param) + res = response.json() + print(res) + + hold = res["output1"] + acc = res["output2"][0] + + holds = [] + for stock in hold: + holds.append( + finestock.Hold(stock['pdno'], stock['prdt_name'], float(stock['pchs_avg_pric']), int(stock['hldg_qty']), int(stock['pchs_amt']), + int(stock['evlu_amt']))) + + return finestock.Account(self.account_num, self.account_num_sub, int(acc["dnca_tot_amt"]), int(acc["nxdy_excc_amt"]), + int(acc["prvs_rcdl_excc_amt"]), holds) + + def do_order(self, code, buy_flag, price, qty): + url = f"{self.DOMAIN}/{self.ORDER}" + header = self.headers.copy() + header["tr_id"] = "VTTC0802U" if buy_flag == finestock.ORDER_FLAG.BUY else "VTTC0801U" #[모의]매수: VTTC0802U, 매도: VTTC0801U + dvsn = "01" if price == 0 else "00" #00: 지정가, 01:시장가 + + param = { + "CANO": self.account_num, + "ACNT_PRDT_CD": self.account_num_sub, + "PDNO": code, # 종목코드 + "ORD_DVSN": dvsn, # 주문구분(00: 지정가, 01:시장가) + "ORD_QTY": str(qty), # 주문수량(01: 대출일별, 02: 종목별) + "ORD_UNPR": str(price) # 주문단가(01: 기본값) + } + + response = requests.post(url, headers=header, data=json.dumps(param)) + res = response.json() + print(res) + + if res['rt_cd'] == "0": + data = res['output'] + return finestock.Order(code, '', price, qty, buy_flag, + data['ODNO'], data['ORD_TMD']) + + def get_order_status(self, code): + pass + + def do_order_cancle(self, order_num, code, qty): + pass + + ''' + async def connect(self): + print("connecting...") + print(uri) + websocket = await websockets.connect(uri) + print("success connection") + return websocket + ''' \ No newline at end of file diff --git a/finestock/kiwoom/__init__.py b/finestock/kiwoom/__init__.py new file mode 100644 index 0000000..827d448 --- /dev/null +++ b/finestock/kiwoom/__init__.py @@ -0,0 +1,4 @@ +from .kiwoom import Kiwoom +from .kiwoom_v import KiwoomV + +__all__ = ['Kiwoom', 'KiwoomV'] diff --git a/finestock/kiwoom/__pycache__/__init__.cpython-312.pyc b/finestock/kiwoom/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62b94aca2e0df95fe070fd844a99e342b3211db3 GIT binary patch literal 273 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 dC^PZfe@du<8H+ literal 0 HcmV?d00001 diff --git a/finestock/kiwoom/__pycache__/kiwoom_v.cpython-312.pyc b/finestock/kiwoom/__pycache__/kiwoom_v.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b9508350b79d9e931140dae9fa80545ec5d73495 GIT binary patch literal 964 zcmZuv&1(}u6o0cH$tEVL3R0WX9=jC+wihWPLPZg=7a`yh7~<|EuHBDxcGH^Rp@)FU zL3)z@4U#{_O9gvb?8%ciB~?oC;G5Y^6zqe2^Lz8=ef{2ktX9i_^8ELq_mBX56~UJ3y=!v%< zMS+vO%CwRjvsDCdu@D2BxL_+TvDGnjh^veYTN5R0eGIROU3dnTmKL*I&Eaa=SA58#VYofJPAH$NM7gvT}O#8{i53ohrTfgWUL!P~3(vP4KO|1(m5fX(t~ za2X{E7o?}U%U<-m7U5UWibZ~tSB0ZUsmpvyhf9CQkQWv4a0!(dqIOuBRE}@lJ~7sL z4ezX?pg3#?3@I*C8bog27q~*{Zolp49+5n9D3wIg4){T{EQDcX>m`>ql~PGRK~*3PsN*%)_z0v= frdate] + + return ohlcvs + else: + logger.error(f"[Kiwoom] Error Code: {res.get('rt_cd')}") + logger.error(f"[Kiwoom] Full Response: {res}") + logger.error(f"[Kiwoom] Error Msg: {res.get('msg1')} {res.get('msg2')}") + except Exception as e: + logger.error(f"[Kiwoom] JSON Parse Error: {e}") + pass + return [] + + def get_ohlcv_min(self, code, todate="", exchgubun="K", cts_date="", cts_time="", tr_cont_key=""): + # TR: ka10080 (Stock Minute Chart) + # params: {stk_cd, base_dt, tic_scope, upd_stkpc_tp} + params = { + "stk_cd": code, + "base_dt": todate if todate else datetime.datetime.now().strftime("%Y%m%d"), + "tic_scope": "1", # 1 minute + "upd_stkpc_tp": "0" # 0:raw, 1:adjusted + } + # Note: Minute chart filtering by daily frdate might need implicit logic (e.g. only date part) + # Since fine_stock.Price.workday for minute data might be "YYYYMMDD", string comparison works if frdate is "YYYYMMDD" + return self._get_chart_sync("ka10080", params, next_key=tr_cont_key, frdate=cts_date if cts_date.strip() else "") + + return self._get_chart_sync("ka10080", params, next_key=tr_cont_key, frdate=cts_date if cts_date.strip() else "") + + def get_stock_list(self, mrkt_tp="0"): + # TR: ka10099 (Stock Info) + # params: {mrkt_tp} 0:KOSPI, 10:KOSDAQ + url = f"{self.DOMAIN}/{self.STOCK_INFO}" + + header = { + "Content-Type": "application/json;charset=UTF-8", + "authorization": f"{self.token_type} {self.access_token}", + "api-id": "ka10099", + "cont-yn": "N", + "next-key": "" + } + + params = { + "mrkt_tp": mrkt_tp + } + + response = requests.post(url, headers=header, data=json.dumps(params)) + + if response.status_code == 200: + try: + res = response.json() + if res.get('rt_cd', '0') == '0': # Note: User example says return_code, but standard Kiwoom is rt_cd usually. Wait, User example shows "return_code":0. + # Let's check user example again. + # User example response: + # { "return_msg":..., "return_code":0, "list": [...] } + # Standard Kiwoom usually uses rt_cd. But user example shows return_code. + # I should handle both or trust user example for this specific TR. + # Actually, for consistency, I will check return_code or rt_cd. + # The user example shows "return_code": 0 (integer). + + # Logic: + if str(res.get('return_code', res.get('rt_cd', '1'))) == '0': + data_list = res.get('list', []) + stocks = [] + for item in data_list: + # Mapping + # code: "005930" + # name: "삼성전자" + # marketName: "코스닥" (Wait, 005930 is KOSPI, example says KOSDAQ? Example might be mock/mixed) + # state: "관리종목" / "증거금100%" + # auditInfo: "투자주의환기종목" / "정상" + + code = item.get('code', '') + name = item.get('name', '') + market = item.get('marketName', '') + + # Simple mapping for bool flags + is_suspended = item.get('auditInfo') != '정상' + is_admin = item.get('state') == '관리종목' + + stocks.append(finestock.Stock(code, name, market, is_suspended, is_admin)) + + logger.info(f"[Kiwoom] get_stock_list received {len(stocks)} items") + return stocks + else: + logger.error(f"[Kiwoom] Stock List Error: {res.get('return_msg', res.get('msg1'))}") + else: # fallback if return_code is not present check rt_cd + pass + except Exception as e: + logger.error(f"[Kiwoom] JSON Parse Error: {e}") + else: + logger.error(f"[Kiwoom] HTTP Error: {response.status_code} {response.text}") + return [] + + def get_index_list(self, mrkt_tp="0"): + # TR: ka10101 (Index Info) + # params: {mrkt_tp} 0:KOSPI, 1:KOSDAQ, ... + url = f"{self.DOMAIN}/{self.STOCK_INFO}" + + header = { + "Content-Type": "application/json;charset=UTF-8", + "authorization": f"{self.token_type} {self.access_token}", + "api-id": "ka10101", + "cont-yn": "N", + "next-key": "" + } + + params = { + "mrkt_tp": mrkt_tp + } + + response = requests.post(url, headers=header, data=json.dumps(params)) + + if response.status_code == 200: + try: + res = response.json() + # User example uses return_code. Standard Kiwoom uses rt_cd. + # Logic: Check return_code first, then rt_cd. + return_code = str(res.get('return_code', res.get('rt_cd', '1'))) + + if return_code == '0': + data_list = res.get('list', []) + indices = [] + for item in data_list: + # Mapping + # marketCode: "0" + # code: "001" + # name: "종합(KOSPI)" + # group: "1" + + code = item.get('code', '') + name = item.get('name', '') + market = item.get('marketCode', '0') + group = item.get('group', '') + + indices.append(finestock.Index(code, name, market, group)) + + logger.info(f"[Kiwoom] get_index_list received {len(indices)} items") + return indices + else: + logger.error(f"[Kiwoom] Index List Error: {res.get('return_msg', res.get('msg1'))}") + except Exception as e: + logger.error(f"[Kiwoom] JSON Parse Error: {e}") + else: + logger.error(f"[Kiwoom] HTTP Error: {response.status_code} {response.text}") + return [] + + def get_index(self, code, frdate="", todate="", cts_date="", tr_cont_key=""): + # TR: ka20006 (Index Daily Chart) + # params: {inds_cd, base_dt} + params = { + "inds_cd": code, + "base_dt": todate if todate else datetime.datetime.now().strftime("%Y%m%d"), + } + return self._get_chart_sync("ka20006", params, next_key=tr_cont_key, frdate=frdate) + + def get_index_min(self, code, todate="", cts_date=" ", cts_time="", tr_cont_key=""): + # TR: ka20005 (Index Minute Chart) + # params: {inds_cd, base_dt, tic_scope} + params = { + "inds_cd": code, + "base_dt": todate if todate else datetime.datetime.now().strftime("%Y%m%d"), + "tic_scope": "1" # 1 minute + } + return self._get_chart_sync("ka20005", params, next_key=tr_cont_key, frdate=cts_date if cts_date.strip() else "") + + def _parse_ohlcv(self, data, tr_code): + ohlcvs = [] + + # Determine list data based on TR code if directly in data dict + items = [] + if isinstance(data, list): + items = data + elif isinstance(data, dict): + if tr_code == "ka10081": + items = data.get('stk_dt_pole_chart_qry', []) + elif tr_code == "ka10080": + items = data.get('stk_min_pole_chart_qry', []) + elif tr_code == "ka20006": + items = data.get('inds_dt_pole_qry', []) + elif tr_code == "ka20005": + items = data.get('inds_min_pole_qry', []) + + for item in items: + try: + # Mapping depends on TR code + if tr_code == "ka10081": # Stock Daily + ohlcvs.append(finestock.Price( + item['dt'], "", item['cur_prc'], item['open_pric'], item['high_pric'], + item['low_pric'], item['cur_prc'], item['trde_qty'], item['trde_prica'] + )) + elif tr_code == "ka10080": # Stock Minute + ohlcvs.append(finestock.Price( + "", "", item['cur_prc'], item['open_pric'], item['high_pric'], + item['low_pric'], item['cur_prc'], item['trde_qty'], "", + item['cntr_tm'] # Time + )) + elif tr_code == "ka20006": # Index Daily + if not item['dt']: continue + ohlcvs.append(finestock.Price.from_values( + item['dt'], "", + float(item['cur_prc']) / 100, + float(item['open_pric']) / 100, + float(item['high_pric']) / 100, + float(item['low_pric']) / 100, + float(item['cur_prc']) / 100, + item['trde_qty'], + item.get('trde_prica', 0) + )) + elif tr_code == "ka20005": # Index Minute + ohlcvs.append(finestock.Price.from_values( + "", "", + float(item['cur_prc']) / 100, + float(item['open_pric']) / 100, + float(item['high_pric']) / 100, + float(item['low_pric']) / 100, + float(item['cur_prc']) / 100, + item['trde_qty'], "", + item['cntr_tm'] # Time + )) + except Exception as e: + logger.warning(f"Parse error for item {item}: {e}") + continue + return ohlcvs + + def get_orderbook(self, code): + # TR: ka10004 (Stock Quote - Hoga) + # Endpoint: /api/dostk/mrkcond + # params: {stk_cd} + url = f"{self.DOMAIN}/{self.STOCK_ORDERBOOK}" + + header = { + "Content-Type": "application/json;charset=UTF-8", + "authorization": f"{self.token_type} {self.access_token}", + "api-id": "ka10004", + "cont-yn": "N", + "next-key": "" + } + + params = { + "stk_cd": code + } + + response = requests.post(url, headers=header, data=json.dumps(params)) + + if response.status_code == 200: + try: + res = response.json() + # Debug logging to see structure if needed + # logger.debug(f"[Kiwoom] Orderbook Response: {res}") + + if res.get('rt_cd', '0') == '0': + # Parse OrderBook + # Response body contains fields directly? Or in output? + # Based on search, it seems fields are in the body (or 'output' if consistent with chart) + # Let's check if 'output' exists, otherwise use 'res'. + # Reference [1] says response body has the fields. Kiwoom REST often wraps in 'output' or 'list'. + # For ka10004, let's assume 'output' block based on previous patterns. + data = res.get('output', res) + + buys = [] + sells = [] + + # 10 levels + for i in range(1, 11): + # Sell side: sel_1st_pre_bid ... sel_10th_pre_bid + # Buy side: buy_1st_pre_bid ... buy_10th_pre_bid + # Quantities: sel_1st_pre_req ... + + try: + if i == 1: + s_price = data.get('sel_fpr_bid', '0') + s_qty = data.get('sel_fpr_req', '0') + b_price = data.get('buy_fpr_bid', '0') + b_qty = data.get('buy_fpr_req', '0') + else: + s_price = data.get(f'sel_{i}th_pre_bid', '0') + s_qty = data.get(f'sel_{i}th_pre_req', '0') + b_price = data.get(f'buy_{i}th_pre_bid', '0') + b_qty = data.get(f'buy_{i}th_pre_req', '0') + + if s_price and int(s_price) > 0: + sells.append(finestock.Hoga(int(s_price), int(s_qty))) + if b_price and int(b_price) > 0: + buys.append(finestock.Hoga(int(b_price), int(b_qty))) + except: + pass + + total_sell = int(data.get('tot_sel_req', '0')) + total_buy = int(data.get('tot_buy_req', '0')) + + return finestock.OrderBook( + code=code, + total_buy=total_buy, + total_sell=total_sell, + buy=buys, + sell=sells + ) + else: + logger.error(f"[Kiwoom] Error Code: {res.get('rt_cd')}") + logger.error(f"[Kiwoom] Error Msg: {res.get('msg1')} {res.get('msg2')}") + except Exception as e: + logger.error(f"[Kiwoom] JSON Parse Error: {e}") + return None + + async def connect(self, callback=None): + logger.info(f"[Kiwoom API] connecting to {self.DOMAIN_WS}...") + try: + self.ws = await websockets.connect(self.DOMAIN_WS) + logger.info("[Kiwoom API] complete connect") + + if self.access_token: + await self.login() + + if callback is not None: + callback() + except Exception as e: + logger.error(f"[Kiwoom API] Connection Failed: {e}") + + async def login(self): + msg = json.dumps({ + "trnm": "LOGIN", + "token": self.access_token + }) + logger.info(f"[Kiwoom WS] Sending Login: {msg}") + await self.ws.send(msg) + + async def disconnect(self, callback=None): + self.stop() + if self.ws: + try: + await self.ws.close() + except Exception as e: + logger.error(f"[Kiwoom API] Disconnect error: {e}") + logger.info("[Kiwoom API] complete disconnect") + if callback is not None: + callback() + + def stop(self): + self.is_run = False + + async def run(self): + logger.info(f"Kiwoom API Run Loop Started") + self.is_run = True + while self.is_run: + try: + # Wait for message + res = await self.ws.recv() + + try: + res_json = json.loads(res) + trnm = res_json.get('trnm') + + if trnm == 'LOGIN': + if res_json.get('return_code') != 0: + logger.error(f"[Kiwoom WS] Login Failed: {res_json.get('return_msg')}") + self.stop() + else: + logger.info("[Kiwoom WS] Login Success") + + elif trnm == 'PING': + await self.ws.send(res) # Echo back + + elif trnm == 'REAL': + # Realtime Data + # Structure: {'data': [{'values': {'20': '...', '10': '...'}, 'type': '0B', ...}], 'trnm': 'REAL'} + + data_list = res_json.get('data', []) + for item in data_list: + if item.get('type') == '0B': + self._parse_real_price(item) + elif item.get('type') == '0J': # Index Realtime + self._parse_real_index(item) + elif item.get('type') == '0D': # Orderbook Realtime + self._parse_real_orderbook(item) + elif item.get('type') == '00': # Order Status Realtime + self._parse_real_order_status(item) + + except json.JSONDecodeError: + logger.warning(f"[Kiwoom WS] Received non-JSON message: {res}") + + except asyncio.TimeoutError: + pass + except ConnectionClosedOK: + logger.info("[Kiwoom WS] Connection Closed") + self.is_run = False + break + except Exception as e: + logger.error(f"[Kiwoom WS] Error: {e}") + # self.is_run = False # Don't stop on minor errors? + + def _parse_real_price(self, item): + values = item.get('values', {}) + code = item.get('item', '') + + # Mapping based on standard Kiwoom FIDs (approximate) + # 10: Current Price (cur_prc) + # 11: Fluctuation (diff) + # 12: Fluctuation Rate (diff_rate) + # 13: Accumulated Volume (trde_qty) ? + # 14: Accumulated Volume Value ? + # 15: Transaction Volume (tick_qty) ? + # 16: Open (oprc) + # 17: High (hgpr) + # 18: Low (lwpr) + # 20: Time (tr_tm) + + today = datetime.datetime.now().strftime("%Y%m%d") + cur_prc = values.get('10', '0') + time_str = values.get('20', '') + + # Prices are often signed (+/-), remove sign for float conversion + # And check if need /100. REST API needed it. + # If 181300 is Samsung, it seems raw won. + + price = abs(self._parse_int(cur_prc)) + open_p = abs(self._parse_int(values.get('16', '0'))) + high_p = abs(self._parse_int(values.get('17', '0'))) + low_p = abs(self._parse_int(values.get('18', '0'))) + + p = finestock.Price( + today, code, price, + open_p, high_p, low_p, price, + self._parse_int(values.get('13', '0')), # Vol + self._parse_int(values.get('14', '0')), # Val + time_str + ) + self.add_price(p) + + def _parse_real_index(self, item): + values = item.get('values', {}) + code = item.get('item', '') + + # Mapping for Index (0J) + # 10: Current Index + # 11: Diff + # 12: Rate + # 13: Vol + # 14: Val + # 20: Time + + time_str = values.get('20', '') + cur_val_str = values.get('10', '0') + + price = abs(self._parse_float(cur_val_str)) + open_p = abs(self._parse_float(values.get('16', '0'))) + high_p = abs(self._parse_float(values.get('17', '0'))) + low_p = abs(self._parse_float(values.get('18', '0'))) + + p = finestock.Price( + datetime.datetime.now().strftime("%Y%m%d"), + code, + price, + open_p, high_p, low_p, price, + self._parse_int(values.get('13', '0')), # Vol + self._parse_int(values.get('14', '0')), # Val + time_str + ) + self.add_price(p) + + def _parse_real_orderbook(self, item): + values = item.get('values', {}) + code = item.get('item', '') + + # Kiwoom Realtime Hoga (0D) FIDs + # Sell Price 1~10: 41~50 + # Buy Price 1~10: 51~60 + # Sell Qty 1~10: 61~70 + # Buy Qty 1~10: 71~80 + # Total Sell Qty: 121 + # Total Buy Qty: 125 + + buys = [] + sells = [] + + for i in range(10): + s_price_fid = str(41 + i) + b_price_fid = str(51 + i) + s_qty_fid = str(61 + i) + b_qty_fid = str(71 + i) + + s_price = values.get(s_price_fid, '0') + b_price = values.get(b_price_fid, '0') + s_qty = values.get(s_qty_fid, '0') + b_qty = values.get(b_qty_fid, '0') + + if self._parse_int(s_price) != 0: + sells.append(finestock.Hoga(abs(self._parse_int(s_price)), self._parse_int(s_qty))) + if self._parse_int(b_price) != 0: + buys.append(finestock.Hoga(abs(self._parse_int(b_price)), self._parse_int(b_qty))) + + total_sell = self._parse_int(values.get('121', '0')) + total_buy = self._parse_int(values.get('125', '0')) + + ob = finestock.OrderBook( + code=code, + total_buy=total_buy, + total_sell=total_sell, + buy=buys, + sell=sells + ) + self.add_orderbook(ob) + + def _parse_real_order_status(self, item): + values = item.get('values', {}) + + # Mapping for Order Status (00) + status_str = values.get('913', '') + trade_flag = finestock.TRADE_FLAG.ORDER + if isinstance(status_str, str): + if '체결' in status_str: + trade_flag = finestock.TRADE_FLAG.COMPLETE + elif '취소' in status_str: + trade_flag = finestock.TRADE_FLAG.CANCLE + elif '정정' in status_str: + trade_flag = finestock.TRADE_FLAG.MODIFY + + buysell_str = values.get('905', '') + order_flag = finestock.ORDER_FLAG.BUY + if isinstance(buysell_str, str) and '매도' in buysell_str: + order_flag = finestock.ORDER_FLAG.SELL + + trade = finestock.Trade( + code=values.get('9001', ''), + name=values.get('302', ''), + trade_flag=trade_flag, + order_flag=order_flag, + price=self._parse_int(values.get('901', '0')), + qty=self._parse_int(values.get('900', '0')), + trade_price=self._parse_int(values.get('910', '0')), + trade_qty=self._parse_int(values.get('911', '0')), + order_num=values.get('9203', '').strip(), + order_time=values.get('908', '') + ) + self.add_trade(trade) + + async def recv_price(self, code, status=True): + # Subscription Packet (REG) + await self._send_subscription(code, "0B", status) + + async def recv_index(self, code, status=True): + # Subscription Packet (REG) for Index (0J) + await self._send_subscription(code, "0J", status) + + async def recv_orderbook(self, code, status=True): + # Subscription Packet (REG) for Orderbook (0D) + await self._send_subscription(code, "0D", status) + + async def recv_trade(self, code, status=True): + # Subscription Packet (REG) for Realtime Trade/Price (0B) + # In Kiwoom, '0B' (Realtime Conclusion) includes both price update and trade tick. + # This is essentially the same as recv_price. + await self._send_subscription(code, "0B", status) + + async def recv_order_status(self, status=True): + # Subscription Packet (REG) for Order Status (00) + await self._send_subscription("", "00", status) + + def do_order(self, code, buy_flag, price, qty): + # Determine TR Code + if buy_flag == finestock.ORDER_FLAG.BUY: + api_id = "kt10000" + elif buy_flag == finestock.ORDER_FLAG.SELL: + api_id = "kt10001" + else: + logger.error(f"[Kiwoom] Invalid buy_flag: {buy_flag}") + return None + + # Determine Price Type + if price == 0: + trde_tp = "3" # Market + ord_uv = "" + else: + trde_tp = "0" # Limit + ord_uv = str(price) + + url = f"{self.DOMAIN}/api/dostk/ordr" # Endpoint for Order + + header = { + "Content-Type": "application/json;charset=UTF-8", + "authorization": f"{self.token_type} {self.access_token}", + "api-id": api_id, + "cont-yn": "N", + "next-key": "" + } + + params = { + "dmst_stex_tp": "KRX", + "stk_cd": code, + "ord_qty": str(qty), + "ord_uv": ord_uv, + "trde_tp": trde_tp, + "cond_uv": "" + } + + response = requests.post(url, headers=header, data=json.dumps(params)) + + if response.status_code == 200: + try: + res = response.json() + if res.get('rt_cd', '0') == '0': + logger.info(f"[Kiwoom] Order Success: {res.get('msg1')} {res.get('msg2')}") + # Return res for now as structure of output is not fully defined + return res + else: + logger.error(f"[Kiwoom] Order Error: {res.get('msg1')} {res.get('msg2')}") + return res + except Exception as e: + logger.error(f"[Kiwoom] JSON Parse Error: {e}") + else: + logger.error(f"[Kiwoom] HTTP Error: {response.status_code} {response.text}") + return None + + def do_order_cancel(self, order_num, code, qty): + url = f"{self.DOMAIN}/api/dostk/ordr" + + header = { + "Content-Type": "application/json;charset=UTF-8", + "authorization": f"{self.token_type} {self.access_token}", + "api-id": "kt10003", + "cont-yn": "N", + "next-key": "" + } + + params = { + "dmst_stex_tp": "KRX", + "orig_ord_no": str(order_num), + "stk_cd": code, + "cncl_qty": str(qty) + } + + response = requests.post(url, headers=header, data=json.dumps(params)) + + if response.status_code == 200: + try: + res = response.json() + if res.get('rt_cd', '0') == '0': + logger.info(f"[Kiwoom] Cancel Success: {res.get('msg1')} {res.get('msg2')}") + return res + else: + logger.error(f"[Kiwoom] Cancel Error: {res.get('msg1')} {res.get('msg2')}") + return res + except Exception as e: + logger.error(f"[Kiwoom] JSON Parse Error: {e}") + else: + logger.error(f"[Kiwoom] HTTP Error: {response.status_code} {response.text}") + return None + + def get_balance(self): + url = f"{self.DOMAIN}/api/dostk/acnt" + + header = { + "Content-Type": "application/json;charset=UTF-8", + "authorization": f"{self.token_type} {self.access_token}", + "api-id": "kt00001", + "cont-yn": "N", + "next-key": "" + } + + params = { + "qry_tp": "3" # 3: Estimated, 2: General + } + + response = requests.post(url, headers=header, data=json.dumps(params)) + print(response.text) + + if response.status_code == 200: + try: + res = response.json() + print(res) + if res.get('rt_cd', '0') == '0' or res.get('return_code') == 0: + # Kiwoom REST API for account often puts fields at the root level + output = res.get('output', res) + logger.info(f"[Kiwoom] Balance Response: {output}") + + # Placeholder mapping based on user example: + # entr: deposit (예수금) + # d1_entra: D+1 예수금 + # d2_entra: D+2 예수금 + # ord_alow_amt: 주문가능금액 + + deposit = int(output.get('entr', '0')) + next_deposit = int(output.get('d2_entra', '0')) # D+2 is standard for "next deposit" in Korea + pay_deposit = int(output.get('ord_alow_amt', '0')) # Trade allowed amount + + return finestock.Account(self.account_num, "", deposit, next_deposit, pay_deposit, []) + else: + logger.error(f"[Kiwoom] Balance Error: {res.get('msg1')} {res.get('msg2')}") + except Exception as e: + logger.error(f"[Kiwoom] JSON Parse Error: {e}") + else: + logger.error(f"[Kiwoom] HTTP Error: {response.status_code} {response.text}") + return None + + def get_holds(self): + url = f"{self.DOMAIN}/api/dostk/acnt" + + header = { + "Content-Type": "application/json;charset=UTF-8", + "authorization": f"{self.token_type} {self.access_token}", + "api-id": "kt00004", + "cont-yn": "N", + "next-key": "" + } + + params = { + "qry_tp": "0", # 0: All, 1: Exclude Delisting]\ + + "dmst_stex_tp": "KRX" + } + + response = requests.post(url, headers=header, data=json.dumps(params)) + + if response.status_code == 200: + try: + res = response.json() + if res.get('rt_cd', '0') == '0' or res.get('return_code') == 0: + logger.info(f"[Kiwoom] Holds Response: {res}") + + holds = [] + + data_list = res.get('stk_acnt_evlt_prst', []) + + for item in data_list: + # Mapping based on typical Kiwoom REST Account mapping (kt00004) + # stk_cd: Code + # stk_nm: Name + # avg_prc: Buying Price (Unit Price) + # rmnd_qty: Holding Qty + # evlt_amt: Evaluation Amount + # pl_amt: Profit/Loss + + code = item.get('stk_cd', '').strip().replace('A', '') # Remove 'A' prefix if present + name = item.get('stk_nm', '').strip() + price = int(self._parse_int(item.get('avg_prc', '0'))) + qty = int(self._parse_int(item.get('rmnd_qty', '0'))) + total = int(self._parse_int(item.get('evlt_amt', '0'))) + eval_pl = int(self._parse_int(item.get('pl_amt', '0'))) + + if code: + holds.append(finestock.Hold(code, name, price, qty, total, eval_pl)) + + return holds + else: + logger.error(f"[Kiwoom] Holds Error: {res.get('msg1')} {res.get('msg2')}") + except Exception as e: + logger.error(f"[Kiwoom] JSON Parse Error: {e}") + else: + logger.error(f"[Kiwoom] HTTP Error: {response.status_code} {response.text}") + return [] diff --git a/finestock/kiwoom/kiwoom_v.py b/finestock/kiwoom/kiwoom_v.py new file mode 100644 index 0000000..facfa97 --- /dev/null +++ b/finestock/kiwoom/kiwoom_v.py @@ -0,0 +1,13 @@ +from loguru import logger +from finestock.kiwoom.kiwoom import Kiwoom + +class KiwoomV(Kiwoom): + """ + Kiwoom Mock Investment Version + """ + def __init__(self): + super().__init__() + print("create KiwoomV Components") + + def __del__(self): + logger.debug("Destroy KiwoomV Components") diff --git a/finestock/ls/__init__.py b/finestock/ls/__init__.py new file mode 100644 index 0000000..4c52d15 --- /dev/null +++ b/finestock/ls/__init__.py @@ -0,0 +1,2 @@ +from .ls import LS +from .ls_v import LSV \ No newline at end of file diff --git a/finestock/ls/__pycache__/__init__.cpython-311.pyc b/finestock/ls/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2adae1090c516b2367dd23c339fdff999bcc1384 GIT binary patch literal 254 zcmZ3^%ge<81Xi~V_6ZL2(`32Dlv7;943xOVl2aUCR>T5iu4MQOBpH60Ia|e) z=NDxc7bGU9#urqUWaQ_?q-Ex%7MJ8FXCtw4ieuvAGxIV_;^XxSDt~d<B2!9bGPCpgScljRmuPH_=4P~sL#PH}u$5etyHlHoIuWcX#~Y!y?U zUzA;3keHkrUr-@3x_qM literal 0 HcmV?d00001 diff --git a/finestock/ls/__pycache__/ls.cpython-311.pyc b/finestock/ls/__pycache__/ls.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..692ba4f8ac6843c5b6790b7755e3cbeeb60b92c4 GIT binary patch literal 36400 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 literal 0 HcmV?d00001 diff --git a/finestock/ls/__pycache__/ls_v.cpython-311.pyc b/finestock/ls/__pycache__/ls_v.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a35cb8d8988c8ba826009f9de8ed352012585114 GIT binary patch literal 1078 zcmaJ=&1(}u6o0cH$+S%h9@Ms8f`~PjR6G_Dp+YaEmp~6oWr(|z8oSv|XEthk>LFl+ zUZq!073s~tWGe;BiYHIrB2kc@d~Y{f5?Y;|`R)6d_c6cO{ZKAX1CHS9r{-^berTU!qO%)44};BSE08)0-R&{S*PFf;FKeA$HHD#jB_yMk*D0Vy*AV{f!#7@jTRbR! z;d7t)+zDSeCe7?9~;@`~Nd_sMz-AmLMitZ_~ zJ^8G=Rb^@hHBAXCs!Wce5T5jf*qh0Yl`8b1>Ujh@)a*eizFs@wOQ(D(&ZnA1Z?!;# z2aUEw!l#93hhEnwxg^9+x8Y}=lr?pYij_yV;7T7$VmBo!MBO}2yHP}E3Klt55s=sX z8CZsWZva;SXZHO8{14K8)6DBtJN-Mls+&<$y14Y3uN4(e_kDychFCi153{p9Qp5^1 lte+wbOvcz5%*T`S3~t4fb6^!2c6b^3w||oL*A=d`{9i%Y?fd`$ literal 0 HcmV?d00001 diff --git a/finestock/ls/__pycache__/ls_v.cpython-312.pyc b/finestock/ls/__pycache__/ls_v.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c41d46026040fb304150c17048ebe4a89a636e11 GIT binary patch literal 863 zcmZuv&ubGw6n?Wml5M-Gc+gtC1QB&FvGgP&B2@4adI|Kf1ctafiLsmA?aZ#4;Gu_r z$w53x{|3p;f5S@!ds*$tlQ$u)l%9NVHwlG0uy5a+H}8Gl`)0mYD$9T?{PCrIivM2} z%$GTHrkm&-fDb+kpvPRs$kzg`r@J}>4W5Iq55YHtKGBOB*XWy0={E%q%Su7i=?ICQ zR&RL4iNb2LV0s6g_Xr}uWj?st2-?hNL(A2DZ3wP0g15{y8{p`fUT^Gp>_Qj9q;L8V z%_nvM$s##s@8AQ@F`4Z!Naj=FG?Q5u27Pd}?ZI_VidG`3^#9I(Xeq_HqLHfm=bKxPnr|YxtKya?c-&83Mq#tv z4TVY~Z});1sAiz}>ss8GOISLvalG0br-VCd4b9m;{In|L@;B?ock3p)hpTswtb0>T z!T-gmVFsmG2=!{NHI>ZPWeL`v5kc9%q=6<$;>`5Li?rh~xq^|R=2cmtM45A61e_06 z3jTROZ%bW4L+n>S+GG3hTJ6YsAXjnEDUqIGt0z#;WzKt%p9bXHobRQrpvdH+$g2cV znThO)0D+K$P3?)p)0~4OwQ^%nf@qx$;l$9)`+qHFZse$We%mqgN14jB7|UMVOf687 t%JXxdp{Su&=howb;S;%zA;IQ1^G9cl9mD!D+&VGJY-4o$6!4c9{sTWozWo3I literal 0 HcmV?d00001 diff --git a/finestock/ls/ls.py b/finestock/ls/ls.py new file mode 100644 index 0000000..ed3d062 --- /dev/null +++ b/finestock/ls/ls.py @@ -0,0 +1,859 @@ +import asyncio +import datetime +import json +import time + +from loguru import logger +import requests +import websockets +from websockets.exceptions import ConnectionClosedOK + +import finestock +from finestock.comm import API + + +class LS(API): + + def __init__(self): + super().__init__() + print("create LS Components") + self.headers["tr_cont"] = "N" + self.headers["tr_cont_key"] = "" + self.is_run = True + self.stock_master = None + + def __del__(self): + logger.debug("Destory LS Components") + + def oauth(self): + data = { + "grant_type": "client_credentials", + "appkey": self.app_key, + "appsecretkey": self.app_secret, + "scope": "oob" + } + return super().oauth(data=data) + + def get_price(self, code): + today = datetime.date.today().strftime('%Y%m%d') + res = self.get_ohlcv(code, frdate=today, todate=today) + return res[0] if res else None + + def get_ohlcv(self, code, frdate="", todate="", cts_date="", tr_cont_key=""): + url = f"{self.DOMAIN}/{self.CHART}" + header = self.headers.copy() + header["tr_cd"] = "t8410" + if cts_date != "": + header["tr_cont"] = "Y" + header["tr_cont_key"] = tr_cont_key + qrycnt = 500 + if frdate == todate: + qrycnt = 1 + + body = { + "t8410InBlock": { + "shcode": code, + "gubun": "2", #주기구분(2:일3:주4:월5:년) + "qrycnt": qrycnt, #요청건수(최대-압축:2000비압축:500) + "sdate": frdate, + "edate": todate, + "cts_date": cts_date, + "comp_yn": "N", + "sujung": "Y" + } + } + + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if res['rsp_cd'] == "00000": + ohlcvs = [] + for price in res['t8410OutBlock1']: + ohlcvs.append(finestock.Price(price['date'], code, price['close'], price['open'], price['high'], + price['low'], price['close'], price['jdiff_vol'], price['value'])) + + if response.headers['tr_cont'] == "Y" and frdate != todate: + _tr_cont_key = response.headers['tr_cont_key'] + cts_date = res['t8410OutBlock']['cts_date'] + if cts_date != "": + time.sleep(1) + pre_ohlcvs = self.get_ohlcv(code, frdate, todate, cts_date, str(_tr_cont_key)) + return pre_ohlcvs + ohlcvs + return ohlcvs + + def get_multiple_ohlcv(self, codes): + url = f"{self.DOMAIN}/{self.PRICE}" + header = self.headers.copy() + header["tr_cd"] = "t8407" + + group_size = 50 + codes_group = [codes[i:i + group_size] for i in range(0, len(codes), group_size)] + ohlcvs = [] + for _codes in codes_group: + ohlcvs += self._get_multiple_ohlcv(_codes) + time.sleep(0.5) + + return ohlcvs + + def _get_multiple_ohlcv(self, codes): + url = f"{self.DOMAIN}/{self.PRICE}" + header = self.headers.copy() + header["tr_cd"] = "t8407" + + body = { + "t8407InBlock": { + "nrec": len(codes), + "shcode": "".join(codes), #주기구분(2:일3:주4:월5:년) + } + } + + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if res['rsp_cd'] == "00000": + today = datetime.date.today() + str_today = today.strftime('%Y%m%d') + ohlcvs = [] + for price in res['t8407OutBlock1']: + ohlcvs.append(finestock.Price(str_today, price['shcode'], price['price'], price['open'], price['high'], + price['low'], price['price'], price['volume'], price['value'])) + + return ohlcvs + + + def get_ohlcv_min(self, code, todate="", exchgubun="K", cts_date="", cts_time="", tr_cont_key=""): + url = f"{self.DOMAIN}/{self.CHART}" + header = self.headers.copy() + header["tr_cd"] = "t8452" + if cts_date != "": + header["tr_cont"] = "Y" + header["tr_cont_key"] = tr_cont_key + qrycnt = 500 + + body = { + "t8452InBlock": { + "shcode": code, + "ncnt": 1, #단위(n분) + "gubun": "2", #주기구분(2:일3:주4:월5:년) + "qrycnt": qrycnt, #요청건수(최대-압축:2000비압축:500) + "nday": "1", + "sdate": todate, + "stime": "", + "edate": todate, + "etime": "", + "cts_date": cts_date, + "cts_time": cts_time, + "comp_yn": "N", + "exchgubun": exchgubun + } + } + + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if res['rsp_cd'] == "00000": + ohlcvs = [] + for price in res['t8452OutBlock1']: + ohlcvs.append(finestock.Price(price['date'], code, price['close'], price['open'], price['high'], + price['low'], price['close'], price['jdiff_vol'], price['value'], price['time'])) + + if response.headers['tr_cont'] == "Y": + _tr_cont_key = response.headers['tr_cont_key'] + cts_date = res['t8452OutBlock']['cts_date'] + cts_time = res['t8452OutBlock']['cts_time'] + if cts_date != "": + time.sleep(1) + pre_ohlcvs = self.get_ohlcv_min(code, todate, exchgubun, cts_date, cts_time, str(_tr_cont_key)) + return pre_ohlcvs + ohlcvs + return ohlcvs + + def get_index(self, code, frdate="", todate="", cts_date="", tr_cont_key=""): + url = f"{self.DOMAIN}/{self.INDEX}" + header = self.headers.copy() + header["tr_cd"] = "t8419" + if cts_date != "": + header["tr_cont"] = "Y" + header["tr_cont_key"] = tr_cont_key + qrycnt = 500 + if frdate == todate: + qrycnt = 1 + body = { + "t8419InBlock": { + "shcode": code, + "gubun": "2", #주기구분(2:일3:주4:월5:년) + "qrycnt": qrycnt, #요청건수(최대-압축:2000비압축:500) + "sdate": frdate, + "edate": todate, + "cts_date": cts_date, + "comp_yn": "N", + "sujung": "Y" + } + } + response = requests.post(url, headers=header, data=json.dumps(body)) + + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if res['rsp_cd'] == "00000": + ohlcvs = [] + for price in res['t8419OutBlock1']: + ohlcvs.append(finestock.Price.from_values(price['date'], code, price['close'], price['open'], price['high'], + price['low'], price['close'], price['jdiff_vol'], price['value'])) + if response.headers['tr_cont'] == "Y" and frdate != todate: + _tr_cont_key = response.headers['tr_cont_key'] + cts_date = res['t8419OutBlock']['cts_date'] + if cts_date != "": + time.sleep(1) + pre_ohlcvs = self.get_index(code, frdate, todate, cts_date, str(_tr_cont_key)) + return pre_ohlcvs + ohlcvs + + return ohlcvs + + def get_index_min(self, code, todate="", cts_date=" ", cts_time="", tr_cont_key=""): + url = f"{self.DOMAIN}/{self.INDEX}" + header = self.headers.copy() + header["tr_cd"] = "t8418" + if cts_date != " ": + header["tr_cont"] = "Y" + header["tr_cont_key"] = tr_cont_key + qrycnt = 500 + + body = { + "t8418InBlock": { + "shcode": code, + "ncnt": 1, #단위(n분) + "qrycnt": qrycnt, #요청건수(최대-압축:2000비압축:500) + "nday": "1", #조회영업일수(0:미사용1>=사용) + "sdate": todate, #기본값 : Space, (edate(필수입력) 기준으로 qrycnt 만큼 조회), 조회구간을 설정하여 필터링 하고 싶은 경우 입력 + #"sdate": " ", #기본값 : Space, (edate(필수입력) 기준으로 qrycnt 만큼 조회), 조회구간을 설정하여 필터링 하고 싶은 경우 입력 + "stime": "", #미사용 + "edate": todate, #처음조회기준일(LE), "99999999" 혹은 '당일' + #"edate": "99999999", #처음조회기준일(LE), "99999999" 혹은 '당일' + "etime": "", #미사용 + "cts_date": cts_date, + "cts_time": cts_time, + "comp_yn": "N" + } + } + response = requests.post(url, headers=header, data=json.dumps(body)) + + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if res['rsp_cd'] == "00000": + ohlcvs = [] + for price in res['t8418OutBlock1']: + ohlcvs.append(finestock.Price.from_values(price['date'], code, price['close'], price['open'], price['high'], + price['low'], price['close'], price['jdiff_vol'], price['value'], price['time'])) + if response.headers['tr_cont'] == "Y": + _tr_cont_key = response.headers['tr_cont_key'] + cts_date = res['t8418OutBlock']['cts_date'] + cts_time = res['t8418OutBlock']['cts_time'] + if cts_date != "": + time.sleep(1) + pre_ohlcvs = self.get_index(code, todate, cts_date, cts_time, str(_tr_cont_key)) + return pre_ohlcvs + ohlcvs + + return ohlcvs + + def get_index_list(self): + url = f"{self.DOMAIN}/{self.INDEX_LIST}" + header = self.headers.copy() + header["tr_cd"] = "t8424" + + body = { + "t8424InBlock": { + "gubun1": "", #주기구분(2:일3:주4:월5:년) + } + } + + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + def get_stock_list(self): + url = f"{self.DOMAIN}/{self.STOCK_LIST}" + header = self.headers.copy() + header["tr_cd"] = "t8436" + + body = { + "t8436InBlock": { + "gubun": "0", #0:전체, 1:코스피, 2:코스닥 + } + } + + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + ''' + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + ''' + if response.status_code == 200: + if "t8436OutBlock" in res: + return res['t8436OutBlock'] + + def __get_stock_master(self): + stock_list = self.get_stock_list() + self.stock_master = { + item['shcode']: {'hname': item['hname'], 'gubun': item['gubun'], 'etfgubun': item['etfgubun']} for item in + stock_list + } + + def get_news_list(self): + url = f"{self.DOMAIN}/{self.CONDITION}" + header = self.headers.copy() + header["tr_cd"] = "t1809" + header["tr_cont"] = "N" + + body = { + "t1809InBlock": { + "gubun": "1", #0 + "jmGb": "1", #0 + "jmcode": "1", + "cts": "1", + } + } + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + ''' + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + ''' + if response.status_code == 200: + if "t1809OutBlock1" in res: + return res['t1809OutBlock1'] + + def get_condition_list(self, htsid): + url = f"{self.DOMAIN}/{self.CONDITION}" + header = self.headers.copy() + header["tr_cd"] = "t1866" + header["tr_cont"] = "N" + + body = { + "t1866InBlock": { + "user_id": htsid, #0 + "gb": "0", #0:그룹+조건리스트 조회, 1:그룹리스트조회, 2:그룹명에 속한 조건리스트조회 + "group_name":"", + "cont": "", + "cont_key": "", + } + } + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if response.status_code == 200: + if "t1866OutBlock1" in res: + return res['t1866OutBlock1'] + + def get_condition_price(self, query_index, tr_cont_key=""): + url = f"{self.DOMAIN}/{self.CONDITION}" + header = self.headers.copy() + header["tr_cd"] = "t1859" + if tr_cont_key != "": + header["tr_cont"] = "Y" + header["tr_cont_key"] = tr_cont_key + + body = { + "t1859InBlock": { + "query_index": query_index, + } + } + + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if res['rsp_cd'] == "00000": + return res['t1859OutBlock1'] + + def _get_stock_master(self): + stock_list = self.get_stock_list() + self.stock_master = { + item['shcode']: {'hname': item['hname'], 'gubun': item['gubun'], 'etfgubun': item['etfgubun']} for item in + stock_list + } + + def _get_market_flag(self, code): + if self.stock_master is None: + self._get_stock_master() + + stock_info = self.stock_master[code] + + if stock_info['gubun'] == '1': + ''' + if stock_info['etfgubun'] == '0': + return finestock.MARKET_FLAG.KOSPI + + if stock_info['etfgubun'] == '1': + return finestock.MARKET_FLAG.ETF + if stock_info['etfgubun'] == '2': + return finestock.MARKET_FLAG.ETN + ''' + return finestock.MARKET_FLAG.KOSPI + + elif stock_info['gubun'] == '2': + return finestock.MARKET_FLAG.KOSDAQ + + def get_orderbook(self, code): + url = f"{self.DOMAIN}/{self.ORDERBOOK}" + header = self.headers.copy() + header["tr_cd"] = "t1101" + + body = { + "t1101InBlock": { + "shcode": code, + } + } + + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if res['rsp_cd'] == "00000": + data = res['t1101OutBlock'] + sells = [] + for i in range(1, 11): + sells.append(finestock.Hoga(int(data[f'offerho{i}']), int(data[f'offerrem{i}']))) + + buys = [] + for i in range(1, 11): + buys.append(finestock.Hoga(int(data[f'bidho{i}']), int(data[f'bidrem{i}']))) + + code = data['shcode'] + total_buy = data['bid'] + total_sell = data['offer'] + order = finestock.OrderBook(code, total_buy, total_sell, buys, sells) + + return order + + def get_balance(self): + url = f"{self.DOMAIN}/{self.ACCOUNT}" + header = self.headers.copy() + header["tr_cd"] = "CSPAQ12200" + + body = { + "CSPAQ12200InBlock1": { + "BalCreTp": "0", + } + } + + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if res['rsp_cd'] == "00136": + data1 = res['CSPAQ12200OutBlock1'] + account = data1['AcntNo'] + account_num = account[:-2] + account_num_sub = account[-2:] + data2 = res['CSPAQ12200OutBlock2'] + pay_deposit = data2['MnyOrdAbleAmt'] + holds = self.get_holds() + return finestock.Account(account_num, account_num_sub, int(data2["Dps"]), int(data2["D1Dps"]), + int(data2["D2Dps"]), holds) + + def get_holds(self): + url = f"{self.DOMAIN}/{self.ACCOUNT}" + header = self.headers.copy() + header["tr_cd"] = "t0424" + + body = { + "t0424InBlock": { + "prcgb": "", + "chegb": "", + "dangb": "", + "charge": "", + "cts_expcode": "" + } + } + + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if res['rsp_cd'] == "00000": + data = res['t0424OutBlock1'] + holds = [] + for hold in data: + holds.append( + finestock.Hold(hold['expcode'], hold['hname'], int(hold['pamt']), int(hold['janqty']), int(hold['mamt']), int(hold['appamt']))) + return holds + + def do_order(self, code, buy_flag, price, qty): + url = f"{self.DOMAIN}/{self.ORDER}" + header = self.headers.copy() + header["tr_cd"] = "CSPAT00601" + order_code = "1" if buy_flag == finestock.ORDER_FLAG.SELL else "2" + OrdprcPtnCode = "03" if price == 0 else "00" #00: 지정가, 03: 시장가 + body = { + "CSPAT00601InBlock1": { + "IsuNo": code, + "OrdQty": qty, + "OrdPrc": price, + "BnsTpCode": order_code, + "OrdprcPtnCode": OrdprcPtnCode, + "MgntrnCode": "000", + "LoanDt": "", + "OrdCndiTpCode": "0" + } + } + + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if res['rsp_cd'] in ("00040", "00039"): + data1 = res['CSPAT00601OutBlock1'] + data2 = res['CSPAT00601OutBlock2'] + return finestock.Order(data1['IsuNo'], data2['IsuNm'], data1['OrdPrc'], data1['OrdQty'], buy_flag, + data2['OrdNo'], data2['OrdTime'], data2['AcntNm'], data1['AcntNo']) + + def get_order_status(self, code): + url = f"{self.DOMAIN}/{self.ACCOUNT}" + header = self.headers.copy() + header["tr_cd"] = "t0425" + body = { + "t0425InBlock": { + "expcode": code, + "chegb": "0", + "medosu": "0", + "sortgb": "2", + "cts_ordno": " " + } + } + + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if res['rsp_cd'] == "00000": + data1 = res['t0425OutBlock'] + data2 = res['t0425OutBlock1'] + orders = [] + for order in data2: + trade_code = order['status'] + trade_flag = 0 + if trade_code == "접수": + trade_flag = finestock.TRADE_FLAG.ORDER + elif trade_code == "정정확인": + trade_flag = finestock.TRADE_FLAG.MODIFY + elif trade_code == "취소확인": + trade_flag = finestock.TRADE_FLAG.CANCLE + elif trade_code == "완료": + trade_flag = finestock.TRADE_FLAG.COMPLETE + + order_code = order['medosu'] + order_flag = 0 + if order_code == "매수": + order_flag = finestock.ORDER_FLAG.BUY + elif order_code == "매도": + order_flag = finestock.ORDER_FLAG.SELL + orders.append( + finestock.Trade(order['expcode'], "", trade_flag, order_flag, order['price'], order['qty'], + order['cheprice'], order['cheqty'], str(order['ordno']), order['ordtime'])) + return orders + + def do_order_cancel(self, order_num, code, qty): + url = f"{self.DOMAIN}/{self.ORDER}" + header = self.headers.copy() + header["tr_cd"] = "CSPAT00801" + body = { + "CSPAT00801InBlock1": { + "OrgOrdNo": int(order_num), + "IsuNo": code, + "OrdQty": qty + } + } + + response = requests.post(url, headers=header, data=json.dumps(body)) + res = response.json() + logger.debug(f"[API: oauth]\n" + f"[URL: {url}]\n" + f"[header: {header}]\n" + f"[param: {body}]\n" + f"[response: {res}]") + + if res['rsp_cd'] == "00156": + data1 = res['CSPAT00801OutBlock1'] + data2 = res['CSPAT00801OutBlock2'] + + return finestock.Order(data1['IsuNo'], data2['IsuNm'], 0, 0, finestock.ORDER_FLAG.VIEW, + data1['OrgOrdNo'], data2['OrdTime']) + + async def connect(self, callback=None): + print(f"[LS API] connecting...") + #self.ws = await websockets.connect(self.DOMAIN_WS, ssl=ssl_context) + self.ws = await websockets.connect(self.DOMAIN_WS) + print("[LS API] complete connect") + if callback is not None: + callback() + + async def disconnect(self, callback=None): + self.stop() + + if self.ws.open: + await self.ws.close() + + print("[LS API] complete disconnect") + if callback is not None: + callback() + + async def recv_price(self, code, status=True): + header = { + "token": self.access_token, + "tr_type": "3" + } + body = { + "tr_cd": "S3_", + "tr_key": code + } + tr_type = "3" if status else "4" + header["tr_type"] = tr_type + flag = self._get_market_flag(code) + if flag == finestock.MARKET_FLAG.KOSPI: + body['tr_cd'] = "S3_" + elif flag == finestock.MARKET_FLAG.KOSDAQ: + body['tr_cd'] = "K3_" + + data = json.dumps({"header": header, "body": body}) + await self.ws.send(data) + + async def recv_index(self, code, status=True): + header = { + "token": self.access_token, + "tr_type": "3" + } + body = { + "tr_cd": "IJ_", + "tr_key": code + } + tr_type = "3" if status else "4" + header["tr_type"] = tr_type + data = json.dumps({"header": header, "body": body}) + await self.ws.send(data) + + async def recv_orderbook(self, code, status=True): + header = { + "token": self.access_token, + "tr_type": "3" + } + body = { + "tr_cd": "H1_", + "tr_key": code + } + tr_type = "3" if status else "4" + header["tr_type"] = tr_type + flag = self._get_market_flag(code) + if flag == finestock.MARKET_FLAG.KOSPI: + body['tr_cd'] = "H1_" + elif flag == finestock.MARKET_FLAG.KOSDAQ: + body['tr_cd'] = "HA_" + data = json.dumps({"header": header, "body": body}) + await self.ws.send(data) + + async def recv_order_status(self, status=True): + header = { + "token": self.access_token, + "tr_type": "1" + } + body = { + "tr_cd": "SC0", + "tr_key": "" + } + tr_type = "1" if status else "2" + header["tr_type"] = tr_type + data = json.dumps({"header": header, "body": body}) + await self.ws.send(data) + + def recv_trade(self, code): + pass + + def stop(self): + self.is_run = False + + async def run(self): + print(f"LS API is RUN: {self.is_run}") + # self.make_queue() # Removed in favor of injection implementation + self.is_run = True + while self.is_run: + try: + #res = await self.ws.recv() + res = await asyncio.wait_for(self.ws.recv(), timeout=1) + res = json.loads(res) + header = res['header'] + body = res['body'] + tr_cd = header['tr_cd'] + if ("rsp_cd" in header) and (header["rsp_cd"] == "00000"): + rsp_cd = header["rsp_cd"] + rsp_msg = header["rsp_msg"] + print(f"API Message: [{rsp_cd}]:{rsp_msg}") + continue + + if body: + data = res["body"] + code = "" + if "tr_key" in res["header"]: + code = res["header"]['tr_key'] + + if tr_cd in ["H1_", "HA_"]: + order = self._parse_orderbook(code, data) + #self.add_orderbook(order) + self.add_data(order) + elif tr_cd in ["S3_", "K3_"]: + price = self._parse_price(code, data) + #self.add_price(price) + self.add_data(price) + elif tr_cd == "IJ_": + price = self._parse_index(code, data) + #self.add_price(price) + self.add_data(price) + elif tr_cd == "SC0": + trade = self._parse_order_status_order(code, data) + #self.add_trade(trade) + self.add_data(trade) + #elif tr_cd in ["SC1", "SC2", "SC3"]: + elif tr_cd in ["SC1"]: #SC2와 SC3은 SC0과 중복으로 발생해서 제거 + trade = self._parse_order_status_trade(code, data) + #self.add_trade(trade) + self.add_data(trade) + + except asyncio.TimeoutError as e: + pass + except ConnectionClosedOK as e: + print(f"ConnectionClosedOK: {e}") + self.is_run = False + except Exception as e: + print(f"Exception: {e}") + print(type(e)) + self.is_run = False + + await self.ws.close() + + def _parse_orderbook(self, code, data): + sells = [] + for i in range(1, 11): + sells.append(finestock.Hoga(int(data[f'offerho{i}']), int(data[f'offerrem{i}']))) + + buys = [] + for i in range(1, 11): + buys.append(finestock.Hoga(int(data[f'bidho{i}']), int(data[f'bidrem{i}']))) + + total_buy = data['totbidrem'] + total_sell = data['totofferrem'] + return finestock.OrderBook(code, total_buy, total_sell, buys, sells) + + def _parse_price(self, code, data): + today = datetime.datetime.now().strftime('%Y%m%d') + return finestock.Price(today, code, int(data['price']), int(data['open']), int(data['high']), + int(data['low']), int(data['price']), int(data['cvolume']), int(data['value']), + data['chetime'], data['hightime'], data['lowtime']) + + def _parse_index(self, code, data): + today = datetime.datetime.now().strftime('%Y%m%d') + return finestock.Price(today, code, float(data['jisu']), float(data['openjisu']), float(data['highjisu']), + float(data['lowjisu']), float(data['jisu']), int(data['volume']), int(data['value']), + data['time'], data['hightime'], data['lowtime']) + + def __parse_trade_flag(self, trade_code): + if trade_code == "SONAT000": + return finestock.TRADE_FLAG.ORDER + elif trade_code == "SONAT001": + return finestock.TRADE_FLAG.MODIFY + elif trade_code == "SONAT002": + return finestock.TRADE_FLAG.CANCLE + elif trade_code == "SONAS100": + return finestock.TRADE_FLAG.COMPLETE + + def __parse_order_flag(self, order_code): + if order_code in ["01", "03"]: #01: 현금매도, 03: 신용매도 + return finestock.ORDER_FLAG.SELL + elif order_code in ["02", "04"]: #02: 현금매수, 04: 신용매수 + return finestock.ORDER_FLAG.BUY + + def _parse_order_status_order(self, code, data): + trade_flag = self.__parse_trade_flag(data['trcode']) + order_flag = self.__parse_order_flag(data['ordgb']) + return finestock.Trade(data["shtcode"], data['hname'], trade_flag, order_flag, data['ordprice'], data['ordqty'], + data['ordprice'], data['ordqty'], str(data['orgordno']), data['proctm']) + + def _parse_order_status_trade(self, code, data): + today = datetime.datetime.now().strftime('%Y%m%d') + trade_code = data['ordxctptncode'] + trade_flag = "" + if trade_code == "01": + trade_flag = finestock.TRADE_FLAG.ORDER + elif trade_code in ["02", "12"]: + trade_flag = finestock.TRADE_FLAG.MODIFY + elif trade_code in ["03", "13"]: + trade_flag = finestock.TRADE_FLAG.CANCLE + elif trade_code in "11": + trade_flag = finestock.TRADE_FLAG.COMPLETE + + order_flag = self.__parse_order_flag(data['ordptncode']) + return finestock.Trade(data['shtnIsuno'], data['Isunm'], trade_flag, order_flag, data['ordprc'], data['ordqty'], + #data['execprc'], data['execqty'], str(data['orgordno']), data['exectime']) + data['execprc'], data['execqty'], str(data['ordno']), data['exectime']) + diff --git a/finestock/ls/ls_v.py b/finestock/ls/ls_v.py new file mode 100644 index 0000000..b6764f1 --- /dev/null +++ b/finestock/ls/ls_v.py @@ -0,0 +1,11 @@ +from loguru import logger +from finestock.ls import LS + +class LSV(LS): + + def __init__(self): + super().__init__() + print("create LS_V Components") + + def __del__(self): + logger.debug("Destory LS_V Components") diff --git a/finestock/model/__init__.py b/finestock/model/__init__.py new file mode 100644 index 0000000..bcb2957 --- /dev/null +++ b/finestock/model/__init__.py @@ -0,0 +1,5 @@ +from .price import * +from .trade import Hold, Account, Order, Trade, Stock, Index +from .flag import * + +__all__ = ['Price', 'OrderBook', 'Hoga', 'Hold', 'Account', 'Order', 'Trade', 'Stock', 'Index', 'TRADE_FLAG', 'ORDER_FLAG', 'MARKET_FLAG'] \ No newline at end of file diff --git a/finestock/model/__pycache__/__init__.cpython-310.pyc b/finestock/model/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9850d6b7b31ad75c3ad5e5dc1886f1fb52418893 GIT binary patch literal 189 zcmd1j<>g`kf|Z&2X<9(~F^GcsfP=ETIuXXa&=#K-FuRNmsS0g9F8q}qY3DrNx^91J{+06|19vH$=8 literal 0 HcmV?d00001 diff --git a/finestock/model/__pycache__/__init__.cpython-311.pyc b/finestock/model/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8546eceea69dcbd1a094202f9d57ff4a35c070d5 GIT binary patch literal 249 zcmZ3^%ge<81Zws3((HlsV-N=hn4pZ$azMs(h7^Vr#vFza2+atknSeA?FoP!ZOGcm~ zO~zY{T7H_$w^$2`GLutpv6d7irlj6tNy|x0U&-(pWZo}JXRDa<{G#mQg2d$1_=3uk zjQqTqw9LHJ;*$L2Y$R50eoAUiOniK1US>&ryk0@&FAf`^SZPkGT@lF9APbB2fy4)9 YMn=XP3?dg`2$d~-fdK^-aRBuI0B=}AmjD0& literal 0 HcmV?d00001 diff --git a/finestock/model/__pycache__/__init__.cpython-312.pyc b/finestock/model/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d2619adc4fe3162da64ad72d9ba4e068a777a3ad GIT binary patch literal 438 zcmaiwzfQw25XS8!PTG`CfDn%m26j+|2(yZhu@h4(U9!dS-nz7h|B00J3!HlS7p zgQ*c3sTrE76F>V!^NhpeJI2Y{H5Fsx234}Cyo<3S0xJX=~PoDu2xam>eA zAyG#VDkSQPh){{H3LXz7I?V{Z@m3_d6iG~FwQM@ze5l6R=t#fgk4ibAtcP)uB?ZRY`E*ffsJk(vdtyU5=^q+xDtwv{ Z!Wme51C3YEn1R(V3)I@its~Y|^#}TjbT$A0 literal 0 HcmV?d00001 diff --git a/finestock/model/__pycache__/flag.cpython-311.pyc b/finestock/model/__pycache__/flag.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7290f0f54dbaa7c0e41154f5010d5fd9ebd0acff GIT binary patch literal 1041 zcmZ{izi-n(6vyxEvy(bbOMxPU1VV@<3tJadA(_}IQS+lD0aPoJ73CbLnqSzBP&W_! z0o^)w10nty#^_e2Y^jo|6YrgqHci4g{@lxbeb3)}em~P`gTQ#cd)YE6A%F4Dd}y;^ z(t_ZEaKdSubf`-yQOG{w%6Gz5pZ-t@`3-kGTt#XOG;KvwrKW?HSkV|yR!L@KglRHV zFsD1{G-pZW1oq^ZJcH?i_{620xC*DP$`zM!)zvt2b*@1gT^O~BWjkK3VC|1^=SV@I z%@G5XG6|9Nyl$i8d!8^nuhZj$HpZsseH}E~Ges*`Y^NghV%g5;j)b1IO4))VlG$?c zu;A33Ut|K=$n|dab^Ti}I0^fWrtkI7PQUcJ_11CM4^Ml|lZ6Q0fq=pA0c{rRT#<#__~|H=cU(S#w-Buf+Mm@9!u0!kS@ zzCK@?06?` zT5tXlDC^9FaVAnlt8(DfmYkO|+Olcwpja#mJIMq#otXHT_(F*lk4PO+)Zx+Y*p;O@DeaqDWyZQ^XKkw%oyDslU1BQ lyu#J#tz@b+JtnKbCt-DZD;Wyi8k1F=zr4cL=^9BM_8*VQ)6xI{ literal 0 HcmV?d00001 diff --git a/finestock/model/__pycache__/flag.cpython-312.pyc b/finestock/model/__pycache__/flag.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df6ec2a23c449bc9b8fbf049e2575bc086077592 GIT binary patch literal 905 zcmZuvOK;Oa5Z<-dPU_fwK#LGU;=;ujE~p$(RVl=q5;c#K22ia?t0;B@s`J2ZgnUb+ z{(#_h^Jf;Pxa|HHO+ICgk`{LORSo(I7-A)#?tfOTAKaO2sp&7xL9YnagaU zRyis22LDQCP>tN~7hX0$_rr^5&}#c)5MO@i_nPM)dwz7;Z(l5VaE>1|&x2Oy*&q(F zsR^L4=mEeLnV8myPpr*RF@>$|@$2y3B5{2~9zuA1LtDKD2LdspJ`!yODI0`8-?w}1_BA`F6NBR48ca^}bZ7&~b;Ca#ye z3z7fj#;VO^u9B}G^TvwSs;#Zc)Q)SXCnc$aaq@3gMLvQPLWE8DBD~kX#*T}rJ`CGx zI)mg=NI*j*^Wo)%IFgm1%m~p60wGq8yUiY?@8>Xl0=w|NMb80#tCZ3oWN*$G-MQYK e6EJgAr55^N=7vJIuAj~cm_H`Ty%=W_X@3EeLbhW7 literal 0 HcmV?d00001 diff --git a/finestock/model/__pycache__/price.cpython-310.pyc b/finestock/model/__pycache__/price.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3d44581900ee6506411d9665ad4d9670f1fb28da GIT binary patch literal 1118 zcmZ`&OK;RL5VjNNvDqzKpj3c>`wEF82UMX74sbvV;&q5BSt0gzqdM`{_ELpwx$;B! z75oohIq?@bF_WZKgjz?L$2SwtGkRwyBXE8#AFt*CA-_=>9|p=Hymk#l5J45GX-?}P z4=Cz^3Tu`#N+cDbU_a<9A|ergCn7!%&S@Tv@I<75ryU-T@QKI(&pJF2(+^}Z`vcL) zBJdMYxS~{rwLV%^Qi%oi>`i6eM-bwb(cffKqVM>Gpd7+$KLe2@r-J0cCn7`11U=|^ z06YRc95@?zH1K%f$-vWrPYxhc$Z+bTccv<3o}6iOB8t|tQVZ#$4WgdujchzySI27~ zDt+doQfVuF{8g)ME%U7Byr|uB5&M+$rl=+7KI6RBVyjS|aelfj)L;p%Gk(r_(KOl> zuF{PKpRj7&Ph$3<(vJLn`tsQkrerroDfy;#Yuy~JszzF;%af4=VWc|h(tWaNO$;~S zuq|Gdku*r@_?gkpJ+cCfwE6 z-*p04*)s(TZyulpC)8DJ9-?$rkM4p2n`LJ}E(WCx-hY8 oH&t`o$G`^nf$*!V0{Qfy-_++6JS>i7m&|Ay?9y}!FWaO40Fxu@ZvX%Q literal 0 HcmV?d00001 diff --git a/finestock/model/__pycache__/price.cpython-311.pyc b/finestock/model/__pycache__/price.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5431bd4fd644f54f2ca7aae3428f51996929a57a GIT binary patch literal 1900 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 literal 0 HcmV?d00001 diff --git a/finestock/model/__pycache__/trade.cpython-310.pyc b/finestock/model/__pycache__/trade.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..78fb7ca76f8890440847a46c5ba8616bd1c0c949 GIT binary patch literal 1875 zcmbVM&5zqe6rZuj-$|2xNYH}#ybuZ;IiOXw%WhgKrCGGy7In0eWjvGCs$-|)2@tN! zwSPzj{|W!WTsiHT8zXbDxq2L@?p~LWC#%QHW#xgERc@%t3%qk~56mbQG8{O{;7!)6~RiI?z4#r2u ze{^{8@cuL7@9$0bCu0-tA3pkaGCm%^A?gNPXW_3e_rE&%u~sjeMV8BSvC=Q<>SR__ zve9+^va^Gq$?`<2Ovo=5E9Jut9;^m)9uh|J4XKrH55n7UcykaS0G^4h!yb<(6V9GK9DjG|RJ*&0CvO4ZcMaaunh8dt^cYg+&s6=h zto~syhI|)Ra}VI$-X}!5k_o!gQU|C9yt}4sO}#bscc4bWtI)7->QWe%LjVoK92sw+ zid-7^m0lT7*E%Z=lRsqT^vzW-joWBt25FjA6`a#WT{Vz!x2W_+f8PRwhp_&<1`w0b z2_Y%?_NYSLc2_5${U5ISG7$c2%0n5+_%?xn1mJiGjz=cg%kz3!X_I8_CWQ_$gAS86 z%kOz0fsnc1O>sqZ0djVmcj*=^Nq232a)CUM3 zB492JE1^f#C&*(b*-&hZ-^ZZ64Xe2tzdKZ+ZhQRxW2s;%VW}X!EEVri38@sei9$2f z9fWrPOjM)FMlf!-x|qwUdJj)r^mC{5KLy1{sI-9GcH^Id_Fp952V=3$k)rWP!FWp{ z?bexaO(Q6S7|I|q-Z2i1OQ%e1r${<0v*(u%@}!m8*5FagK!d2e=)-3KF#opa8s|}) z@J+G!9JF{(gPq@Pi;L#K6Vp-OBo`B2HVWr&TR^^EEsE;7tsdxAsAGlG&yG7gPTG>f o5>Xgxh4*briwSqyv3(CGe1rKNk{-0@&>8xnH>6>BzHZUK0nA5mZvX%Q literal 0 HcmV?d00001 diff --git a/finestock/model/__pycache__/trade.cpython-311.pyc b/finestock/model/__pycache__/trade.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90f2ce010993ba2771eaf47595c68059406ee7f5 GIT binary patch literal 2568 zcmbtW&2QT_6sJBc+miFqd^t^W8oI6Sh8z-L1BPNiwzN%B!vV4s!;AoeX3?opS#l}4 zn|G3d9dg(qw*vbEvJLGYV*@=1JQdh!HwVe7r@cqnik)~aw$b#@Ka%fJB!7HQ{%vYX zCr}=L`|0to3L$@DV^C6ka&b>0dv>g&`Qc)o&fTx9Zy*xJv;PM^up58t?)!OyEfgOoE9yffgPo#>sN;H`tVG0|I} z;GF~Ssfpf(J^c+S%>0FWDabr)TY*(^EZ^s;YK=N}LE>t$<_9pQA0BLNKQJE_w;pkQ z|6u#Uf#8Q=qE(sq6Ll-ei2wm@M7b!!&r3o{S+Yr4enO~1Ra?3by@JVtrJzqqn3}Lu z!Zc)d8djU(>W=5wT&;LE<*IAdDNi+6twOo-JZSM$;02b$RrUXQ;n3%;J{9hUyY1a5A;xsL7w)z9 zqHHeQ33u8%QPMN8K^L|>n0WlLH(>bx9Xnhsjiz5~qajhw&@9z&!|ZVZ;nG5y3`mpZ z>8(n|Yq|k9teBW?v(9IR8m8a;o~Lcv@cdf9b1wZcFb6H8VYLPgxK}eaty)bdFsoK2 z@K}qhPeJ%n!C<%G;cO0J9$^7t5n%~I1>mX!_m+KtZOjX?5P4+cC>rh!&SSD%P&L98JxJ#jw~eMstfD6>C>rjE0T#Yp2cM4?FpdZhqrUek0sI zxpNZya<8LqKzHR_4kDKZ{Xq=^Qm)x-4U7xf_h@w&r!63$IlCs% zhuDXJa`{bw5rGB)F)4ax=fnMQzr7#L%!Q?})GkF!E8*U$(ot8ScGZ<=dM^CxMClLa zSHs7r^Br{+YF8kTSSeh`c%=du`4>uQph}$M(uq>Ygi;il2r0pYURPQT>105_<#_54 zGtHGwTo}snbeTy_9uA1OGet-vRa%Ns=ORw==FrB;OgW zZ^`GKakZxr*$8i+-swpBu9SZx<$JOwP4~!6ILE$AgmZ<+%ZcgixEVHahU|kF)_UY7oMYc5!qp6DwRCe0y0p+EH{l%nE)lL~7_kx}{|(%dN|XQq literal 0 HcmV?d00001 diff --git a/finestock/model/__pycache__/trade.cpython-312.pyc b/finestock/model/__pycache__/trade.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..00dd10f4aff18b9d17a737936b8c706fcccb7fc5 GIT binary patch literal 2797 zcmbtW&2QX96d$k0_O88ONj4v(1zI%8l@scK3=J>{!)TBTA}eXXun^}10t z6p|uLap`mI8O+2dMXnQ9e?na2St=QMmb^$cJ<>M~zL|Z#nUTIG_*(mX&5^!Y@XhV> zwMP2p!MCu_H#^d|2)<+cd~>VB9sf?L=35ix(jORcIVCc#6FW8Ei6Wt`c+7XpilDc> zCCTm_cci=wF8T@&KW9O$%bR?uU1fI>gr|g&s^XGr>Qlm0 zMqTARj7llhmsFHoTGf~i9$Hs3q^6_RGRkS8eG-SY2ZGi@mkAm;btd$>!ymBtnkadZ z9dpO^f>k?eMhzCY%oSrW>bP|;fH=4ldyiNJ)=AqoPPT1f+IBs3n?A}}+kV(|{H{fX z38G-zP7s7K%nXCbws{`5BIvy^^mzfqAIL5sd3E;khj%{<`Gct8)R^69#rMPD?ur+% z2sb<=!N-_?7x&3dHCh~#T!H~Y^bWw6$=^hnLXaDtL20%J$YAs~z-vK|Pp(~Czr0B&J7W`TSJxMwEG2Vs z+UQ_!g5|JanFfK%ER&K9SxPe3q%;VeE{4846t~vkUX2O$VcZg04C&wt${sm>rK}$q zzfip(-Wxgw%Mp;0=or9jp%E#- zO|uL$-+*<2F+<+HbU7o8i?v$V3}Rt9iC_oKx+n}OaP961!)1*y@?wz<*yGslX;#B& z^%N)?1(&TjO+U6*oLU_6mZ0~6@k-e`kWM}a8{!l|P#-Vzd#Gb8kbLeXdL9i3$Xs*= z0EiwdZPHR_a%PjxbaKV^?auh5Ofww*dHU%0%UjcD)~>A2t;b)zyG73=)Z-$fsgJ?( zpQy(>m!us^GVNTWd;8MzE<&@CcQMn|(o!?!VHjmgXiMB>e2}6180>`~2M}h6CVRzq zRz)U};h-wL@z{f+netqY#ipD&gzp7h_5%cT-%Ijo978w>zvv@?1G2d|(O&8l#@m(7 z)a=^L4Rw>wcJkwEm)2Dnnm)32Yhz-I9!dDd^+xl}!SXNo#zvA{bED5UN|JAel1y$X zN!AC953y~PvqD?OdOcuUn6iLNj`{nrt0DTW#FP|_Ork*9;Fy9O>4g4sFzGsaVJ}G1 zeNW*$iv|Qdk?0)2YkU`Hk=FzVl_W`eNxp>QM6aOYN>IRLO_KLTo~QhX>`Pd1E9HW) z`hN^q#8ntfuAv^c8qjRz9TcU0vpXeQ+$qdIJ_x73;Hb|D1KOjGLA`qqVEYeqS`O#r%9kang9b4~m%K-UV=pEJa| KqSpNY@%TWUW literal 0 HcmV?d00001 diff --git a/finestock/model/flag.py b/finestock/model/flag.py new file mode 100644 index 0000000..6705be2 --- /dev/null +++ b/finestock/model/flag.py @@ -0,0 +1,20 @@ +from enum import Enum + +class TRADE_FLAG(Enum): + ORDER = 1 + MODIFY = 2 + CANCLE = 3 + COMPLETE = 4 + +class ORDER_FLAG(Enum): + BUY = 1 + SELL = 2 + VIEW = 3 + +class MARKET_FLAG(Enum): + KOSPI = 1 + KOSDAQ = 2 + #ETF = 3 + #ETN = 4 + +__all__ = ['TRADE_FLAG', 'ORDER_FLAG', 'MARKET_FLAG'] \ No newline at end of file diff --git a/finestock/model/price.py b/finestock/model/price.py new file mode 100644 index 0000000..461eaf4 --- /dev/null +++ b/finestock/model/price.py @@ -0,0 +1,72 @@ +from dataclasses import dataclass, field +from typing import List + +@dataclass(frozen=True) +class Price: + workday: str + code: str + price: float + open: float + high: float + low: float + close: float + volume: int + volume_amt: int + time: str = None + hightime: str = None + lowtime: str = None + + @classmethod + def from_values(cls, workday, code, price, open_, high, low, close, volume, volume_amt, + time=None, hightime=None, lowtime=None): + return cls( + workday=str(workday), + code=str(code), + price=float(price), + open=float(open_), + high=float(high), + low=float(low), + close=float(close), + volume=int(volume), + volume_amt=int(volume_amt), + time=time, + hightime=hightime, + lowtime=lowtime + ) + + @classmethod + def from_series(cls, series): + """ + Converts a pandas Series (a row from a DataFrame) into a Price instance. + If `code` is not provided, attempts to use series['code']. + """ + return cls.from_values( + workday=series['date'], + code=series['code'], + price=series['close'], + open_=series['open'], + high=series['high'], + low=series['low'], + close=series['close'], + volume=series['volume'], + volume_amt=series['volume_amt'], + time=series.get('time', None), + hightime=series.get('hightime', None), + lowtime=series.get('lowtime', None) + ) + + +@dataclass(frozen=True) +class Hoga: + price: int + qty: int + +@dataclass(frozen=True) +class OrderBook: + code: str + total_buy: int + total_sell: int + buy: List[Hoga] = field(default_factory=list) + sell: List[Hoga] = field(default_factory=list) + +__all__ = ['Price', 'Hoga', 'OrderBook'] \ No newline at end of file diff --git a/finestock/model/trade.py b/finestock/model/trade.py new file mode 100644 index 0000000..6773cb3 --- /dev/null +++ b/finestock/model/trade.py @@ -0,0 +1,65 @@ +from dataclasses import dataclass, field +from typing import List + +from finestock.model.flag import TRADE_FLAG, ORDER_FLAG + + +@dataclass(frozen=True) +class Stock: + code: str + name: str # Name + market: str # Market Name + is_trading_suspended: bool = False # auditInfo or state mapping? + is_administrative: bool = False # state mapping? + +@dataclass(frozen=True) +class Index: + code: str + name: str + market: str = "0" # marketCode + group: str = "" + +@dataclass(frozen=True) +class Hold: + code: str + name: str + price: int + qty: int + total: int + eval: int + +@dataclass(frozen=True) +class Account: + account_num: str + account_num_sub: str + deposit: int + next_deposit: int + pay_deposit: int + hold: List[Hold] = field(default_factory=list) + +@dataclass(frozen=True) +class Order: + code: str + name: str + price: int + qty: int + order_flag: str + order_num: str + order_time: str = None + id: str = None + account_num: str = None + +@dataclass(frozen=True) +class Trade: + code: str + name: str + trade_flag: TRADE_FLAG + order_flag: ORDER_FLAG + price: int + qty: int + trade_price: int + trade_qty: int + order_num: str + order_time: str + +__all__ = ['Hold', 'Account', 'Order', 'Trade', 'Stock', 'Index'] \ No newline at end of file diff --git a/finestock/path.py b/finestock/path.py new file mode 100644 index 0000000..7dcbea2 --- /dev/null +++ b/finestock/path.py @@ -0,0 +1,86 @@ +_EBEST_ = { + "DOMAIN": "https://openapi.ebestsec.co.kr:8080", + "DOMAIN_WS": "wss://openapi.ebestsec.co.kr:9443/websocket", + "OAUTH": "oauth2/token", + "REVOKE": "oauth2/revoke", + "CHART": "stock/chart", + "INDEX": "indtp/chart", + "ORDERBOOK": "stock/market-data", + "PRICE": "stock/market-data", + "ACCOUNT": "stock/accno", + "ORDER": "stock/order", + "INDEX_LIST": "indtp/market-data", + "STOCK_LIST": "stock/etc", + "CONDITION_LIST": "stock/item-search", +} + +_LS_ = { + "DOMAIN": "https://openapi.ls-sec.co.kr:8080", + "DOMAIN_WS": "wss://openapi.ls-sec.co.kr:9443/websocket", + "OAUTH": "oauth2/token", + "REVOKE": "oauth2/revoke", + "CHART": "stock/chart", + "INDEX": "indtp/chart", + "ORDERBOOK": "stock/market-data", + "PRICE": "stock/market-data", + "ACCOUNT": "stock/accno", + "ORDER": "stock/order", + "INDEX_LIST": "indtp/market-data", + "STOCK_LIST": "stock/etc", + "CONDITION": "stock/item-search", +} +_LS_V_ = { + **_LS_, + "DOMAIN_WS":"wss://openapi.ls-sec.co.kr:29443/websocket" +} +_KIS_ = { + "DOMAIN": "https://openapi.koreainvestment.com:9443", + "DOMAIN_WS": "ws://ops.koreainvestment.com:21000", + "OAUTH": "oauth2/tokenP", + "REVOKE": "oauth2/revokeP", + "CHART": "uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice", + "ACCOUNT": "uapi/domestic-stock/v1/trading/inquire-balance", + "ORDER": "uapi/domestic-stock/v1/trading/order-cash", + "INDEX": "uapi/domestic-stock/v1/quotations/inquire-daily-indexchartprice", + "ORDERBOOK": "uapi/domestic-stock/v1/quotations/inquire-asking-price-exp-ccn", +} +_KIS_V_ = { + **_KIS_, + "DOMAIN":"https://openapivts.koreainvestment.com:29443", +} + +_KIWOOM_ = { + "DOMAIN": "https://api.kiwoom.com", + "DOMAIN_WS": "wss://api.kiwoom.com:10000/api/dostk/websocket", + "OAUTH": "oauth2/token", + "REVOKE": "oauth2/revoke", + "STOCK_CHART": "api/dostk/chart", + "INDEX_CHART": "api/dostk/chart", # Using same endpoint based on research + "STOCK_INFO": "api/dostk/stkinfo", + "STOCK_ORDERBOOK": "api/dostk/mrkcond", # TR ka10004 + "CHART": "uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice", # Placeholder to keep compatibility if used elsewhere + "ACCOUNT": "uapi/domestic-stock/v1/trading/inquire-balance", # Placeholder + "ORDER": "uapi/domestic-stock/v1/trading/order-cash", # Placeholder +} + +_KIWOOM_V_ = { + **_KIWOOM_, + "DOMAIN": "https://api.kiwoom.com", # Default is https://api.kiwoom.com ??? Wait, main DOMAIN is api.kiwoom.com + # User said: mockapi.kiwoom.com in previous example. + # Let's verify standard. + # If main is api.kiwoom.com (Production), then mock is likely openapi.kiwoom.com or mockapi.kiwoom.com. + # User's example said: #host = 'https://mockapi.kiwoom.com' # 모의투자 + # So I will use that. + "DOMAIN": "https://mockapi.kiwoom.com", + "DOMAIN_WS": "wss://mockapi.kiwoom.com:10000/api/dostk/websocket", +} + +_API_PATH_ = { + "EBest": {**_EBEST_}, + "LS": {**_LS_}, + "LSV": {**_LS_V_}, + "Kis": {**_KIS_}, + "KisV": {**_KIS_V_}, + "Kiwoom": {**_KIWOOM_}, + "KiwoomV": {**_KIWOOM_V_}, +} \ No newline at end of file