From eb1c60cbc11341d8aead144d56e43dd1c8c6da3b Mon Sep 17 00:00:00 2001 From: Simon Date: Sun, 17 Jan 2021 12:01:23 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=83=D1=82=D0=B8=D0=BB=D0=B8=D1=82=D0=B0=20ibd?= =?UTF-8?q?ds:=20=D0=BF=D0=BE=D0=BC=D0=BE=D1=88=D0=BD=D0=B8=D0=BA=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=B3=D0=BE=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BE=D1=82=D1=87=D1=91=D1=82=D0=B0=20=D0=BE=20?= =?UTF-8?q?=D0=B4=D0=B2=D0=B8=D0=B6=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=B4=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=B6=D0=BD=D1=8B=D1=85=20=D1=81=D1=80=D0=B5=D0=B4?= =?UTF-8?q?=D1=81=D1=82=D0=B2=20=D0=BF=D0=BE=20=D1=81=D1=87=D1=91=D1=82?= =?UTF-8?q?=D1=83=20=D0=B2=20IB=20(#33)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .flake8 | 2 +- README.md | 25 +- images/ibdds_2020.png | Bin 0 -> 62157 bytes investments/cash.py | 18 ++ investments/currency.py | 18 ++ investments/ibdds/__main__.py | 3 + investments/ibdds/ibdds.py | 95 ++++++ investments/ibtax/ibtax.py | 15 +- investments/report_parsers/ib.py | 15 + poetry.lock | 509 +++++++++++++++---------------- pyproject.toml | 2 + tests/ibdds/show_report.py | 54 ++++ tests/report_parsers/ib_test.py | 44 +++ 13 files changed, 526 insertions(+), 274 deletions(-) create mode 100644 images/ibdds_2020.png create mode 100644 investments/cash.py create mode 100644 investments/ibdds/__main__.py create mode 100644 investments/ibdds/ibdds.py create mode 100644 tests/ibdds/show_report.py diff --git a/.flake8 b/.flake8 index ab8846e..83fdbee 100644 --- a/.flake8 +++ b/.flake8 @@ -19,7 +19,7 @@ max-cognitive-score = 24 max-cognitive-average = 24 # magic methods includes -max-methods = 18 +max-methods = 20 # function arguments max-arguments = 8 diff --git a/README.md b/README.md index c622050..8a222d9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Investments -Библиотека для анализа брокерских отчетов + утилита для подготовки налоговой отчетности +Библиотека для анализа брокерских отчетов + утилиты для подготовки налоговой отчетности ![Tests status](https://github.com/cdump/investments/workflows/tests/badge.svg) @@ -24,16 +24,31 @@ $ pip install investments --upgrade --user *Пример отчета:* ![ibtax report example](./images/ibtax_2020.jpg) - ### Запуск Запустить `ibtax` указав в `--activity-reports-dir` и `--confirmation-reports-dir` директории отчетами в формате `.csv` (см. *Подготовка отчетов Interactive Brokers*) Важно, чтобы csv-отчеты `activity` и `confirmation` были в разных директориях! -### Подготовка отчетов Interactive Brokers + +## Утилита ibdds +Утилита для подготовки отчёта о движении денежных средств по счетам у брокера Interactive Brokers (USA) для резидентов РФ + +- выводит отчёт по каждой валюте счёта отдельно +- вывод максимально приближен к форме отчёта о ДДС + +*Пример отчета:* +![ibdds report example](./images/ibdds_2020.png) + +### Запуск +Запустить `ibdds` указав в `--activity-report-filepath` путь до отчёта о активности по счёту в формате `.csv` (см. *Подготовка отчетов Interactive Brokers*) + +Важно: утилита не проверяет период отчёта `activity` и для корректной подготовки налоговой отчётности необходимо указать передать путь до отчёта за один год. + + +## Подготовка отчетов Interactive Brokers Для работы нужно выгрузить из [личного кабинета](https://www.interactivebrokers.co.uk/sso/Login) два типа отчетов: *Activity statement* (сделки, дивиденды, информация по инструментам и т.п.) и *Trade Confirmation* (settlement date, необходимая для правильной конвертации сумм по курсу ЦБ) -#### Activity statement +### Activity statement Для загрузки нужно перейти в **Reports / Tax Docs** > **Default Statements** > **Activity** Выбрать `Format: CSV` и скачать данные за все доступное время (`Perioid: Annual` для прошлых лет + `Period: Year to Date` для текущего года) @@ -42,7 +57,7 @@ $ pip install investments --upgrade --user ![Activity Statement](./images/ib_report_activity.jpg) -#### Trade Confirmation +### Trade Confirmation Для загрузки нужно перейти в **Reports / Tax Docs** > **Flex Queries** > **Trade Confirmation Flex Query** и создать новый тип отчетов, выбрав в **Sections** > **Trade Confirmation** все пункты в группе **Executions**, остальные настройки - как на скриншоте: diff --git a/images/ibdds_2020.png b/images/ibdds_2020.png new file mode 100644 index 0000000000000000000000000000000000000000..c7378c01bc33b169c9fce272291c31aec9c6f188 GIT binary patch literal 62157 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

?IFaT``L8&E_WEC3ZTyYy>&`>Zw;rsqL3W@4H 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%0D>~< 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

  • Gfo^)?j{K=tt5kyP>{u1?O^j{xG?^*Q^Jk*->iGIHXK1~jlT;gjCzBKm+W zy3PIj*3CQo5S6GAvJ`&zvlQozq-hHnKbZc0;3$NcP~umH^#&|>y2#~K9~tia`@4j< zj!%^jNu_D`bP1Yk{gFAp?*L1*_9C=eUPh?JiZ8|(8d*LntB-OK8TumiFClCD%K}C~7iXUe+#uzkmQF0kI!yhN{dl7bHgv*9Skw;uQD)@>IZ^@KA9${*Fo7 zCYo(qN|)FLILhlWoJi)U@Z-h~U^BgZRP7C_G*fRq!Iz<&Nyv(BbEF4|gSt`EhhX2R zGe=HFH%`tMYW*oUyZ5qBZaD3HYQ5yhhR{^_0zdev_0QECk~Z$2v0x2T_t@|Jb}0sL zWrWu=ClC8EG=+vE3jJwqH}&p|uA)dHm2YOyh$i@-3?ynZ*txXn|7am%NXmPKI6#uu zL5cU}G3jX;;je-$mgcN=eLX0e`&@Fk0o}JtGz)1jgc;E@5CF6)QyNR%$aX1_GcYOC zXp@fx#Wg1mV?(7*+41R&6IXUY#Tx~n zK(FK@*YO*QuDIG|1}w{$0dA}t%RTjNu6&^TJ6s&}urWMluV%d7aiSzepdBt)XDXt$ zmHgWMvx0&>f}Z&k_-y6f+M<^yPevqZ7Iw`t8DS+It!9kW`i%GB!o6~^3_os%9 zEqo;<%M=-Yz0a0k3%{_XRnYNLfod{upz79yPe@((i4iI}a+}@{H(XvYY8m~a^m$05 z?DC}fqwoA?>2nnUVi$*pDg^jbRsjE9usLSA*?-a45tQOT8oT2`Vrg}z>5*J=SX32% z-t=_uDU}PLU(9^v`?Al_9a)eBYQpevP|W`>#5*L_hk(`o&;rKXKAFdpYIWVFq3^I7 zVvPuJ0Dp$IFx{-PG;Xif{6#4akE@`c^eVN;?Ihz5TPh}L;V&90h$@2d9KaAYMeim9 zTv`6g$H#r@wSsR^iE|p(ezrS%{$nQilbiyQ?BGkX!h$3x*o8H21u*C{o*N7$-~u9DQzyYq@HRKgy@8_4+N*(q9mF?l(z} z^Y;?oV=WQ+UNHr_$7k!8L4*%ZR!+9~2R^sjb2nYWqK=Z@u5Y?#^|82ab<-msb4&`R z{j=66)2I4mPliwrU(1*})u6k}fT~Or4A%Ke8v={#CQlQ^t@k19?Ck^YjLj}I+OZL8 zAQ`~7vo2rjC~a$FxX?_TW0<CHM@Z^;~0=0nyb%<6v3e-Fk({ zL>Slst*QT+RhL~PQ~pca=J|JN^QjJ6&gg>tl^;GiEgmRxC%;Fuy6oS9n)wVx(A;^q ze-^{9Yg8#_dLNDbt^W`5@&i!ECw{(Iq<<8*D{e;jm03qngnz{^k{msxhE_?I>`K(f z^Z!iG*ZTp3H`BFGsP}G#yC>hFSZV$ICN{l=$#L^hjwTzPF$F!X>61da{-P$I1IddSGgIoTrKU3xr>Vlf+Qe?I{Q5AsS9Ng zxdOO*z6zwDZA<8bWa@K6K1eMwFP91^#?(QaIsO0BUkkz-} z`F108fOR$RzPt_e{!j;APc9P6Jzx^j!*fZpn%B3Mo&h33t0%~bz%TMC|9b>JDGXWe zU-j^!@hT1aO8PG?gYAR=8bJ>nE(%dBQL6FCOPDot0tX}2;v%*;H!U-6zGAA*o(7WS z<0hfo@V5}c2Tp+_Ik%_Ijq4B(7 zsuAm*Mxl-8uN}V9^1EtcCW3%D7ocO+bg3T1p#|iHEw3TvW&_=i+^|GBEB<{Qs zw=#Q49hBfGjPiW$R+D)jgIp6vxXeJcN$tpi{mZ{D3Q>`iJ6Z&ZvOH$F=x*9n zV;knP^H+Q1QZi?r-f*nUTl6WQfBKp@Ck$~K)nvu5(m<)=&#*y{M`NL4!*l#CMd0? zu(^0c>h8W_LBQv7Dk&I0cdSFzMHa<{9+}QI%sq+C`%eeJNGWaK@b8R!M#Cwyi^#>Q zVpXi(h6v_G&&r3=a}^l!odQ|F1|ji&FCMqR;*aX;nD(-&GyT_sb<1o1?i&^06;QMb zSb1@UHf<^_@mG3&3Trj21}*Bn0mG z-{ zQ70(+2z6OSZX1cpUVtkS#>IR(dR}1#U#UwUeSZExI7XRI%YgUWc_J7D2V#)Glx?1(`P5| z>G_A|1cKo8f$~3x-PEo5LuqweLu?4SM#Agw-6d2uGiK$P&$iChBr7AN5N3M5F4 zEI&UwvS$^XDSM86B{m?p>ZV-oTP$gR(cMGVPwYvOUUioF7rNl!yW}uJHN=ljcfj&A zXZWNsx4+!o966_jMAZLr-&et%`5^RM8_nb4{70w>*B82<@f_4s*lTlbHSo+s zE@cU%zQ6f;Rhw%#L!>@MlFBjzseJn+3fSHG0*&4i%c1)*UD*><=(IP%++h_-09=}k7I+k&MgNEJe<3oblNX|YCvuY!Y<65x_aCGEf#H^@?C&hz zv?Oq}E+viiR*7ScbNdeLzQ<;}Yo|S4X-Gq+@1k3VJMGSgQM8F+cl=O@8XJH|EPWFOL}IDvdDk_ z!*!H5(m0-%7r-|lejsk-%Uzij6>Sw1#{HYq0gIHvehcqSNAp~5>8MB^zL)4NyH}1> z&tu9IJoGsd!;clW(;+ASaps`BZ|2v`*oN~3HD?<|8Bh|!`90qU_gC#T8w@p)VwV{0 z*+4Chvds?|$K6FS5wGylzcU@43q_e`Z4Wu7Y=OiLOdgBpa>-37k%n3r@eh=e2P#fj2!2On*PoOIDpZziUNR`Ie@)iMV9vKyT56|E(E z3W-yBqm(71UfQD3O%b8hV;L)7JfjnZ0tPy${UScKs@)Uz3j}K4M;xBu%Y6@D4K;#}DA^DPl4FoD1AsX7wOZuE@q)7~d@iB5c`pxZ$-!pTzb3H_>WF$JSk8l8i}pbN0S$djEmgEts&SDh5zl zHu9l2Swn!QKai6biYC<_^PS~IPFzsC(QX`zB>(&r>c8h6R%Pp3^wCrTr z=sh_)1>#GIaTuG3blMUdasE;u?5rE|^yExAMO@p97Z>dV%dY_$aTK6o@ISHu`ZPT= zzU_6EmoZ9J*D5l<;o0d#(+RCHGpmp+#Lz0W{T1eZO(0 z4f-j9sw_-|g19BSZIc70o6lWad2hk3jX6GD%yEtjF^Ac@W0!e|OuDJN{Re@EIB>lh z995p8lll9<*c7$86#lHaeO7vglzWxsg2M9a*d%lDdgo!Y9;a1Q7mS_qlJf{M-z3FD z=AJ^+oNdf9mgM?pdZ!M_N7$#4k34ac_lH=0~+;lFPL&uUK?Bd-_}S4zcawzShij`__N5$1s@;PfH;lOK`DC*q|mezN=KVl4^F-_O|~|7LDay zN&J8JN7@w7m+wqZ^H(C?6fgw{HgUyRsG2V{5F!FptJjH!MoGP9pPo{r7rjYvwr!KC z)_der#)a;1N?&FLiNg`Crb2YSu-i5esC$CJW!>xAjq+=d6+OuyM|&&X7NAv-@Lf;1 z(J+8-_4Gz`I(I7dz=A9G%F4jkq|}KNCEaScUjD>zaxv)II{ACt0(aHhneTA0gu@lC zUoyNg`e@<+bsev{5u2(R_O4m2M z_k48Ezp)H1t^n{JzdY=sDCyh25OB}X*|s|@1T}0gJ08sjvd%ZXgv1j(Pi6^!alnKq z6pxRX+uU|ayN??N=(VLSWPAL)n|Kvk4F4s!+d5Rj<8i0n&7{-AQVJq8C)ql=tfZ3i zi;zU@wfqO>#CIU5Qz3^R7#EItbhd%bIr4rAv%~E&f@smfl35MJ`*DcV>De}xmEnOs z+RqScT+k}>#0?H7Rj~J76^jp`9@@9A0z;zze2YyxKJtDstA=#2oYz^}k*%-rdWHNu zU_E=~s#{6&UF5ke44S}6#m6`W$@qk*8 z5iSzlT&E4%G_`cw4?Px4cG`B6xP0Edvh*Gfo%m3$GZgxLF8%?p%=s5yIVi=#dv#;6 z-__07m^fjE4u9lJ;F1ps7|l5kSROFmj2CI8*Ap9ITED}g8x?q}3`08;E?*Mw&6IaP zIpaa15$@Q_#&N%GFzyVJT|-n-DIkm+UA-hf@gDm)P z3->s-gt$XR@JBh)jk`9e0Hza_n=rdA90_Kk!cw%8U$&Ctf;`cWiqU8!S>4S)kSYgJ`%`$Y8N2)%9d0sUX@g96!7&*Ltwm8$o z&E3>xSFd8rH(FgRyy zdqi^mw?O1rcF-LnLeHM<5URDq1regDE;x!}TEycFp?Is>TL3>o*A>h=UdPQgSGEf! zBT@HL5Q-;;DB5#Mi%W1d=ZG;pBPtW!G4jV|e}Arr}6YROaY-rj*x=W5tVo`W@fSzFrcbZmbNS7IPFA z6cO$zc=pj$FuZ#ya2xSd#x5ltIC72eC)u`nWO@*8;lzg@_l%Z$!Uul{S0LZ;ai@4)&t z`D)7mOATmsWOAp;NRDqRG#tK!CO<$pX|mdf3^1 zM(pISl2Mc;M!w!?D`ri^)H;^aoU4h%@yJr^0znMAG>0JZB2ZJU(Yb35c0ow7n zZ33YvuH3(D@~i1D`RUZ&D;X=E{{tvdjceLlGi8ZjeslH^(Kr{?=-y)1XWl!0vJf{-yW2&4slXA0m`suYC7QM@Yrxd5Fl-(o#<0ZNLqIy>1mqTa5Xs};%JRC}@K z?Y1jpzS(W43oc3s$Ua3+U;o~w+T~6WwMe}ERnDP`c~t@V*tSqKG6wepE)DLgo$hUa z`mX$*2!5O`o~@TklhY&P^eY#!LIF&x0&>*T2SY~aMcl5~=T~0=WGy01i-9h>=+CAI zl`zTbvRao+TRVGP`RQwnUy!u;*{YE@bjx+($d!Kn9k?vdMt}ZhVY_toXQ@3cu(1b0 zga&7odN4#}d6DEw^_;>v{|TU=b-28ztjxli-;{A-((Y)NcpyTLtiVGa8ap4AQiXX- z3$4bHxvM84No;9}rG9Bm$jy&?4?vZD)t4zP*+<6;p2%ao`&KNNZC2omX$OUU4P4KW zhS(&9;x+_sDMrNT$_qY^K=wZS%ceQ-bw+DD4c!D^gj_UViQ4i-G%u~3>ZjehUr!S? zb9{I_^htjmi@+M)WExLTN~#2Dddkg1k3#ts{_K^mDzj_E;= zd~;hwoGIy(MI(u^e!3cxPp-K#ggA!0$xrS4cUoH9uH~^$+jK$4r|Puy!~Fg0*p+4^ zLNT{Nw+;qVI9#*|O4`w4Qc?u9B(6H>y@*pWjyYGQqtuToKq*YMK9ZkJofVB;zx#_9 zSpDn>nTt z=x0|K%pWlcl|S&>!y1feG%c!MTPs^-YL#hW=2H9R<3MKX>l#|j-EW3xtm;W4Dld4x zwY?nB+p4<(l>HW||D=F8;MpfUe;10%DDEcLg>W)Lv=EZk)dgIJgWrV(HMSwhKy&Ekyo7H>CGUVnHGdIBbUc8+1OWBes_LZo_PvUmD+a&RKco| zW!~A6hVuq7ix0eP8>#tGP-<4?$GC;pZ`3!cMJGDZ# z8lhVPZE0O3o~PIK^v$D3k*A9XTi{C%S+ z0pj_wAl!eVFCGgbQ*I+vGSurjX2Ov0HM4Fa-y4hM{)UoBE!FmJ>f95UC}(d`r`n(0s;3)s||b}#SWyW zEblF*-m4;ggDC=`8=EB0jY83M$rV&oM*7OmkTiJZCJDYs%M`gRTAKerT=-`@)p9G~%M3^Zx z0t1>_0Uw<}Knn8HB;I?x$sdl_yAcF#iQNY`y&`_uT{)4@>hu0{Hli!MiuX}SXIIAS z@~&b5K4&~F_?aLrXZBfXW(Q|GVtgJk-r<@tPb}I~yIeqxVwATrZal5$mZKw1+;Jw$ zOgMr~W4MJvpWP)gcp9&~x2Z%*FA1t+0-=Z|U^$ zh}BfmA*38!>_QfgZxUn2R}8x^u!c-9K4Qa;L%^wMiU9B745>pBnK844I&Ut#gf#2E zh<;azRBIjmctSzdNtk=hMBgBH2PxALdEiQCxOvhQyhYp*_Ua}I4bBEfgY-YH6TEz3_tS|~4G z1+n4tg};(+)9m4Nzg^A!3o9KOtGhO>|1`UC68?adek0eDtF*NQq-bd=OSeKS%*T3R zO*H{~%ocblLDy1fIW6AI6&H^wRuES1x)q?$e|cFQxX}cK8gki%WzR{CS$kx-M*6cy zf0!b+jOiE3$U@s@74iiHRa3A~Di4*_?hBgAg2uWM*+00_l=S2oZVW#P-XXZsZtGy{ z2d*@CuUBzz@_M>sdNA+%eTYhBm?f~c-lLUM&HD@iKL3=&HvbNDD7(lBaZdTgGs=&6 zkZc$|jXb^(7BV?(wzwacmNNpyOdC(l%zkTA{(sG#XH-+&y0%eN zM8JX~O`3FRO7B6CDn)t+X;K72q_;$rA|0fc1f>cnk=_Xip+gX)hu*vNKqzP7d*1zh z`;4>q{(CY;eq^jQ#>!Z8KABnfJ)irsHoi_TlmcN=|9D70n|}8Nme^q}#5hvs(=4TQ zqUMIqjz&wYfiJk8ru}=$;WloaI-+K^+Lb6&Q9UNF%yzjirof!waXEGS?_M^?;^OoY zTBW=)M$xYnJPubMxfVW+SXl_ri&0Khlk2+pv6r@LDk6OVc&}WPJCpbf;7xMGJA@1R zeJ=cFN$2FDeNZv?(HlPaBI#l%sN52Rn9Cl~F$%`2KAR#>Jl|qp=N1sHr=TduUFp(C zOs7V-CFFcxT!Zi7n=eq*NpuoXu?7S~(?z74`rRe^NiJpFaam6cRijcH8R*@t#yi53 zFV{S_bYthP_9*iKP}IF+AIp6SCE6srM~IeWYU^7@J7EBdIwtazVT*0FJwdio#wUs_ zuaw%cfhq719D0odu~GbJdNh}0V%%)n%jb!`af7Y{e?Ua=8{UH6p5&h^KU(LVaQnj-yFmMwPF<0d9ZPG?a{Ys+MzV2 zW}UHp>Q>z2_OW|%CCx|$nv4d|-+cz=~XmR*#E*I_o-Zee{t2HrXl!O`Go z+#-`YS4|aGgd=TXl@*(4|C+!h(U{V`r&Kua1hX(aY732xpi<(p`Y z$|==qEXrE?W74d}ha^9UQI>6$zY(^cTz7f-7nm^?K8;=VZ%Mjzl7tbNa616U)#V#u zPQIuXFss+ZH+k5*(64+8>4m&xL`*KTr_1W}tGp3)=gwOM6@0T3QI-=}db2nJQI{YH zbIT>f;`#CWKMmAbKI%<3vX@cD)*F9jahmSxp^)&sXa3Mus_R+h6-&&k7}J++r5ioZ z0z;daz=ZEOg6`>3 z$(8%a$Q&hs81;Rt<*(-!b*!Ny0`}I66rT;o2RvVTj+JxFFQY15OGwK~9A~5EW9|yl zMBcYWYub3}$KChjCveoT?H=O9#!vi)F89aO#2;xy5<(h4xEN0rCE zr<(2;NelAA4Vu!xIRZ9-%BC>UMD?8dD5c}UXiXdo(cAZ>T%b8-fLTpO-A4GH&O{Ox zY-;W=yYm(xTzE2vd2mJ{Cr=~7J)>;rt0bsq&05jB+h6g+KsSm=#W#RuVY#{?^@n9a z39u{@7H4LR$XG4SX~l>ngi~f!~p%V z92?Lkitq9kPPYy(XEb=AZF@j}pL?TY?Ut(5(DJiI=dLH;+xrE~9>tlx#3!XeXkBSh zXHL&`jA@9@PkF+Hv(3d20Ji$%Z*29r)2)6ovL1^(My^;M^RM&cRtg%uLcF=QGs~mz zpUm)VZCt%yxXZ9Kl3W{9_PKz9k9)h?wm6(PViLXx0@<*+#3SJ|d}-OTX)K7pI0 zXzk(h54S-en+uHTk1FXqd)rU6Q!}1ogTXjx5A$TaPdBEm z^p}GC>!dU{g!$uces>(W1{Iy{OuBV;sC9YFBOx3jdwn9l@n_vhE2HHZe-g#g>mX%x zk}<+N#dvn9qvMx`LX&NacTt#6A$4b3*CFriKDy)> zNeG<+N~faAlSf16VMdWjxhx!TJZ`ObX)3Y%Ns2jhcFU|2aqw-B{dkK$RFUSL$v zpi`k8RpS2Z9^yg`YoaWSKz#ke+|wI;BHy7Chu!c)LSkoliexbnPbvpUKd&}3(3Pm7 z*ki({;dohU(gYEaWn-Qhd}ZQ0#<|7=NIrUpm=T$AjYEoU%nvD!V< zzQSSlIU<6lDaMSBn0N<0?#Rx!PwERb=;}O8iPxdmw1pBZzNJ^Kag}8Dy*9VC$#{NZ zpE+Fg3f<(W!gbzW57OvGMf3h-*#4E2$3b`x131n{*9VMPe-C0YqkfZ(-`xK<10bri z#_O8E`oTCA=!?LHA5Z*Zj*B&3Jke3I86DPoNOf|X;UA_&g4AqpU)z+m$N0vDp`ml; zDtE+r_Wmeic=O?WUtS%~`@yBSjG2AS(2HW3ZO;AOy35RAcE*cDb~OUQM;!rH-upeF zCTGV&XBG9I7WHmm0whmisLva@hq$S&olviNYWixi6piy8>e@G4NM6HGFB~=f8oVh} zQSCKR2x?Ofc+(*}`5acS3VFu$-_A4A)k0|;F!QfiK;lj8;3Kf|wtAFADD54x={ z@bgI>`=v4OvwFPApox3-hOOtxmCdz@w6qCJ(XSGAD?OOOl_FgPf4`MRte8-IqaO5& zHH6tY*=1u^hwLHCNQ4Iyd^f2eV>2-}F*Q=4gpA2aEk^qqhu41ck8q8MT(u@n4@U!K ztxE>uo>t2|cigbhL}r^$qfw=Mr;>FPL2sXGSBsPcI*|-R_C5xeGL}EGEByCd!v$DyG%%)=Z$?6 zeo{vZ8@4O0Gj$&LXPFgn3-g7kf~OHF(h)<|zI?PJJp=^gkCmT2)j1kZBz^E=Nm)M2 z{%HNwhtuE8J7x+i5~#fkW$DVFrhEd&Kh%kWHU#oS8YHh$!xof{w+m}SNb8$; zPVn;EwJWG4qdYiy-VbI!^}v=?Wq-_ z#4dT#J3k`;Iv{6@T5-13$2^CdO!W$xauo>)tD9*HlxIx(P^E#33Xz4;0809SvIK6p zQ~RF&X?W{GcX~k_eNr?bfvq$?iO0%TVL_AIEzOPLZo!oBV}7%}mhD1=DozJ-IirFY z2;Ii8OdWXj2;f2iG5!s3J{5@MQo)w%uw(jjPMNj-#2A8v<`r=a=l){S%&gmfJVxsA zZFBjo-&)&bv?3;cOTW?*rEeIGvGNY$WU~770<|G+!>hrv)Tv^Dp@$Kf^-c`ClP29w z1#xo|U(H{mvv$&cAlg!_yx*$S#&Gn7DTzVSn<-xlR|{B)fI%okU8VzF1zwO9cE+rk zg}+nfDKVNld2-K)i~-Z$D4Pj7iGk5QLY0>JYmgs+K&?D+A*Bb?;vXHN$qo-xz19!A zuS>UyxKfsImT%u;mTEd-K^O!CQ9N>Gg%7&9mG{Xxc5e`T#Slaz_x^%&oUy(hkB**8uhY> z>BjmhFd4eWt7~4jmMgS&yzU=`RwxY3rIP+??(=*7p|H*T?yt&s1BnXV#dE(-IT8!o zp62}RxikWvi^=FfZsqBt@X;3!P&HQ^(k!r26%=;Ep3uG*b>Mzr?5kwAb8~dj_FRk% z4A-P_EVn#0=GHtL7^P9S)wY|46$9KTmy%Xi-RR5qc7IHDy?YlHGMMp6kL^UZIpvMM z=wz>>#{>1+R{6?vXwgK}SEKLA6oQJ>Q8WQM%`+U}hv7MXQjQK&A(xy4dpE{9XN)|Z zI&a+I5TyEWq26Dv{S$p;9T#t@xPOads`pD$|GT1zaVy>kD-G3ho@mz4b+PpD{u+{r zQ$Wz0z2A*fWUyFN6Z1QtyeyneHPP;-8(DN3OCxiBiRnu|(P)A}@F)ObNe>)25WxHvyuM2RYMV)PE z!if;hZ1H0ZgAUQp+w`|P7Q@|A4hS$H@dRiN-T>EghsL!CY-eh)+0bd9Z=t$@r~Af2*Vch9o=DRSzG-}^$R>%MDZql}ZZ^yPlY3Nvwz?XZ zf09B{TVGfF>n*`$3JD@DSCdi(qwUPyFC4j1n`*-^%_VzO1}zo4kQ~vQb$-7q3q<+m zV~yuZ-p66rNko4UoER6xxGu=8O?P0w*^4F+$d3DDvLp%bc+?Qb5xa;Y{n2v!dG4RW zIz+-mq{9lBpHeT?PrP`93La$4MEHh}andE|2b0d{Suj=kGqMeae1A*;688!4s3zN} zyGLHYM@1vl&=7L^SI9#!z*U) zR>P|J*!6fUT(vmSSH-fktu1OH!#QsYDf5h&j)ck3<0MEL1`e;IGMPaY4vsJNcCPZj zbnSiDxXp0xkEwk{b{BT6OKxn4K7s7ck76kb1a~;xGsu4>B)yX8$ULI`UA6RNv(EP# zbFW`(FIFLakIUJsic2({Pg`pWNIU{nM^&OE(1W{5SsaC9X0A)}nW*EiZ}$3#i4`$_ zVQoK}*bHCFhj^b@Phn#5;5(lj?y<3G-X05Tm!4dobQ16|dBEa~dk5C|Qbxki^NaX! z_v->V9}lv2ED^ZF^k3du<~)z~@M>w*_A?!a??nKpXAgee{OS!EFn(pjTy9~YFn(l9 z`qhy=QL<5=1#Sbo+sACunb9>n5Lq=a_3~VJur4ro4DAgS2^Tq---c*=eV(Etu=Ak3 z>T$IIOd`Y|?RLK_=jdr^MQKA-u7G+C|4kv{)9nVP9^o??TPzO-#T%IsSJbOPB)5^r z9Hr0y&u+x81SLg(cQigXB2M-LP1(Pa#beXxfh7ha-sAjFTKMilQln1X4I7c!*D*yd z+wcKyErHG#HRYd8NrFmseQ)(#AtL%L&ab4*2bQ#T<`qi~^4K7Jam&_ZYJy29CLq=# zc}|~ytCUV3(euKOSU{-EwCrYTWZh+U2LpmjY)aoArK`xqfC>aM>P}Kwc}45s-$Ipt zAKlvX^(H^WN+eeu`9_=&V5#6I0bk&-zlhKR1TYhzfJgy*N%Sff#g*QNZ5HUf_ z;U>G#5f*SFSramV;Yu>yf;AnDBNk@p4ZoUIQPF!v*}wO(jGTN9)or2`Lv=()_gz;*DytL4II>6(TII#t ztK@}dNkQhH81DD92Kn_Ez?N6cvlf7s1xBe@ZQD-1e7O4VXIP6VY2mk>0cv3y%K+N%EE!tXCS6mmJT^rYcRSO%VtpdyQf|;G zMC4?YzrtO}+cq2z0SY#g#jgOsTE3_^>F+7cpQW7~PP2E0Y! zxN>vChzfH#NZlhi=niv@i}#kp5Zhoe3M1pc50qjCFg_|b$)nR5HS95cpP6(~+!hW3 zF*9(r5lbsEUEi_70MayXhG-0;=}RF(V>m72^*{o6?KI+QEx(H$y5*}$yYCjP(|3z+ zzSdw)#PMvr3P)3>u!&$85j@ajJ3H1q%iD*EBDS6;F*Q>yr!*&0fVB1x=1T4j`PIik zVN~cjzM~SWwkPByjWpPVrqeLPgH`gVRUpZzd)`j$Rs3wS? z7t!@B^Q~sa{!eY%oB|}?-g*}88RyqS!gaG300V3tpT$n8qSB1#q~}B2;Fz(htNpHy zqVS%VyJSptu;+p@+m6w?moUPn%aaF}+Xljw$9JR(@feHM^gQB``jGb>K^#K_W1j1+ zjho)wK0cRx-B4n~`Fx;q+Sq!jb}S^Gg{Z9X*f(`6?n8 zoL{yk5V36$Q_dM|9bz6SDDA&B{kgb-h1a}&)mkD3oyezm=d6>IzUq*Lm|?xG? zNvz+!oVex7m+EKLLZoL%oo%vQ+78$4tf9PW`XAM>u@Bz7Q^@-HZXHK;lOClx)#EEP zj5_|#5?}*6PyRuz8A1JqOKPIC%1b(GYswtGN>k_^`J7$v_oDHNYKmY)fTri;RxJhS zD}Kd_O_q)qv=XAVI|9=DYF1-05Xr|QP3T?Qk^Mn-mRnw7>^`)};C~}0W028Mmx)?QQ3m1yRXn=;rW$^jRkMrfgVbsOucvQ5;&)!(H#b9zJIMT7a~st zpD99ljzb381k!kjw2(&KcGYboNsmKoukE+O+BS_qtU3&LnV4-K+g8yT_j&`*0FC|S zww&dpb^W|Nc*wc`9j`Xz>;yx9K}Cjm>EZX6_8Mlemo%jc?EFCEe}H^q05CKDlf?Xd zNs*Og!r3>*+0O2!#8tyk$0JMDDf-t1OE?n{pDC?K-yparHG>zJ7w&YqQB@vNkP=>> zF^#J)ey>uX8GKCP@>BVTbC4sl$&vUxJZ_j`3#nKdK{-5p7;FcUf;^BZjVrSds$H!f z%g<|2Xu!Ze0`q|*?_Vj%M+)gCe?$~$Yy8^^Q}A9b3Ph_iZD$6xy8Q%}(iOH_&MU;# zfZgft!jglKmfH_g5$`Y`M!Qi{NkYi_W>3yx&m;3={SR!p*97(lLH%6E$Y8(+U+ON0 zm>g=JbIsOYsIk+7pYtwu%BEK}O-*wK;%s8fG35VHbW^7(d*rhySc_dF;oaiqR{kl0 z3?jv1$i|>Q$wwE=VxFGXj*YZ-DSa%K^$yC7d@Ja7rlvLI*!&rnie+1B_G>=_S=l`W za%ev}u|A+%XNKAf&sKn0imJPqpxZ2yBuvld{z str: + """ + Код валюты в соответствии с общероссийским классификатором валют (ОК (МК (ИСО 4217) 003-97) 014-2000). + + see https://classifikators.ru/okv + + Raises: + ValueError: if currency is unsupported + + """ + if self == Currency.USD: + return '840' + elif self == Currency.RUB: + return '643' + elif self == Currency.EUR: + return '978' + raise ValueError(self) diff --git a/investments/ibdds/__main__.py b/investments/ibdds/__main__.py new file mode 100644 index 0000000..8095df0 --- /dev/null +++ b/investments/ibdds/__main__.py @@ -0,0 +1,3 @@ +from investments.ibdds.ibdds import main + +main() diff --git a/investments/ibdds/ibdds.py b/investments/ibdds/ibdds.py new file mode 100644 index 0000000..bd920a3 --- /dev/null +++ b/investments/ibdds/ibdds.py @@ -0,0 +1,95 @@ +""" +Утилита для подготовки отчёта о движении денежных средств для брокера Interactive Brokers (USA). + +Актуальна на 15.01.2021 +Отчёт нужно подавать каждый год до 1 июня или в течение месяца после закрытия счёта. +В будущем (уже в отчёте за 2021 год) обещают обновить требования в пользу большей детализации, но пока так. +@see статью 12 173-ФЗ «О валютном регулировании» + +""" + +import argparse +import csv +import logging +from pathlib import Path +from typing import List + +from tabulate import tabulate + +from investments.cash import Cash +from investments.money import Money +from investments.report_parsers.ib import InteractiveBrokersReportParser + + +class InteractiveBrokersCashReportParser(InteractiveBrokersReportParser): + def parse_csv(self, *, activity_report_filepath: Path, **kwargs): + with open(activity_report_filepath, newline='') as activity_fh: + self._real_parse_activity_csv(csv.reader(activity_fh, delimiter=','), { + 'Cash Report': self._parse_cash_report, + }) + + +def parse_reports(activity_report_filepath: str) -> InteractiveBrokersCashReportParser: + parser_object = InteractiveBrokersCashReportParser() + + activity_report = Path(activity_report_filepath) + logging.info(f'Activity report {activity_report}') + + logging.info('start reports parse') + parser_object.parse_csv(activity_report_filepath=activity_report) + logging.info(f'end reports parse {parser_object}') + + return parser_object + + +def dds_specific_round(source_amount: Money) -> Money: + return (source_amount / 1000).round(3) + + +def show_report(cash: List[Cash]): + currencies = set(map(lambda x: x.amount.currency, cash)) + logging.info(f'currency={currencies}') + + for currency in currencies: + operations = [op for op in cash if op.amount.currency == currency] + begin_amount = dds_specific_round([op.amount for op in operations if op.description == 'Starting Cash'][0]) + end_amount = dds_specific_round([op.amount for op in operations if op.description == 'Ending Cash'][0]) + + deposits = [op.amount for op in operations if 'Cash' not in op.description and op.amount > Money(0, op.amount.currency)] + deposits_amount = dds_specific_round(sum(deposits) if deposits else Money(0, currency)) + + withdrawals = [op.amount for op in operations if 'Cash' not in op.description and op.amount < Money(0, op.amount.currency)] + withdrawals_amount = dds_specific_round(sum(withdrawals) if withdrawals else Money(0, currency)) + + report = [ + [f'{currency.name} {currency.iso_numeric_code()}', 'Сумма в тысячах единиц'], + ['Остаток денежных средств на счете на начало отчетного периода', begin_amount], + ['Зачислено денежных средств за отчетный период', deposits_amount], + ['Списано денежных средств за отчетный период', abs(withdrawals_amount)], + ['Остаток денежных средств на счете на конец отчетного периода', end_amount], + ] + + print('\n') + print(tabulate(report, headers='firstrow', tablefmt='presto', colalign=('right', 'decimal'))) + print('\n') + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--activity-report-filepath', type=str, required=True, help='InteractiveBrokers .csv activity report file path') + parser.add_argument('--verbose', nargs='?', default=False, const=True, help='details mode') + args = parser.parse_args() + + if args.verbose: + logging.basicConfig(level=logging.INFO) + + parser_object = parse_reports(args.activity_report_filepath) + + cash_report = parser_object.cash + logging.info(f'cash report={cash_report}') + + show_report(cash_report) + + +if __name__ == '__main__': + main() diff --git a/investments/ibtax/ibtax.py b/investments/ibtax/ibtax.py index 3926fa6..eedfa7b 100644 --- a/investments/ibtax/ibtax.py +++ b/investments/ibtax/ibtax.py @@ -35,8 +35,14 @@ def prepare_trades_report(finished_trades: List[FinishedTrade], cbr_client_usd: df['fee_per_piece'] = df.apply(lambda x: x['fee_per_piece'].round(digits=fee_round_digits), axis=1) df['fee'] = df.apply(lambda x: (x['fee_per_piece'] * abs(x['quantity'])).round(digits=fee_round_digits), axis=1) - df['total'] = df.apply(lambda x: compute_total_cost(x['quantity'], x['price'], x['fee_per_piece']).round(digits=2), axis=1) - df['total_rub'] = df.apply(lambda x: compute_total_cost(x['quantity'], x['price_rub'], x['fee_per_piece_rub']).round(digits=2), axis=1) + df['total'] = df.apply( + lambda x: compute_total_cost(x['quantity'], x['price'], x['fee_per_piece']).round(digits=2), + axis=1, + ) + df['total_rub'] = df.apply( + lambda x: compute_total_cost(x['quantity'], x['price_rub'], x['fee_per_piece_rub']).round(digits=2), + axis=1, + ) df = df.join(cbr_client_usd.dataframe['rate'].rename('settle_rate'), how='left', on=tax_date_column) df = df.join(cbr_client_usd.dataframe['rate'].rename('fee_rate'), how='left', on=trade_date_column) @@ -69,7 +75,10 @@ def prepare_dividends_report(dividends: List[Dividend], cbr_client_usd: cbr.Exch df['tax_paid_rub'] = df.apply(lambda x: cbr_client_usd.convert_to_rub(x['tax_paid'], x[operation_date_column]).round(digits=2), axis=1) if verbose: - df['tax_rate'] = df.apply(lambda x: round(x['tax_paid'].amount * 100 / x['amount'].amount, 2), axis=1) + df['tax_rate'] = df.apply( + lambda x: round(x['tax_paid'].amount * 100 / x['amount'].amount, 2), + axis=1, + ) return df diff --git a/investments/report_parsers/ib.py b/investments/report_parsers/ib.py index f8aac83..9264a10 100644 --- a/investments/report_parsers/ib.py +++ b/investments/report_parsers/ib.py @@ -4,6 +4,7 @@ import re from typing import Dict, Iterator, List, Tuple +from investments.cash import Cash from investments.currency import Currency from investments.dividend import Dividend from investments.fees import Fee @@ -102,6 +103,7 @@ def __init__(self): self._dividends = [] self._fees: List[Fee] = [] self._interests: List[Interest] = [] + self._cash: List[Cash] = [] self._deposits_and_withdrawals = [] self._tickers = TickersStorage() self._settle_dates = {} @@ -129,6 +131,10 @@ def fees(self) -> List[Fee]: def interests(self) -> List[Interest]: return self._interests + @property + def cash(self) -> List[Cash]: + return self._cash + def parse_csv(self, *, activity_csvs: List[str], trade_confirmation_csvs: List[str]): # 1. parse tickers info for ac_fname in activity_csvs: @@ -158,6 +164,7 @@ def parse_csv(self, *, activity_csvs: List[str], trade_confirmation_csvs: List[s # 'Mark-to-Market Performance Summary', # 'Net Asset Value', 'Notes/Legal Notes', 'Open Positions', 'Realized & Unrealized Performance Summary', # 'Statement', '\ufeffStatement', 'Total P/L for Statement Period', 'Transaction Fees', + 'Cash Report': self._parse_cash_report, }) # 4. sort @@ -313,3 +320,11 @@ def _parse_interests(self, f: Dict[str, str]): amount = Money(f['Amount'], currency) description = f['Description'] self._interests.append(Interest(date, amount, description)) + + def _parse_cash_report(self, f: Dict[str, str]): + currency_code = f['Currency'] + if currency_code != 'Base Currency Summary': + currency = Currency.parse(currency_code) + description = f['Currency Summary'] + amount = Money(f['Total'], currency) + self._cash.append(Cash(description, amount)) diff --git a/poetry.lock b/poetry.lock index 0d3fac3..bb4182e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,167 +1,156 @@ [[package]] -category = "main" -description = "Async http client/server framework (asyncio)" name = "aiohttp" +version = "3.7.0" +description = "Async http client/server framework (asyncio)" +category = "main" optional = false python-versions = ">=3.6" -version = "3.7.0" [package.dependencies] async-timeout = ">=3.0,<4.0" attrs = ">=17.3.0" chardet = ">=2.0,<4.0" +idna-ssl = {version = ">=1.0", markers = "python_version < \"3.7\""} multidict = ">=4.5,<7.0" +typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.7\""} yarl = ">=1.0,<2.0" -[package.dependencies.idna-ssl] -python = "<3.7" -version = ">=1.0" - -[package.dependencies.typing-extensions] -python = "<3.7" -version = ">=3.6.5" - [package.extras] speedups = ["aiodns", "brotlipy", "cchardet"] [[package]] -category = "main" -description = "Asyncio MOEX ISS API" name = "aiomoex" +version = "1.2.2" +description = "Asyncio MOEX ISS API" +category = "main" optional = false python-versions = ">=3.6" -version = "1.2.2" [package.dependencies] aiohttp = "*" [[package]] -category = "dev" -description = "Read/rewrite/write Python ASTs" name = "astor" +version = "0.8.1" +description = "Read/rewrite/write Python ASTs" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "0.8.1" [[package]] -category = "main" -description = "Timeout context manager for asyncio programs" name = "async-timeout" +version = "3.0.1" +description = "Timeout context manager for asyncio programs" +category = "main" optional = false python-versions = ">=3.5.3" -version = "3.0.1" [[package]] -category = "dev" -description = "Atomic file writes." -marker = "sys_platform == \"win32\"" name = "atomicwrites" +version = "1.4.0" +description = "Atomic file writes." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.4.0" [[package]] -category = "main" -description = "Classes Without Boilerplate" name = "attrs" +version = "20.2.0" +description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.2.0" [package.extras] -dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] +dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "sphinx-rtd-theme", "pre-commit"] docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] -tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] +tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] [[package]] -category = "dev" -description = "Security oriented static analyser for python code." name = "bandit" +version = "1.6.2" +description = "Security oriented static analyser for python code." +category = "dev" optional = false python-versions = "*" -version = "1.6.2" [package.dependencies] +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} GitPython = ">=1.0.1" PyYAML = ">=3.13" -colorama = ">=0.3.9" six = ">=1.10.0" stevedore = ">=1.20.0" [[package]] -category = "main" -description = "Python package for providing Mozilla's CA Bundle." name = "certifi" +version = "2020.6.20" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = "*" -version = "2020.6.20" [[package]] -category = "main" -description = "Universal encoding detector for Python 2 and 3" name = "chardet" +version = "3.0.4" +description = "Universal encoding detector for Python 2 and 3" +category = "main" optional = false python-versions = "*" -version = "3.0.4" [[package]] -category = "dev" -description = "Cross-platform colored terminal text." -marker = "sys_platform == \"win32\" or platform_system == \"Windows\"" name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.4.4" [[package]] -category = "dev" -description = "A utility for ensuring Google-style docstrings stay up to date with the source code." name = "darglint" +version = "1.5.5" +description = "A utility for ensuring Google-style docstrings stay up to date with the source code." +category = "dev" optional = false python-versions = ">=3.5,<4.0" -version = "1.5.5" [[package]] -category = "dev" -description = "Docutils -- Python Documentation Utilities" name = "docutils" +version = "0.16" +description = "Docutils -- Python Documentation Utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "0.16" [[package]] -category = "dev" -description = "Removes commented-out code." name = "eradicate" +version = "1.0" +description = "Removes commented-out code." +category = "dev" optional = false python-versions = "*" -version = "1.0" [[package]] -category = "dev" -description = "the modular source code checker: pep8 pyflakes and co" name = "flake8" +version = "3.8.4" +description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -version = "3.8.4" [package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.6.0a1,<2.7.0" pyflakes = ">=2.2.0,<2.3.0" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = "*" - [[package]] -category = "dev" -description = "Automated security testing with bandit and flake8." name = "flake8-bandit" +version = "2.1.2" +description = "Automated security testing with bandit and flake8." +category = "dev" optional = false python-versions = "*" -version = "2.1.2" [package.dependencies] bandit = "*" @@ -170,85 +159,82 @@ flake8-polyfill = "*" pycodestyle = "*" [[package]] -category = "dev" -description = "Flake8 plugin to forbid backslashes for line breaks" name = "flake8-broken-line" +version = "0.2.1" +description = "Flake8 plugin to forbid backslashes for line breaks" +category = "dev" optional = false python-versions = ">=3.6,<4.0" -version = "0.2.1" [package.dependencies] flake8 = ">=3.5,<4.0" [[package]] -category = "dev" -description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." name = "flake8-bugbear" +version = "19.8.0" +description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." +category = "dev" optional = false python-versions = ">=3.5" -version = "19.8.0" [package.dependencies] attrs = "*" flake8 = ">=3.0.0" [[package]] -category = "dev" -description = "Flake8 lint for trailing commas." name = "flake8-commas" +version = "2.0.0" +description = "Flake8 lint for trailing commas." +category = "dev" optional = false python-versions = "*" -version = "2.0.0" [package.dependencies] flake8 = ">=2,<4.0.0" [[package]] -category = "dev" -description = "A flake8 plugin to help you write better list/set/dict comprehensions." name = "flake8-comprehensions" +version = "3.3.0" +description = "A flake8 plugin to help you write better list/set/dict comprehensions." +category = "dev" optional = false python-versions = ">=3.5" -version = "3.3.0" [package.dependencies] flake8 = ">=3.0,<3.2.0 || >3.2.0,<4" - -[package.dependencies.importlib-metadata] -python = "<3.8" -version = "*" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} [[package]] -category = "dev" -description = "ipdb/pdb statement checker plugin for flake8" name = "flake8-debugger" +version = "3.2.1" +description = "ipdb/pdb statement checker plugin for flake8" +category = "dev" optional = false python-versions = "*" -version = "3.2.1" [package.dependencies] flake8 = ">=1.5" pycodestyle = "*" [[package]] -category = "dev" -description = "Extension for flake8 which uses pydocstyle to check docstrings" name = "flake8-docstrings" +version = "1.5.0" +description = "Extension for flake8 which uses pydocstyle to check docstrings" +category = "dev" optional = false python-versions = "*" -version = "1.5.0" [package.dependencies] flake8 = ">=3" pydocstyle = ">=2.1" [[package]] -category = "dev" -description = "Flake8 plugin to find commented out code" name = "flake8-eradicate" +version = "0.3.0" +description = "Flake8 plugin to find commented out code" +category = "dev" optional = false python-versions = ">=3.6,<4.0" -version = "0.3.0" [package.dependencies] attrs = "*" @@ -256,119 +242,114 @@ eradicate = ">=1.0,<2.0" flake8 = ">=3.5,<4.0" [[package]] -category = "dev" -description = "flake8 plugin that integrates isort ." name = "flake8-isort" +version = "3.0.1" +description = "flake8 plugin that integrates isort ." +category = "dev" optional = false python-versions = "*" -version = "3.0.1" [package.dependencies] flake8 = ">=3.2.1,<4" +isort = {version = ">=4.3.5,<5", extras = ["pyproject"]} testfixtures = ">=6.8.0,<7" -[package.dependencies.isort] -extras = ["pyproject"] -version = ">=4.3.5,<5" - [package.extras] test = ["pytest (>=4.0.2,<6)"] [[package]] -category = "dev" -description = "Polyfill package for Flake8 plugins" name = "flake8-polyfill" +version = "1.0.2" +description = "Polyfill package for Flake8 plugins" +category = "dev" optional = false python-versions = "*" -version = "1.0.2" [package.dependencies] flake8 = "*" [[package]] -category = "dev" -description = "Flake8 lint for quotes." name = "flake8-quotes" +version = "2.1.2" +description = "Flake8 lint for quotes." +category = "dev" optional = false python-versions = "*" -version = "2.1.2" [package.dependencies] flake8 = "*" [[package]] -category = "dev" -description = "Python docstring reStructuredText (RST) validator" name = "flake8-rst-docstrings" +version = "0.0.12" +description = "Python docstring reStructuredText (RST) validator" +category = "dev" optional = false python-versions = "*" -version = "0.0.12" [package.dependencies] flake8 = ">=3.0.0" restructuredtext_lint = "*" [[package]] -category = "dev" -description = "string format checker, plugin for flake8" name = "flake8-string-format" +version = "0.2.3" +description = "string format checker, plugin for flake8" +category = "dev" optional = false python-versions = "*" -version = "0.2.3" [package.dependencies] flake8 = "*" [[package]] -category = "dev" -description = "Git Object Database" name = "gitdb" +version = "4.0.5" +description = "Git Object Database" +category = "dev" optional = false python-versions = ">=3.4" -version = "4.0.5" [package.dependencies] smmap = ">=3.0.1,<4" [[package]] -category = "dev" -description = "Python Git Library" name = "gitpython" +version = "3.1.11" +description = "Python Git Library" +category = "dev" optional = false python-versions = ">=3.4" -version = "3.1.11" [package.dependencies] gitdb = ">=4.0.1,<5" [[package]] -category = "main" -description = "Internationalized Domain Names in Applications (IDNA)" name = "idna" +version = "2.10" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.10" [[package]] -category = "main" -description = "Patch ssl.match_hostname for Unicode(idna) domains support" -marker = "python_version < \"3.7\"" name = "idna-ssl" +version = "1.1.0" +description = "Patch ssl.match_hostname for Unicode(idna) domains support" +category = "main" optional = false python-versions = "*" -version = "1.1.0" [package.dependencies] idna = ">=2.0" [[package]] -category = "dev" -description = "Read metadata from Python packages" -marker = "python_version < \"3.8\"" name = "importlib-metadata" +version = "2.0.0" +description = "Read metadata from Python packages" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" -version = "2.0.0" [package.dependencies] zipp = ">=0.5" @@ -378,17 +359,15 @@ docs = ["sphinx", "rst.linker"] testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] [[package]] -category = "dev" -description = "A Python utility / library to sort Python imports." name = "isort" +version = "4.3.21" +description = "A Python utility / library to sort Python imports." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "4.3.21" [package.dependencies] -[package.dependencies.toml] -optional = true -version = "*" +toml = {version = "*", optional = true, markers = "extra == \"pyproject\""} [package.extras] pipfile = ["pipreqs", "requirementslib"] @@ -397,36 +376,36 @@ requirements = ["pipreqs", "pip-api"] xdg_home = ["appdirs (>=1.4.0)"] [[package]] -category = "dev" -description = "McCabe checker, plugin for flake8" name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "dev" optional = false python-versions = "*" -version = "0.6.1" [[package]] -category = "dev" -description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" +version = "8.5.0" +description = "More routines for operating on iterables, beyond itertools" +category = "dev" optional = false python-versions = ">=3.5" -version = "8.5.0" [[package]] -category = "main" -description = "multidict implementation" name = "multidict" +version = "5.0.0" +description = "multidict implementation" +category = "main" optional = false python-versions = ">=3.5" -version = "5.0.0" [[package]] -category = "dev" -description = "Optional static typing for Python" name = "mypy" +version = "0.782" +description = "Optional static typing for Python" +category = "dev" optional = false python-versions = ">=3.5" -version = "0.782" [package.dependencies] mypy-extensions = ">=0.4.3,<0.5.0" @@ -437,40 +416,40 @@ typing-extensions = ">=3.7.4" dmypy = ["psutil (>=4.0)"] [[package]] -category = "dev" -description = "Experimental type system extensions for programs checked with the mypy typechecker." name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" optional = false python-versions = "*" -version = "0.4.3" [[package]] -category = "main" -description = "NumPy is the fundamental package for array computing with Python." name = "numpy" +version = "1.19.2" +description = "NumPy is the fundamental package for array computing with Python." +category = "main" optional = false python-versions = ">=3.6" -version = "1.19.2" [[package]] -category = "dev" -description = "Core utilities for Python packages" name = "packaging" +version = "20.4" +description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "20.4" [package.dependencies] pyparsing = ">=2.0.2" six = "*" [[package]] -category = "main" -description = "Powerful data structures for data analysis, time series, and statistics" name = "pandas" +version = "1.1.3" +description = "Powerful data structures for data analysis, time series, and statistics" +category = "main" optional = false python-versions = ">=3.6.1" -version = "1.1.3" [package.dependencies] numpy = ">=1.15.4" @@ -481,151 +460,146 @@ pytz = ">=2017.2" test = ["pytest (>=4.0.2)", "pytest-xdist", "hypothesis (>=3.58)"] [[package]] -category = "dev" -description = "Python Build Reasonableness" name = "pbr" +version = "5.5.1" +description = "Python Build Reasonableness" +category = "dev" optional = false python-versions = ">=2.6" -version = "5.5.1" [[package]] -category = "dev" -description = "Check PEP-8 naming conventions, plugin for flake8" name = "pep8-naming" +version = "0.9.1" +description = "Check PEP-8 naming conventions, plugin for flake8" +category = "dev" optional = false python-versions = "*" -version = "0.9.1" [package.dependencies] flake8-polyfill = ">=1.0.2,<2" [[package]] -category = "dev" -description = "plugin and hook calling mechanisms for python" name = "pluggy" +version = "0.13.1" +description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "0.13.1" [package.dependencies] -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} [package.extras] dev = ["pre-commit", "tox"] [[package]] -category = "dev" -description = "library with cross-python path, ini-parsing, io, code, log facilities" name = "py" +version = "1.9.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "1.9.0" [[package]] -category = "dev" -description = "Python style guide checker" name = "pycodestyle" +version = "2.6.0" +description = "Python style guide checker" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.6.0" [[package]] -category = "dev" -description = "Python docstring style checker" name = "pydocstyle" +version = "5.1.1" +description = "Python docstring style checker" +category = "dev" optional = false python-versions = ">=3.5" -version = "5.1.1" [package.dependencies] snowballstemmer = "*" [[package]] -category = "dev" -description = "passive checker of Python programs" name = "pyflakes" +version = "2.2.0" +description = "passive checker of Python programs" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "2.2.0" [[package]] -category = "dev" -description = "Pygments is a syntax highlighting package written in Python." name = "pygments" +version = "2.7.2" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" optional = false python-versions = ">=3.5" -version = "2.7.2" [[package]] -category = "dev" -description = "Python parsing module" name = "pyparsing" +version = "2.4.7" +description = "Python parsing module" +category = "dev" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" -version = "2.4.7" [[package]] -category = "dev" -description = "pytest: simple powerful testing with Python" name = "pytest" +version = "5.4.3" +description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.5" -version = "5.4.3" [package.dependencies] -atomicwrites = ">=1.0" +atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} attrs = ">=17.4.0" -colorama = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} more-itertools = ">=4.0.0" packaging = "*" pluggy = ">=0.12,<1.0" py = ">=1.5.0" wcwidth = "*" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=0.12" - [package.extras] -checkqa-mypy = ["mypy (v0.761)"] +checkqa-mypy = ["mypy (==v0.761)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] -category = "main" -description = "Extensions to the standard Python datetime module" name = "python-dateutil" +version = "2.8.1" +description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -version = "2.8.1" [package.dependencies] six = ">=1.5" [[package]] -category = "main" -description = "World timezone definitions, modern and historical" name = "pytz" +version = "2020.1" +description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" -version = "2020.1" [[package]] -category = "dev" -description = "YAML parser and emitter for Python" name = "pyyaml" +version = "5.3.1" +description = "YAML parser and emitter for Python" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "5.3.1" [[package]] -category = "main" -description = "Python HTTP for Humans." name = "requests" +version = "2.24.0" +description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" -version = "2.24.0" [package.dependencies] certifi = ">=2017.4.17" @@ -635,65 +609,73 @@ urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" [package.extras] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] -category = "dev" -description = "reStructuredText linter" name = "restructuredtext-lint" +version = "1.3.1" +description = "reStructuredText linter" +category = "dev" optional = false python-versions = "*" -version = "1.3.1" [package.dependencies] docutils = ">=0.11,<1.0" [[package]] -category = "main" -description = "Python 2 and 3 compatibility utilities" name = "six" +version = "1.15.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -version = "1.15.0" [[package]] -category = "dev" -description = "A pure Python implementation of a sliding window memory map manager" name = "smmap" +version = "3.0.4" +description = "A pure Python implementation of a sliding window memory map manager" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -version = "3.0.4" [[package]] -category = "dev" -description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." name = "snowballstemmer" +version = "2.0.0" +description = "This package provides 26 stemmers for 25 languages generated from Snowball algorithms." +category = "dev" optional = false python-versions = "*" -version = "2.0.0" [[package]] -category = "dev" -description = "Manage dynamic plugins for Python applications" name = "stevedore" +version = "3.2.2" +description = "Manage dynamic plugins for Python applications" +category = "dev" optional = false python-versions = ">=3.6" -version = "3.2.2" [package.dependencies] +importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} pbr = ">=2.0.0,<2.1.0 || >2.1.0" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = ">=1.7.0" +[[package]] +name = "tabulate" +version = "0.8.7" +description = "Pretty-print tabular data" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +widechars = ["wcwidth"] [[package]] -category = "dev" -description = "A collection of helpers and mock objects for unit tests and doc tests." name = "testfixtures" +version = "6.15.0" +description = "A collection of helpers and mock objects for unit tests and doc tests." +category = "dev" optional = false python-versions = "*" -version = "6.15.0" [package.extras] build = ["setuptools-git", "wheel", "twine"] @@ -701,57 +683,57 @@ docs = ["sphinx", "zope.component", "sybil", "twisted", "mock", "django (<2)", " test = ["pytest (>=3.6)", "pytest-cov", "pytest-django", "zope.component", "sybil", "twisted", "mock", "django (<2)", "django"] [[package]] -category = "dev" -description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" +version = "0.10.1" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" optional = false python-versions = "*" -version = "0.10.1" [[package]] -category = "dev" -description = "a fork of Python 2 and 3 ast modules with type comment support" name = "typed-ast" +version = "1.4.1" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" optional = false python-versions = "*" -version = "1.4.1" [[package]] -category = "main" -description = "Backported and Experimental Type Hints for Python 3.5+" name = "typing-extensions" +version = "3.7.4.3" +description = "Backported and Experimental Type Hints for Python 3.5+" +category = "main" optional = false python-versions = "*" -version = "3.7.4.3" [[package]] -category = "main" -description = "HTTP library with thread-safe connection pooling, file post, and more." name = "urllib3" +version = "1.25.11" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" -version = "1.25.11" [package.extras] brotli = ["brotlipy (>=0.6.0)"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] -category = "dev" -description = "Measures the displayed width of unicode strings in a terminal" name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" optional = false python-versions = "*" -version = "0.2.5" [[package]] -category = "dev" -description = "The strictest and most opinionated python linter ever" name = "wemake-python-styleguide" +version = "0.14.1" +description = "The strictest and most opinionated python linter ever" +category = "dev" optional = false python-versions = ">=3.6,<4.0" -version = "0.14.1" [package.dependencies] astor = ">=0.8,<0.9" @@ -770,47 +752,40 @@ flake8-isort = ">=3.0.1,<4" flake8-quotes = ">=2.0.1,<3.0.0" flake8-rst-docstrings = ">=0.0.12,<0.0.13" flake8-string-format = ">=0.2,<0.3" +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} pep8-naming = ">=0.9.1,<0.10.0" pygments = ">=2.4,<3.0" typing_extensions = ">=3.6,<4.0" -[package.dependencies.importlib-metadata] -python = "<3.8" -version = "*" - [[package]] -category = "main" -description = "Yet another URL library" name = "yarl" +version = "1.6.2" +description = "Yet another URL library" +category = "main" optional = false python-versions = ">=3.6" -version = "1.6.2" [package.dependencies] idna = ">=2.0" multidict = ">=4.0" - -[package.dependencies.typing-extensions] -python = "<3.8" -version = ">=3.7.4" +typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} [[package]] -category = "dev" -description = "Backport of pathlib-compatible object wrapper for zip files" -marker = "python_version < \"3.8\"" name = "zipp" +version = "3.3.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" optional = false python-versions = ">=3.6" -version = "3.3.2" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] [metadata] -content-hash = "c4595048427b87381a334c5fdf68bc94765cba7004d1807026726e2711505eda" -lock-version = "1.0" +lock-version = "1.1" python-versions = "^3.6.1 || ^3.7" +content-hash = "efe87f2d5d0c7871e98db69689bc23d9fc3af0711cc99151b285a1f3d8047892" [metadata.files] aiohttp = [ @@ -1170,6 +1145,10 @@ stevedore = [ {file = "stevedore-3.2.2-py3-none-any.whl", hash = "sha256:5e1ab03eaae06ef6ce23859402de785f08d97780ed774948ef16c4652c41bc62"}, {file = "stevedore-3.2.2.tar.gz", hash = "sha256:f845868b3a3a77a2489d226568abe7328b5c2d4f6a011cc759dfa99144a521f0"}, ] +tabulate = [ + {file = "tabulate-0.8.7-py3-none-any.whl", hash = "sha256:ac64cb76d53b1231d364babcd72abbb16855adac7de6665122f97b593f1eb2ba"}, + {file = "tabulate-0.8.7.tar.gz", hash = "sha256:db2723a20d04bcda8522165c73eea7c300eda74e0ce852d9022e0159d7895007"}, +] testfixtures = [ {file = "testfixtures-6.15.0-py2.py3-none-any.whl", hash = "sha256:e17f4f526fc90b0ac9bc7f8ca62b7dec17d9faf3d721f56bda4f0fd94d02f85a"}, {file = "testfixtures-6.15.0.tar.gz", hash = "sha256:409f77cfbdad822d12a8ce5c4aa8fb4d0bb38073f4a5444fede3702716a2cec2"}, diff --git a/pyproject.toml b/pyproject.toml index 201c26a..d8d509d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,12 +17,14 @@ classifiers = [ [tool.poetry.scripts] ibtax = "investments.ibtax.ibtax:main" +ibdds = "investments.ibdds.ibdds:main" [tool.poetry.dependencies] python = "^3.6.1 || ^3.7" pandas = "^1.0.3" requests = "^2.23.0" aiomoex = "^1.2.2" +tabulate = "^0.8.7" [tool.poetry.dev-dependencies] pytest = "^5.4" diff --git a/tests/ibdds/show_report.py b/tests/ibdds/show_report.py new file mode 100644 index 0000000..5c259b8 --- /dev/null +++ b/tests/ibdds/show_report.py @@ -0,0 +1,54 @@ +from investments.cash import Cash +from investments.currency import Currency +from investments.ibdds.ibdds import show_report +from investments.money import Money + + +def test_full_report(capsys): + cash_operations = [ + Cash('deposit 1', Money(10.1, Currency.USD)), + Cash('deposit 2', Money(178945.78915, Currency.USD)), + Cash('wh 1', Money(-100.500, Currency.USD)), + Cash('Starting Cash', Money(0.500, Currency.USD)), + Cash('Ending Cash', Money(478.51, Currency.USD)), + Cash('Unknown Cash', Money(100500, Currency.USD)), + + Cash('Starting Cash', Money(0., Currency.RUB)), + Cash('deposit 1', Money(10.1, Currency.RUB)), + Cash('Ending Cash', Money(17.89, Currency.RUB)), + ] + + show_report(cash_operations) + + captured = capsys.readouterr() + assert 'Остаток денежных средств на счете на начало отчетного периода | 0.000₽' in captured.out + assert 'Зачислено денежных средств за отчетный период | 0.010₽' in captured.out + assert 'Списано денежных средств за отчетный период | 0.000₽' in captured.out + assert 'Остаток денежных средств на счете на конец отчетного периода | 0.018₽' in captured.out + + assert 'Остаток денежных средств на счете на начало отчетного периода | 0.000$' in captured.out + assert 'Зачислено денежных средств за отчетный период | 178.956$' in captured.out + assert 'Списано денежных средств за отчетный период | 0.100$' in captured.out + assert 'Остаток денежных средств на счете на конец отчетного периода | 0.479$' in captured.out + + +def test_empty_report(capsys): + cash_operations = [ + Cash('Starting Cash', Money(0, Currency.USD)), + Cash('Ending Cash', Money(0, Currency.USD)), + Cash('Starting Cash', Money(0, Currency.RUB)), + Cash('Ending Cash', Money(0, Currency.RUB)), + ] + + show_report(cash_operations) + + captured = capsys.readouterr() + assert 'Остаток денежных средств на счете на начало отчетного периода | 0.000$' in captured.out + assert 'Зачислено денежных средств за отчетный период | 0.000$' in captured.out + assert 'Списано денежных средств за отчетный период | 0.000$' in captured.out + assert 'Остаток денежных средств на счете на конец отчетного периода | 0.000$' in captured.out + + assert 'Остаток денежных средств на счете на начало отчетного периода | 0.000₽' in captured.out + assert 'Зачислено денежных средств за отчетный период | 0.000₽' in captured.out + assert 'Списано денежных средств за отчетный период | 0.000₽' in captured.out + assert 'Остаток денежных средств на счете на конец отчетного периода | 0.000₽' in captured.out diff --git a/tests/report_parsers/ib_test.py b/tests/report_parsers/ib_test.py index e396441..072dfac 100644 --- a/tests/report_parsers/ib_test.py +++ b/tests/report_parsers/ib_test.py @@ -2,6 +2,7 @@ import datetime from decimal import Decimal +from investments.cash import Cash from investments.currency import Currency from investments.fees import Fee from investments.interests import Interest @@ -141,6 +142,49 @@ def test_parse_interests(): description='USD Credit Interest for Feb-2020') +def test_parse_cash(): + p = InteractiveBrokersReportParser() + + lines = """Cash Report,Header,Currency Summary,Currency,Total,Securities,Futures,Month to Date,Year to Date, +Cash Report,Data,Starting Cash,Base Currency Summary,0,0,0,,, +Cash Report,Data,Commissions,Base Currency Summary,-82.64370531,-82.64370531,0,-8.62621762,-82.64370531, +Cash Report,Data,Deposits,Base Currency Summary,65663.765,65663.765,0,1625.96,65663.765, +Cash Report,Data,Dividends,Base Currency Summary,1045.45,1045.45,0,523.67,1045.45, +Cash Report,Data,Broker Interest Paid and Received,Base Currency Summary,0.13844211,0.13844211,0,0,0.13844211, +Cash Report,Data,Net Trades (Sales),Base Currency Summary,74217.07255104,74217.07255104,0,3923.70991107,74217.07255106, +Cash Report,Data,Net Trades (Purchase),Base Currency Summary,-140090.704656633,-140090.704656633,0,-7874.79454332,-140090.70465664, +Cash Report,Data,Other Fees,Base Currency Summary,-23.2,-23.2,0,-0.6,-23.2, +Cash Report,Data,Withholding Tax,Base Currency Summary,-103.55,-103.55,0,-51.39,-103.55, +Cash Report,Data,Cash FX Translation Gain/Loss,Base Currency Summary,-156.23378547,-156.23378547,0,,, +Cash Report,Data,Ending Cash,Base Currency Summary,470.093845757,470.093845757,0,,, +Cash Report,Data,Ending Settled Cash,Base Currency Summary,470.093845757,470.093845757,0,,, +Cash Report,Data,Starting Cash,RUB,0,0,0,,, +Cash Report,Data,Deposits,RUB,4495000,4495000,0,120000,4495000, +Cash Report,Data,Broker Interest Paid and Received,RUB,3.21,3.21,0,0,3.21, +Cash Report,Data,Net Trades (Purchase),RUB,-4495003.210000001,-4495003.210000001,0,-295619.97975,-4495003.21, +Cash Report,Data,Ending Cash,RUB,0,0,0,,, +Cash Report,Data,Ending Settled Cash,RUB,0,0,0,,, +Cash Report,Data,Starting Cash,USD,0,0,0,,, +Cash Report,Data,Commissions,USD,-82.64370531,-82.64370531,0,-8.62621762,-82.64370531, +Cash Report,Data,Dividends,USD,1045.45,1045.45,0,523.67,1045.45, +Cash Report,Data,Broker Interest Paid and Received,USD,0.09,0.09,0,0,0.09, +Cash Report,Data,Net Trades (Sales),USD,74217.07255104,74217.07255104,0,3923.70991107,74217.07255106, +Cash Report,Data,Net Trades (Purchase),USD,-74583.125,-74583.125,0,-3932.96,-74583.125, +Cash Report,Data,Other Fees,USD,-23.2,-23.2,0,-0.6,-23.2, +Cash Report,Data,Withholding Tax,USD,-103.55,-103.55,0,-51.39,-103.55, +Cash Report,Data,Ending Cash,USD,470.093845757,470.093845757,0,,, +Cash Report,Data,Ending Settled Cash,USD,470.093845757,470.093845757,0,,,""" + + lines = lines.split('\n') + p._real_parse_activity_csv(csv.reader(lines, delimiter=','), { + 'Cash Report': p._parse_cash_report, + }) + + assert len(p.cash) == 16 + assert p.cash[1] == Cash(amount=Money(4495000, Currency.RUB), description='Deposits') + assert p.cash[15] == Cash(amount=Money(470.093845757, Currency.USD), description='Ending Settled Cash') + + def test_parse_trades_with_fees(): p = InteractiveBrokersReportParser()