From 85c23b175462877ae29bf4e9a60743487239afb6 Mon Sep 17 00:00:00 2001 From: Chris Bingham Date: Sun, 15 Dec 2024 23:15:33 +0000 Subject: [PATCH 01/13] text file noodling --- docs/notes.md | 67 +++++++++++++++++++-------------------------------- readme.md | 2 +- 2 files changed, 26 insertions(+), 43 deletions(-) diff --git a/docs/notes.md b/docs/notes.md index 19e72e70..02af466f 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -26,10 +26,10 @@ todo: - moving averages - indicators - -> Build arm6 on x86 +# Docker +Build arm6 on x86 ```bash - docker run -e QEMU_CPU=arm1176 --privileged --rm -it --platform linux/arm/v6 balenalib/raspberry-pi:buster bash +docker run -e QEMU_CPU=arm1176 --privileged --rm -it --platform linux/arm/v6 balenalib/raspberry-pi:buster bash # build container atrm6 docker buildx build --platform linux/arm/v6 . -t bitbot --progress string # run it, have to specify which chip QEMU should emulate @@ -37,22 +37,27 @@ docker run -e QEMU_CPU=arm1176 --privileged --rm -t --platform linux/arm/v6 navi docker buildx build --platform linux/arm/v6 . -t bitbot -f scripts/docker/dockerfile --progress string docker run -e QEMU_CPU=arm1176 --privileged --rm -it --platform linux/arm/v6 bitbot +``` +# Hackery +```bash # remove all containers docker container rm $(docker container ls -q -a) #' which cpus to use for the build # --cpuset-cpus=0-3' -# wifi-connect docker pull balenablocks/wifi-connect:rpi -docker run --network=host -v /run/dbus/:/run/dbus/ balenablocks/wifi-connect:rpi # error: failed to solve: failed to solve with frontend dockerfile.v0: failed to create LLB definition: rpc error: code = Unknown desc = error getting credentials - err: exit status 255, out: `` = In ~/.docker/config.json `change credsStore to credStore` # error exec "--env" "executable file not found in $PATH: unknown" = badly ordered docker args, envs must come before image name +``` +# Testing +```bash # test run docker run --rm --env BITBOT_TESTRUN=true --env BITBOT_OUTPUT=disk --env BITBOT_SHOWIMAGE=false bb + # run tests docker run --rm \ --name bitbot_tests \ @@ -62,17 +67,24 @@ bb \ python3 -m unittest discover ``` -> get linux os version +# PiOS +Get linux os version ```sh cat /etc/os-release ``` -> enable vnc raspiconfig +Enable vnc raspiconfig ```sh sudo raspi-config nonint do_vnc 0 ``` -> setup my git +Check cpu arch +```sh +dpkg --print-architecture +``` + +# Git +Setup my git ```sh # setup user git config --global user.email ccbing@gmail.com @@ -87,38 +99,9 @@ git config --global --unset user.password git config --global alias.ci commit git config --global alias.st status ``` -> check cpu arch -```sh -dpkg --print-architecture -``` - - -## fonts -> place in `~/.fonts` or `/usr/local/share/fonts` for system wide access - mkdir ~/.fonts && cp ~/bitbot/src/resources/04B_03__.TTF ~/.fonts/04B_03__.TTF - -> manually rebuild the font cache with `fc-cache -f -v` - -> list fonts with `fc-list` - - - -# balena -``` -# install gbalena cli -wget https://github.com/balena-io/balena-cli/releases/download/v13.3.0/balena-cli-v13.3.0-linux-x64-standalone.zip -unzip balena-cli-v13.3.0-linux-x64-standalone.zip -# balena cli -balena login -balena push gh_donbing/teste -# git -# add pud key to balena -balena key add Main ~/.ssh/id_rsa.pub -# list keys -balena keys -# push to balena -git remote add balena gh_donbing@git.balena-cloud.com:gh_donbing/teste.git -# push our main branch to balena master branch -git push balena main:master -``` +# Fonts +- place in `~/.fonts` or `/usr/local/share/fonts` for system wide access +- `mkdir ~/.fonts && cp ~/bitbot/src/resources/04B_03__.TTF ~/.fonts/04B_03__.TTF` +- manually rebuild the font cache with `fc-cache -f -v` +- list fonts with `fc-list` diff --git a/readme.md b/readme.md index 35557f95..d8b795c8 100644 --- a/readme.md +++ b/readme.md @@ -36,4 +36,4 @@ - [πŸ’Ύ **Config** Options](docs/config_options.md) - [πŸ“’ Dev **Notes**](docs/development.md) - [πŸ‹ **Docker** Setup](docs/docker_installation.md) - - [πŸ“ˆ Current charting examples](/tests/images/) + - [πŸ“ˆ Chart examples](/tests/images/) From 26f8c7570bfde2f89d9fd91a1026a3c8098c95cb Mon Sep 17 00:00:00 2001 From: Chris Bingham Date: Tue, 17 Dec 2024 00:34:31 +0000 Subject: [PATCH 02/13] cleanup --- src/drawing/legacy_mpf_plotted_chart.py | 97 ------------------ .../test_264x176_APPLE_1mo_defaults.fail.png | Bin 395 -> 0 bytes .../test_264x176_APPLE_3mo_defaults.fail.png | Bin 426 -> 0 bytes ...76_AUDCAD_3mo_defaults_with_entry.fail.png | Bin 2155 -> 0 bytes ...st_264x176_bitmex_BTC_1d_defaults.fail.png | Bin 933 -> 0 bytes .../test_264x176_bitmex_BTC_1h_100K.fail.png | Bin 532 -> 0 bytes ...st_264x176_bitmex_BTC_1h_defaults.fail.png | Bin 261 -> 0 bytes ...st_264x176_bitmex_BTC_5m_defaults.fail.png | Bin 391 -> 0 bytes ...st_264x176_bitmex_ETH_1d_defaults.fail.png | Bin 394 -> 0 bytes ...st_264x176_bitmex_ETH_1h_defaults.fail.png | Bin 438 -> 0 bytes ...st_264x176_bitmex_ETH_5m_defaults.fail.png | Bin 403 -> 0 bytes tests/images/title_block2.png | Bin 0 -> 484 bytes 12 files changed, 97 deletions(-) delete mode 100644 src/drawing/legacy_mpf_plotted_chart.py delete mode 100644 tests/images/test_264x176_APPLE_1mo_defaults.fail.png delete mode 100644 tests/images/test_264x176_APPLE_3mo_defaults.fail.png delete mode 100644 tests/images/test_264x176_AUDCAD_3mo_defaults_with_entry.fail.png delete mode 100644 tests/images/test_264x176_bitmex_BTC_1d_defaults.fail.png delete mode 100644 tests/images/test_264x176_bitmex_BTC_1h_100K.fail.png delete mode 100644 tests/images/test_264x176_bitmex_BTC_1h_defaults.fail.png delete mode 100644 tests/images/test_264x176_bitmex_BTC_5m_defaults.fail.png delete mode 100644 tests/images/test_264x176_bitmex_ETH_1d_defaults.fail.png delete mode 100644 tests/images/test_264x176_bitmex_ETH_1h_defaults.fail.png delete mode 100644 tests/images/test_264x176_bitmex_ETH_5m_defaults.fail.png create mode 100644 tests/images/title_block2.png diff --git a/src/drawing/legacy_mpf_plotted_chart.py b/src/drawing/legacy_mpf_plotted_chart.py deleted file mode 100644 index abaecbc0..00000000 --- a/src/drawing/legacy_mpf_plotted_chart.py +++ /dev/null @@ -1,97 +0,0 @@ -import matplotlib -import tzlocal -import matplotlib.pyplot as plt -import matplotlib.dates as mdates -from mplfinance.original_flavor import candlestick_ohlc, volume_overlay -from src.drawing import price_humaniser - -matplotlib.use('Agg') -local_tz = tzlocal.get_localzone() - -price_formatter = matplotlib.ticker.FuncFormatter( - price_humaniser.format_scale_price -) - - -class PlottedChart: - layouts = { - '3mo': (20, mdates.YearLocator(), mdates.YearLocator(1), mdates.DateFormatter('%Y'), local_tz), - '1mo': (0.01, mdates.MonthLocator(), mdates.YearLocator(1), mdates.DateFormatter('%Y'), local_tz), - '1d': (0.01, mdates.DayLocator(bymonthday=range(1, 31, 7)), mdates.MonthLocator(), mdates.DateFormatter('%b'), local_tz), - '1h': (0.005, mdates.HourLocator(byhour=range(0, 23, 4)), mdates.DayLocator(), mdates.DateFormatter('%a %d %b', local_tz)), - "5m": (0.0005, mdates.MinuteLocator(byminute=[0, 30]), mdates.HourLocator(interval=1), mdates.DateFormatter('%-I.%p', local_tz)), - } - - def __init__(self, config, display, files, chart_data): - self.candle_width = chart_data.candle_width - # πŸ–¨οΈ create MPL plot - self.fig, ax = self.create_chart_figure(config, display, files) - # πŸ“ find suiteable layout for timeframe - layout = self.layouts[self.candle_width] - # βž– locate/format x axis ticks for chosen layout - ax[0].xaxis.set_minor_locator(layout[1]) - ax[0].xaxis.set_minor_formatter(plt.NullFormatter()) - ax[0].xaxis.set_major_locator(layout[2]) - ax[0].xaxis.set_major_formatter(layout[3]) - # πŸ’²currency amount uses custom formatting - ax[0].yaxis.set_major_formatter(price_formatter) - - self.plot_chart(config, layout, ax, chart_data.candle_data) - - def plot_chart(self, config, layout, ax, candle_data): - # βœ’οΈ draw candles to MPL plot - candlestick_ohlc(ax[0], candle_data, colorup='green', colordown='red', width=layout[0]) - # βœ’οΈ draw volumes to MPL plot - if config.show_volume(): - ax[1].yaxis.set_major_formatter(price_formatter) - _, opens, _, _, closes, volumes = list(zip(*candle_data)) - volume_overlay(ax[1], opens, closes, volumes, colorup='white', colordown='red', width=1) - self.fig.subplots_adjust(bottom=0.01) - - # πŸ“‘ styles overide each other left to right? - def get_default_styles(self, config, display, files): - small_display = self.is_small_display(display) - - if small_display: - yield files.small_screen_style - yield files.default_style - - if config.expand_chart(): - yield files.expanded_style - if small_display: - yield files.small_expanded_style - - def is_small_display(self, display): - small_display = display.size()[0] < 300 - return small_display - - def create_chart_figure(self, config, display, files): - # πŸ“ apply global base style - plt.style.use(files.base_style) - num_plots = 2 if config.show_volume() else 1 - heights = [4, 1] if config.show_volume() else [1] - plt.tight_layout() - # πŸ“ select mpl style - stlyes = list(self.get_default_styles(config, display, files)) - # πŸ“ scope styles to just this plot - with plt.style.context(stlyes): - display_width, display_height = display.size() - fig = plt.figure(figsize=(display_width / 100, display_height / 100)) - gs = fig.add_gridspec(num_plots, hspace=0, height_ratios=heights) - ax1 = fig.add_subplot(gs[0], zorder=1) - ax2 = None - - # πŸ“ align price tick labels for expanded chart - if(config.expand_chart()): - ax1.set_yticklabels(ax1.get_yticklabels(), ha='left') - - if config.show_volume(): - with plt.style.context(files.volume_style): - ax2 = fig.add_subplot(gs[1], zorder=0) - - return (fig, (ax1, ax2)) - - def write_to_stream(self, stream): - self.fig.savefig(stream, dpi=self.fig.dpi, pad_inches=0) - stream.seek(0) - plt.close(self.fig) diff --git a/tests/images/test_264x176_APPLE_1mo_defaults.fail.png b/tests/images/test_264x176_APPLE_1mo_defaults.fail.png deleted file mode 100644 index 110a6b49fcc75753297fc9e3f61dd17eb6b744ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 395 zcmeAS@N?(olHy`uVBq!ia0y~yVB`R@H*hck$(pKk7N9_or;B4q#hkZy9lKf_cvvqi znfW{3a<4#FmhRk0U+1TLLsxR-ITd`a2dYH@_a}XJ4Rv)%JNNwYhmU?~4%){hdJ<>?n|PD$}(+Yx%rfSMvXF*AJ|(>v~r0x~8Vtx3GWt zqZvEiv}bEG|`MY;au!7zytAPiuZee^Nfh15)Yf>gTe~DWM4fRZw|# diff --git a/tests/images/test_264x176_APPLE_3mo_defaults.fail.png b/tests/images/test_264x176_APPLE_3mo_defaults.fail.png deleted file mode 100644 index 97052bc060793d9dae28df3cf5cb0d0522bce4ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 426 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 Nd%F6$taD0e0sso~jUoU5 diff --git a/tests/images/test_264x176_bitmex_BTC_1h_100K.fail.png b/tests/images/test_264x176_bitmex_BTC_1h_100K.fail.png deleted file mode 100644 index 15fc525fcce99e9a47f11a772d5a016b5cd33112..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 532 zcmeAS@N?(olHy`uVBq!ia0y~yVB`R@H*hck$(pKk7NEdwPZ!6KiaBrZ8s;4~;9(8W zJn?t^8NNJG*GYFvGJ-qa^LDkblcYuEyNEZkkXj4*BYWw=Za(>HSUjLeS{!|HBVGluyAuRi`fa+IG5#DEUY>`ml3XO|Eq}(Qfv%g_BJ0<(%#1dwiAy3{UKH4vG-|@YV3&5j!NlEgy$lKdZoLP0ZVpU7y#BQFjq{mJ`7=F(gj7p=fS?83{ F1ORGl#nu1- diff --git a/tests/images/test_264x176_bitmex_BTC_1h_defaults.fail.png b/tests/images/test_264x176_bitmex_BTC_1h_defaults.fail.png deleted file mode 100644 index 66c3c8785d7472e397b1edc6c97a2c4f96fca5b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 261 zcmeAS@N?(olHy`uVBq!ia0y~yVB`R@H*hck$(pKk79e%T)5S5QV$R#^j$8*6cvu{m z|NftT*?D2d$}bTaX?|&YrNoW{RZ$A|O_(`z=bYyPvvy5i*tT2n-}4)>G3)J>vN+!A m^nQ#`xZw9UnNlklKD=TnyI^sC>Rk(SkXfFtelF{r5}E*iCNXdT diff --git a/tests/images/test_264x176_bitmex_BTC_5m_defaults.fail.png b/tests/images/test_264x176_bitmex_BTC_5m_defaults.fail.png deleted file mode 100644 index e4a85547703855f4be7d85443f0bf5ff8c7d08bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 391 zcmeAS@N?(olHy`uVBq!ia0y~yVB`R@H*hck$(pKk7NCHir;B4q#hka-4D${f@URA4 z{P1`E89tv#$Gj%LOojdC7V&e8CMdY4^%)v%b~>3qb@vXI8&@xCDjmD>gI#7d-ip&d(2q=;f6%bT`v&Yir%=gg9|qV^JTD-_?cUy4R<0cm1x&>Dl>m`s`CSq}T#dThnxA zO@6E1x6PkpDu3T^*=}X!dSu3|ee?FS-MDu?!6sU2=Fgn8gy{PH-7x`ojz`)TUylFo gtj~+jng;*xtU*mj=TBN2=KxaY>FVdQ&MBb@05ivS#{d8T diff --git a/tests/images/test_264x176_bitmex_ETH_1d_defaults.fail.png b/tests/images/test_264x176_bitmex_ETH_1d_defaults.fail.png deleted file mode 100644 index 140fa7737982f6aedbf46bb03ab9c44155971fcd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 394 zcmeAS@N?(olHy`uVBq!ia0y~yVB`R@H*hck$(pKk7N9_&r;B4q#hkZy9kUJ_2(SkD zpZvStCU(ZEV?pXHQq~^tb(!Qc7q;3Lh1K5%YC-{ap=oJp_BLz3RVNfJpZ;$~%b&vY zCm*;S?KO;P)vUdGdZwh{^6JN3#Ru0OJD4Gvu`)yHQCBe|iV-NB{jaPZCoxT4{ko^U zI-&09Uj_a(i&q)n+rQ@5tT4{Rs?fO~x8)wUN_IVlq6>vnusU&Np<#RIAJLv9(X>w$ zncF=!m@p<3`d97y-B~Y%q85cyuydbVPJPaPuVWEcK3NcIb|3{)i ze7e>Bxu49DG#xk)SiiOZb#lOzrT&HUgE!BASJ)cWXSdYv-l1Q8|GX{i*pU>YF&oS` z?oWFE(#Y<6P4Jb+i{#r_v}WvhZmo3Zv#+4=!4p1}3pxr7w(-2#&SA}lr1b#5)qR7q yrJr>rbn+M7y5-Vj^sH&&MK3|C`$&qAnBp~z3;ylDtGG%RtiaRN&t;ucLK6V`u#toS diff --git a/tests/images/test_264x176_bitmex_ETH_5m_defaults.fail.png b/tests/images/test_264x176_bitmex_ETH_5m_defaults.fail.png deleted file mode 100644 index 3bd4989c5838b9bb544fa3039708b81567b992d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 403 zcmeAS@N?(olHy`uVBq!ia0y~yVB`R@H*hck$(pKk7N9_sr;B4q#hkZy9r>CJ1Xu%( z75vxk?_RZXX@Y@a*6({w7Z!4h{b`u}eiKj&GWbwCbLPzI)9+Z8ZSZv}*OcSQx7`}8 zGUG^Rg3FQT@0jaOrCM86=svltp3jM_4a{yZe^a(%>ZcVQE25W_taFSGTbvi9=mSy@$RNuYPtp+zFx_jm>{Iyv~bbn zN*h*Xx}MC9VFY%8dS&C;skWNW`;#|Iwr%Y__-Ri2-qk>JA4IuK;c;=9 z*}*c|g6mk2>mtkcjdz3S3j3^P6%TOlj^K}D01VW0Cl`zb-EUN+3x{CrpWycJgWua;JQ zKY!`m{_4G%pO=;V`o8AV{wPc7`*GjbUEY0ODtvu){Qr-8EFZk>%W$d?E;fl=tFrxB zrttmU#u2Y0U-!Q&xc1|!b^Fva7gc)iJ(qoc*HKGIR;ceAYyP3H>(*bZPJ8iIlxyU&HKOS?Cbvif5Yzndupk-WncEbF7;gVd(UH?x4%@Z z8h%f;FW=w4UHPKo!-@WvPQCj#=TY>;7r;hO}EYiAhO?$XY}1s1D#{QaW)e8#l(|F`@sXhH}d{@HJ5-8Kj(e< literal 0 HcmV?d00001 From d0bf30bc17e6ea2b41c8101ee9033a8ac2940ec7 Mon Sep 17 00:00:00 2001 From: Chris Bingham Date: Tue, 17 Dec 2024 00:59:51 +0000 Subject: [PATCH 03/13] shufflee mpl styles into folder and update docs a bit --- config/{ => mpl_styles}/base.mplstyle | 0 .../default.expanded.mplstyle | 0 config/{ => mpl_styles}/default.mplstyle | 0 .../{ => mpl_styles}/small.expanded.mplstyle | 0 config/{ => mpl_styles}/small.mplstyle | 0 config/{ => mpl_styles}/volume.mplstyle | 0 docs/development.md | 43 ++++++++----------- docs/device_setup.md | 2 +- docs/docker_installation.md | 36 ---------------- readme.md | 1 - src/configuration/bitbot_files.py | 21 ++++++--- 11 files changed, 32 insertions(+), 71 deletions(-) rename config/{ => mpl_styles}/base.mplstyle (100%) rename config/{ => mpl_styles}/default.expanded.mplstyle (100%) rename config/{ => mpl_styles}/default.mplstyle (100%) rename config/{ => mpl_styles}/small.expanded.mplstyle (100%) rename config/{ => mpl_styles}/small.mplstyle (100%) rename config/{ => mpl_styles}/volume.mplstyle (100%) delete mode 100644 docs/docker_installation.md diff --git a/config/base.mplstyle b/config/mpl_styles/base.mplstyle similarity index 100% rename from config/base.mplstyle rename to config/mpl_styles/base.mplstyle diff --git a/config/default.expanded.mplstyle b/config/mpl_styles/default.expanded.mplstyle similarity index 100% rename from config/default.expanded.mplstyle rename to config/mpl_styles/default.expanded.mplstyle diff --git a/config/default.mplstyle b/config/mpl_styles/default.mplstyle similarity index 100% rename from config/default.mplstyle rename to config/mpl_styles/default.mplstyle diff --git a/config/small.expanded.mplstyle b/config/mpl_styles/small.expanded.mplstyle similarity index 100% rename from config/small.expanded.mplstyle rename to config/mpl_styles/small.expanded.mplstyle diff --git a/config/small.mplstyle b/config/mpl_styles/small.mplstyle similarity index 100% rename from config/small.mplstyle rename to config/mpl_styles/small.mplstyle diff --git a/config/volume.mplstyle b/config/mpl_styles/volume.mplstyle similarity index 100% rename from config/volume.mplstyle rename to config/mpl_styles/volume.mplstyle diff --git a/docs/development.md b/docs/development.md index cdd96343..91361146 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,25 +1,19 @@ # Development - > Bitbot is somewhat cobbled together, but is fairly carefully commented and has been factored with ease of change in mind. ## βœ”οΈ Tests -> [python unittests](/tests) with the default test framework - - python3 -m unittest discover tests -v +[python unit tests](/tests) with the default test framework +`python3 -m unittest discover tests -v` ## βœ‰οΈ Env vars -> `BITBOT_TESTRUN` loads one chart and then exits - -> `BITBOT_SHOWIMAGE` [opens the image in vscode](/run.py) after loading the chart - -> `BITBOT_OUTPUT` may be set to `disk` to write to disk rather than the e-ink display - - export BITBOT_TESTRUN=true BITBOT_OUTPUT=disk BITBOT_SHOWIMAGE=true +`BITBOT_TESTRUN` loads one chart and then exits +`BITBOT_SHOWIMAGE` [opens the image in vscode](/run.py) after loading the chart +`BITBOT_OUTPUT` may be set to `disk` to write to disk rather than the e-ink display +`export BITBOT_TESTRUN=true BITBOT_OUTPUT=disk BITBOT_SHOWIMAGE=true` ## πŸ“» Easy WiFi config -> [`comitup`](https://github.com/davesteele/comitup) is used for the ***disk image***, it creates a **config hotspot** on the Pi if it **cant connect** to any wifi itself. - -> The config file is located at `/etc/comitup.conf` +[`comitup`](https://github.com/davesteele/comitup) is used for the ***disk image***, it creates a **config hotspot** on the Pi if it **cant connect** to any wifi itself. +The config file is located at `/etc/comitup.conf` ```sh # show comitup info sudo comitup -i @@ -28,11 +22,9 @@ sudo comitup-cli ``` ## 🌳Logging -> BitBot will log to `syslog`, `StdOut` and a rolling `debug.log` file, configured in [πŸ“logging.ini](/logging.ini) - -> Log level is **defaulted to `INFO`**, but there is some ***limited debug level logging*** if you wish to get more info. - -> Cron jobs were configured to output to syslog. 😞 +BitBot will log to the `config web-ui` `syslog`, `StdOut` and a rolling `debug.log` file, configured in [πŸ“logging.ini](/logging.ini) +Log level is **defaulted to `INFO`**, but there is some ***limited debug level logging*** if you wish to get more info. +Cron jobs were configured to output to syslog. 😞 ```sh # roling file log tail ~/bitbot/debug.log @@ -41,23 +33,22 @@ tail -f /var/log/syslog | grep 'Bitbot:' ``` ## 🎁 Packages +UML diagram of broad [package interactions](http://www.plantuml.com/plantuml/svg/3Oon3KCX30NxFqMo0EvJ_LN0M7mhO11-LjOFrUckkDkHDsBqwwt6FQh4xgy7MFuXslcNckA94YwRfq4CYUUWEgseDIgACa4Zgvt6JcT5A_CtD_6qZbstM3ty0m00) + - [Pimoroni](pimoroni.com) [`inky`](https://github.com/pimoroni/inky) does the **e-ink display**, - [`CCXT`](https://github.com/ccxt/ccxt) talks to **crypto exchanges** - [`MPL-Finance`](https://github.com/matplotlib/mpl-finance) **draws the graphs** (and could do with updating to [`mplfinance`](https://github.com/matplotlib/mplfinance)) - [`Pillow`](https://github.com/python-pillow/Pillow) draws **drawing overlay** text onto the graph -![Package Interactions](http://www.plantuml.com/plantuml/svg/3Oon3KCX30NxFqMo0EvJ_LN0M7mhO11-LjOFrUckkDkHDsBqwwt6FQh4xgy7MFuXslcNckA94YwRfq4CYUUWEgseDIgACa4Zgvt6JcT5A_CtD_6qZbstM3ty0m00) - ## 🐳 Docker -> **Github actions** builds and tests and publishes a **container image** on each commit to `main` and `release` -### 🐳 Build -> building on `x86` is way faster than on the Pi +**Github actions** builds and tests and publishes a **container image** on each commit to `main` and `release` + +🐳 Build on `x86` is way faster than on the Pi. ```sh # remove the `--platform` args if building on a pi docker buildx build --platform linux/arm/v6 . -t bitbot -f scripts/docker/dockerfile --progress string ``` -### 🐳 Run -> **Priviledged access** is needed for `GPIO`, this looks to be fixable thru bind mounts +🐳 Run **Priviledged access** is needed for `GPIO`, this looks to be fixable thru bind mounts. ```sh docker run --privileged --platform linux/arm/v6 bitbot ``` \ No newline at end of file diff --git a/docs/device_setup.md b/docs/device_setup.md index f2107413..84a27654 100644 --- a/docs/device_setup.md +++ b/docs/device_setup.md @@ -24,7 +24,7 @@ - A list of **supported crypto-exchanges** can be found [here](https://github.com/ccxt/ccxt/wiki/Exchange-Markets) - Please see your selected exchange for the ***instruments that it supports*** -### Bitbot uses [Style Files](../config/base.mplstyle) to control the chart layout. +### Bitbot uses [Style Files](../config/mpl_styles/base.mplstyle) to control the chart layout. - If you're feeling experimental.. you can edit these! Examples of the ***styling*** options can be [found here](https://matplotlib.org/stable/tutorials/introductory/customizing.html#the-default-matplotlibrc-file) ### Inky Impression **Button** support diff --git a/docs/docker_installation.md b/docs/docker_installation.md deleted file mode 100644 index 761a5bef..00000000 --- a/docs/docker_installation.md +++ /dev/null @@ -1,36 +0,0 @@ -# πŸ‹ How to install Docker - -1. ### Update the host package manager -```sh -sudo apt update -y -``` -2. ### Install docker -```sh -curl -fsSL https://get.docker.com -o get-docker.sh -sudo sh get-docker.sh -``` -3. ### Setup user -```sh -sudo usermod -aG docker pi -``` -4. ### docker compose -```sh -sudo curl -fL 'https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-armv6' -o /usr/local/bin/docker-compose -sudo chmod +x /usr/local/bin/docker-compose -``` -5. ### Reboot -```sh -sudo shutdown -r now -``` -6. ### Run bitbot image - -- `main` - ```shell - docker run --restart unless-stopped --privileged ghcr.io/donbing/bitbot:main - docker run -it --privileged ghcr.io/donbing/bitbot:main - docker-compose -f scripts/docker/docker-compose.yml up - ``` -- `release` (stable) - ```shell - docker run --restart unless-stopped --privileged ghcr.io/donbing/bitbot:release - ``` \ No newline at end of file diff --git a/readme.md b/readme.md index d8b795c8..2a3b1fca 100644 --- a/readme.md +++ b/readme.md @@ -35,5 +35,4 @@ - [βš™οΈ Device **Setup**](docs/device_setup.md) - [πŸ’Ύ **Config** Options](docs/config_options.md) - [πŸ“’ Dev **Notes**](docs/development.md) - - [πŸ‹ **Docker** Setup](docs/docker_installation.md) - [πŸ“ˆ Chart examples](/tests/images/) diff --git a/src/configuration/bitbot_files.py b/src/configuration/bitbot_files.py index 5dd048ee..d87671f4 100644 --- a/src/configuration/bitbot_files.py +++ b/src/configuration/bitbot_files.py @@ -23,13 +23,20 @@ def __init__(self, base_path): self.config_ini = self.existing_file_path('config.ini') self.logging_ini = self.existing_file_path('logging.ini') - self.base_style = self.existing_file_path('base.mplstyle') - self.default_style = self.existing_file_path('default.mplstyle') - self.volume_style = self.existing_file_path('volume.mplstyle') - self.small_screen_style = self.existing_file_path('small.mplstyle') - self.expanded_style = self.existing_file_path('default.expanded.mplstyle') - self.small_expanded_style = self.existing_file_path('small.expanded.mplstyle') - + self.base_style = self.load_mpl_style('base.mplstyle') + self.default_style = self.load_mpl_style('default.mplstyle') + self.volume_style = self.load_mpl_style('volume.mplstyle') + self.small_screen_style = self.load_mpl_style('small.mplstyle') + self.expanded_style = self.load_mpl_style('default.expanded.mplstyle') + self.small_expanded_style = self.load_mpl_style('small.expanded.mplstyle') + + def load_mpl_style(self, file_name): + file_path = pjoin(self.config_folder, 'mpl_styles', file_name) + if not exists(file_path): + self.file_not_found(file_path) + self.all_files[file_name] = file_path + return file_path + def existing_file_path(self, file_name): file_path = pjoin(self.config_folder, file_name) if not exists(file_path): From 4faaa395bdc375d3b3217586555567177932fdec Mon Sep 17 00:00:00 2001 From: Chris Bingham Date: Tue, 17 Dec 2024 19:30:18 +0000 Subject: [PATCH 04/13] image for newer test --- ...test_264x176_MSFT_3mo_defaults_with_entry.png | Bin 0 -> 2025 bytes tests/test_chart_rendering.py | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 tests/images/test_264x176_MSFT_3mo_defaults_with_entry.png diff --git a/tests/images/test_264x176_MSFT_3mo_defaults_with_entry.png b/tests/images/test_264x176_MSFT_3mo_defaults_with_entry.png new file mode 100644 index 0000000000000000000000000000000000000000..47ee422531b7275ce42328ab7092c6d990ed3b5c GIT binary patch literal 2025 zcmeAS@N?(olHy`uVBq!ia0y~yVB`R@H*hcm$s@-Y5*Zkn{s#Djxc>kDA1DICqhK@y zMpy`hCLCJKz`(NH)5S5QV$Rz+y=Bb`JZ>lN{a;uf<;=k$GBy6^yPL1F1RqLm>@7Lg zAb8=Ux67x~ub=vd2K}=$Q~dsY`y{{IyhGbRyNlmxj-BvUcjEU;-_@Djs5GK3P>Hn5gk$1)oCotuLj4 zu}4-puxy^rQ8YbMa^}yadtD@K+jcK};r#btP+@88nt3;VMaZtKI<)rcf*dokh5B!; zyA{44^Nn9^|3+2G`SQU};qF1N*PS|<8_eZmP=98!)g&F&M<4O2Y_xa4NICeg+ zTj+7{xp}?1;K#;2hrJz-_XbWB6kBp!F0U(N=4P2AE`o*}(rE$*xjP=Px_n?#s$dk{ z)4&nuz@o3vBtE_PWI#If6fbT(=4}@7v_?!fbDWjH=QX_B-1~?L|Qf9H%=t zta$~${gf4aC;VgA%3YVwbj=pr_-D;7DdqB^+kzrgLa*xxwA|!1*xlU`tMgs#Vorv@HQ|dK9SPF}FV6CGE!eEUq$JUi zpwJ|#*<{n>{ed^m*Ic{vyZIlXG!=QnJ;!5R)|lHcZOg5OG}K1uW!{QN|xbp7@|prJ}ix1J}r+;lBrR%(0u@yyRWIhPzwrSfQTr5U|0 zM}F}O3L7+c?Ask0+Qz-Qlgnip~?2BRp;a-rwJY8_ZK#v_?=5T(_m`a)xD2}0u@8QH+i$% z^}5=se&|x;1ZDk4fB%XLDX~4j!!F=^HCKeg$wbNQ)&2#pZzMRJLXPyEnP>5e4U$N~ zNf(q_H?Qr;Tqe$!_9JCeWCI7Al1_Jx@DBU>nd#HbWFvN+I@rPy-8p^pok*AUf1O=^ zUlJ%>{c4d{nb<~FQy)f#h6T%JSIVCaYJDoV@RwI+lA7Ve1AjV@JN<8M?4+*k1lTbs6JV(855uJGU63u>A-yz*IndV;LywWim-;@_fv1-Dp+ z&z;lx^nA_dZ2MfL|4Y{|xte-I_T-y}nEQK_-S)dq*zOtDB5&#V_Mw_yl2=qL+sTa+ z_6Db1U$^0P*B|bqH7-Av`Yy^dw>As Date: Tue, 17 Dec 2024 19:33:57 +0000 Subject: [PATCH 05/13] fix path --- tests/test_stock_exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_stock_exchange.py b/tests/test_stock_exchange.py index 48c3378e..c996b71b 100644 --- a/tests/test_stock_exchange.py +++ b/tests/test_stock_exchange.py @@ -11,7 +11,7 @@ test_params = ['1mo', '1h', '1wk', 'random'] curdir = pathlib.Path(__file__).parent.resolve() -files = use_config_dir(os.path.join(curdir, "../")) +files = use_config_dir(os.path.join(curdir, "..")) config_ini = load_config_ini(files) # πŸͺ³ ''1h',' <- fails on weekends due to short chart duration From d46e53ebb37a5cb4b727d33e7fc2e4e103e774b2 Mon Sep 17 00:00:00 2001 From: Chris Bingham Date: Sun, 29 Dec 2024 07:12:27 +0000 Subject: [PATCH 06/13] start adding YT subs page --- run.py | 6 +++-- src/bitbot.py | 8 ++++++- src/configuration/bitbot_config.py | 3 +++ src/youtube_stats/subscriber_counter.py | 32 +++++++++++++++++++++++++ tests/test_youtube_subs.py | 23 ++++++++++++++++++ 5 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 src/youtube_stats/subscriber_counter.py create mode 100644 tests/test_youtube_subs.py diff --git a/run.py b/run.py index f2008ae6..3d63b225 100644 --- a/run.py +++ b/run.py @@ -36,11 +36,13 @@ def refresh_display(sc, reason): if config.photo_mode_enabled(): if reason != "scheduled": app.display_photo() - else: - app.display_chart() + elif app.display_chart(): # πŸͺ³ show image in vscode for debug if config.shoud_show_image_in_vscode(): os.system("code last_display.png") + elif app.youtube_subs_enabled(): + app.display_youtube_subs() + # βŒ› dont reschedule if testing if not config.is_test_run(): diff --git a/src/bitbot.py b/src/bitbot.py index 23e5c158..4265f00e 100644 --- a/src/bitbot.py +++ b/src/bitbot.py @@ -1,4 +1,4 @@ -from PIL import Image, ImageDraw, ImageFont +from PIL import Image, ImageDraw from os.path import exists import io from src.exchanges import crypto_exchanges, stock_exchanges @@ -7,6 +7,7 @@ from src.drawing.chart_overlay import ChartOverlay from src.display.picker import picker as display_picker from src.configuration.network_utils import wait_for_internet_connection +from src.youtube_stats.subscriber_counter import YouTubeSubscriberCount class Cartographer(): @@ -68,5 +69,10 @@ def display_photo(self): if(exists(image_path)): self.display.show(Image.open(image_path)) + @info_log + def display_youtube_subs(self): + subscriber_display = YouTubeSubscriberCount(self.display.size(), self.display.title_font, self.config) + subscriber_display.play() + def __repr__(self): return f'' diff --git a/src/configuration/bitbot_config.py b/src/configuration/bitbot_config.py index bb1ca71e..22c74888 100644 --- a/src/configuration/bitbot_config.py +++ b/src/configuration/bitbot_config.py @@ -140,6 +140,9 @@ def toggle_photo_mode(self, enabled_state, cycle_state): def photo_mode_enabled(self): return self.config['picture_frame_mode']["enabled"] == 'true' + + def youtube_subs_enabled(self): + return self.config['youtube_subs']["enabled"] == 'true' def cycle_pictures_enabled(self): return self.config['picture_frame_mode']["cycle"] == 'true' diff --git a/src/youtube_stats/subscriber_counter.py b/src/youtube_stats/subscriber_counter.py new file mode 100644 index 00000000..3949be30 --- /dev/null +++ b/src/youtube_stats/subscriber_counter.py @@ -0,0 +1,32 @@ +from PIL import Image, ImageDraw +from ..drawing.image_utils.CenteredText import centered_text +from ..configuration.network_utils import wait_for_internet_connection +import json +import requests + +page1 = ''' +Hi, + I will count your youtube subscribers +''' +channel_id="UCAotflAHrgfuhK9Rw-C_-Ug" +your_key="AIzaSyCFJ6-vHN9KgOE3a6mjdqBcG-pYlwcRGj4" + +url = f"https://www.googleapis.com/youtube/v3/channels?part=statistics&id={channel_id}&key={your_key}" +transparent = (255, 0, 0, 0) + +class YouTubeSubscriberCount: + def __init__(self, size, font, config): + self.display_size = size + self.font = font + self.centre = tuple(dim / 2 for dim in self.display_size.size) + + def play(self): + http_response = requests.get(url) + response_json = json.loads(http_response.text) + img = Image.new("RGBA", self.display_size.size, transparent) + draw = ImageDraw.Draw(img) + centered_text(draw, "Subscribers" + response_json['items'][0]['statistics']['subscriberCount'], self.font, img.size, 'centre') + return img + + def no_op(self): + pass diff --git a/tests/test_youtube_subs.py b/tests/test_youtube_subs.py new file mode 100644 index 00000000..c857acfe --- /dev/null +++ b/tests/test_youtube_subs.py @@ -0,0 +1,23 @@ +import unittest +from src.display import title_font +from src.youtube_stats import subscriber_counter +from os.path import join as pjoin +import pathlib +from src.configuration.bitbot_files import use_config_dir +from src.configuration.bitbot_config import load_config_ini + +curdir = pathlib.Path(__file__).parent.resolve() +files = use_config_dir(pjoin(curdir, "../")) +config = load_config_ini(files) + +class DisplaySize(): + def __init__(self, size): + self.size = size + +class YouTubeSubsTests(unittest.TestCase): + display_size = DisplaySize((400, 300)) + + def test_showing_youtube_subscriber_count(self): + aubscriber_count_display = subscriber_counter.YouTubeSubscriberCount(self.display_size, title_font, config) + image = aubscriber_count_display.play() + image.save(f'tests/images/youtube_subs_count.png') From ad18ff396c9556335bbd4d288745e4d37e30af7e Mon Sep 17 00:00:00 2001 From: Chris Bingham Date: Sun, 29 Dec 2024 09:23:42 +0000 Subject: [PATCH 07/13] correct display size --- src/youtube_stats/subscriber_counter.py | 3 ++- tests/images/youtube_subs_count.png | Bin 0 -> 1298 bytes 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 tests/images/youtube_subs_count.png diff --git a/src/youtube_stats/subscriber_counter.py b/src/youtube_stats/subscriber_counter.py index 3949be30..8bac7e23 100644 --- a/src/youtube_stats/subscriber_counter.py +++ b/src/youtube_stats/subscriber_counter.py @@ -23,9 +23,10 @@ def __init__(self, size, font, config): def play(self): http_response = requests.get(url) response_json = json.loads(http_response.text) + text_to_draw = "Subscribers" + response_json['items'][0]['statistics']['subscriberCount'] img = Image.new("RGBA", self.display_size.size, transparent) draw = ImageDraw.Draw(img) - centered_text(draw, "Subscribers" + response_json['items'][0]['statistics']['subscriberCount'], self.font, img.size, 'centre') + centered_text(draw, text_to_draw, self.font, self.display_size, 'centre') return img def no_op(self): diff --git a/tests/images/youtube_subs_count.png b/tests/images/youtube_subs_count.png new file mode 100644 index 0000000000000000000000000000000000000000..54052d319ff0bfb3e40773a6d4169476e38614bf GIT binary patch literal 1298 zcmeAS@N?(olHy`uVBq!ia0y~yV4MKNIvi|3k+<8Q9%5i%x#{WR7*a9k?d`Kc%?1(< zfdw`H!`Eq>`zW7m{bm0A%@&DEyLQwt-<6kUSX2G`B`?DVb50J1&IX1F9HZ2T41zuX zzp(v~|CU>A^ZRe|?QfgTy}G~mRH6O;-1_pt|m zy*@8jZ!SHY4+_GUw@6`t6>)gCPK~@@3i@R zR@J9o<=p)ha%uIui*weRSHDmGYP0zB)yEIHKx*DMGE4v&#KO|xGD?kvAh2V2n3x@H Uo@})dSk^Iky85}Sb4q9e0IjoN@c;k- literal 0 HcmV?d00001 From 0d1ed89be91bdffce4be0861ab4344370f21cd7c Mon Sep 17 00:00:00 2001 From: Chris Bingham Date: Sun, 29 Dec 2024 09:52:38 +0000 Subject: [PATCH 08/13] config and dead key --- src/configuration/bitbot_config.py | 19 +++++++++++++------ src/youtube_stats/subscriber_counter.py | 15 ++++++--------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/configuration/bitbot_config.py b/src/configuration/bitbot_config.py index 22c74888..cb0c2bb6 100644 --- a/src/configuration/bitbot_config.py +++ b/src/configuration/bitbot_config.py @@ -134,15 +134,12 @@ def set_display(self, formData): self.save() # πŸ–ΌοΈ picture frame mode - def toggle_photo_mode(self, enabled_state, cycle_state): - self.config['picture_frame_mode']["enabled"] = enabled_state - self.config['picture_frame_mode']["cycle_pictures"] = cycle_state - def photo_mode_enabled(self): return self.config['picture_frame_mode']["enabled"] == 'true' - def youtube_subs_enabled(self): - return self.config['youtube_subs']["enabled"] == 'true' + def toggle_photo_mode(self, enabled_state, cycle_state): + self.config['picture_frame_mode']["enabled"] = enabled_state + self.config['picture_frame_mode']["cycle_pictures"] = cycle_state def cycle_pictures_enabled(self): return self.config['picture_frame_mode']["cycle"] == 'true' @@ -191,3 +188,13 @@ def intro_background(self): self.config_files.resource_folder, self.config['first_run']['intro_background_image'] ) + + # πŸ“Ί youtube setup + def youtube_subs_enabled(self): + return self.config['youtube_subs']["enabled"] == 'true' + + def youtube_channelid(): + return "UCAotflAHrgfuhK9Rw-C_-Ug" + + def youtube_key(): + return "AIzaSyCFJ6-vHN9KgOE3a6mjdqBcG-pYlwcRGj4" \ No newline at end of file diff --git a/src/youtube_stats/subscriber_counter.py b/src/youtube_stats/subscriber_counter.py index 8bac7e23..b7369cc5 100644 --- a/src/youtube_stats/subscriber_counter.py +++ b/src/youtube_stats/subscriber_counter.py @@ -4,26 +4,23 @@ import json import requests -page1 = ''' -Hi, - I will count your youtube subscribers -''' -channel_id="UCAotflAHrgfuhK9Rw-C_-Ug" -your_key="AIzaSyCFJ6-vHN9KgOE3a6mjdqBcG-pYlwcRGj4" - -url = f"https://www.googleapis.com/youtube/v3/channels?part=statistics&id={channel_id}&key={your_key}" transparent = (255, 0, 0, 0) class YouTubeSubscriberCount: def __init__(self, size, font, config): + self.config = config self.display_size = size self.font = font self.centre = tuple(dim / 2 for dim in self.display_size.size) def play(self): + channel_id=self.config.youtube_channelid() + your_key=self.config.youtube_key() + url = f"https://www.googleapis.com/youtube/v3/channels?part=statistics&id={channel_id}&key={your_key}" http_response = requests.get(url) response_json = json.loads(http_response.text) - text_to_draw = "Subscribers" + response_json['items'][0]['statistics']['subscriberCount'] + subscriber_count = response_json['items'][0]['statistics']['subscriberCount'] + text_to_draw = f"{subscriber_count} Subscribers" img = Image.new("RGBA", self.display_size.size, transparent) draw = ImageDraw.Draw(img) centered_text(draw, text_to_draw, self.font, self.display_size, 'centre') From 2f5138290e6e91a43c63415369d7c263db7fda6d Mon Sep 17 00:00:00 2001 From: Chris Bingham Date: Sun, 29 Dec 2024 11:28:53 +0000 Subject: [PATCH 09/13] disable broken youtube test --- tests/test_youtube_subs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_youtube_subs.py b/tests/test_youtube_subs.py index c857acfe..6def3563 100644 --- a/tests/test_youtube_subs.py +++ b/tests/test_youtube_subs.py @@ -14,6 +14,7 @@ class DisplaySize(): def __init__(self, size): self.size = size +@unittest.skip("needs youtube api key") class YouTubeSubsTests(unittest.TestCase): display_size = DisplaySize((400, 300)) From 619ffb09907e1e07565d0fbd3547a5782e59981c Mon Sep 17 00:00:00 2001 From: Chris Bingham Date: Sun, 29 Dec 2024 11:37:15 +0000 Subject: [PATCH 10/13] more consistent stock test --- ...t_264x176_MSFT_3mo_defaults_with_entry.png | Bin 2025 -> 2016 bytes .../images/test_264x176_TSLA_3mo_defaults.png | Bin 0 -> 1824 bytes tests/test_chart_rendering.py | 19 ++++++++++-------- 3 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 tests/images/test_264x176_TSLA_3mo_defaults.png diff --git a/tests/images/test_264x176_MSFT_3mo_defaults_with_entry.png b/tests/images/test_264x176_MSFT_3mo_defaults_with_entry.png index 47ee422531b7275ce42328ab7092c6d990ed3b5c..b912725aa8e6f0f35859a854aa9ae9ab116d03fb 100644 GIT binary patch delta 1188 zcmV;V1Y7&*58w~5Edzg^Nkl zCxXasR-NTiwzyHdE$zYefZ`Ex5)lajZ*wR9YFBuOaKS*bo?L%K7z#S71IV5>eO*)+ zu__S3Km@aw2y*u;a*5(i^#@DE)pftNU8y74J{iN^nEYWg5#H6wY%D_Gx|6mX@E5+H(s2@g3>X9 z8jlf1KIrs^C3Rd%7NqAPk&TEHts=tcAe1c>7K#;oi->;@q)+(IOH!}7M7&`0F(O6N zRNWvZiiknjFoOtQKm;!!f)^0M3y9zaMDPM4cmWZ-*j$94^SQeB8%1P`=w}+!@-ciI zWr5IbR^2#CL`d@Eg(9vD4Phqiogxa$`nk~;jS;u55oaKRfe2>(R2?FMfd~d77>HmX z;>knz8HinAAc9$6#N&@j50({CDG-EZM1-A&2>bi?BA$$q-pm|c!<+UJVf6HzMKe@k z|L=+DJiV|5=Rmj-4=#bdy9oD@##ZXKBAAGX_W2lLb`jlxh+#)ObN)0BBDNHf`&btun2kil`VSEkFoTG` zhgCCX%nXm=gG9_Xs}3R}cMW|HKZl6ne`BN`Bl@N4q}wA>E!Thu1|pcYsk)U!G_4&X z!fWKFBBZyf+x@eU*QhHZbF(x<1}FzaFg4p3J0q5%iHK-44+Ik3lPCTgk_O$0{7=H^V_9e2bQk#W5?ZCRKH+bXa4iE#54-U#%{LjB4QVy+9(p+Ar+= zP~Rj>R;^~VkyV*%idXx$aD0cb>P)N4kON89g6oYJ_FDQ=ggIB8oQdVneDiBR?IM4r zucXyw*aOMzfL!0acZ`0EAa9qf2tRUy+QVI3#FVtU43hUkj!uz7yTYz3I`StJ^AX{f zHi^33?AS={ClTI>@T=8#sasnw8nhZB0001hjrj-T!Vfth8GLsD0000J~kou_qUQxZEnK1^G6rlgI$pAEs^)q_a}I2Ru^$}i4U#UtV*A`$}L%bobEUEv|Z1p~=?auI)FDCnpTAbZ;Mbx~c! zsz3y@mk4r~X&IUMrM*NLJ%+oX=tKR%QgL-%te)8VSRJW)!mZVyY&I65FYP;vAos>i zU#matB_c!9!SYu}QtPhD$!${#L@6l{ z0}%{FFkJ@AtBrq83CIb03Pdmv!9WDl{Xw_2-`7<&7eu+x=oM5hSSyn}`^f zj~E>hB=;A;I!v<+)dXZj*t}FH5yUrpj|SOBkv@EsD0n=5y89_2g6!T*aL+32i&)bxH2@jbjX`U6qfa~Brh5xZe1hJKm-F3 z%=)Q1Lxn-Q`6thzcPI?t+WLD>I$rv^W~NyM$Irpn|mo2siKA_GJ)tBI)nhEiB0Ro5&K zgxL)+yNifjEcX`CY-kYH6VV8WV3rZlnn^`)vmle61q^>n@C{x~H!F$vH~bQ2Bp)%n z$6Z9MHodUjsp0+&?e-$fN;%8s5wS|n5|>YAa}oA*mE>Vh5%c6Mb+5OM2#8=JBHHI; zgxN)O10sg)@yz*d9z<*@B6nLCBAAUt#QF~r6EK5_zPnXZhs+Eg!v~3&Z+am_MBX*@ z-TfRQhL3-ddW`6os*`SyNVQx8A{dBZ+NSDO64A7FhzPHdn~IR$s&4nsLSCb;h|HU% z88SdQAcCpczStSD3{6B#I}l`eE`pX01fiyg6$3%2D`H_b6mc}STB^<;8I}^k4^I;_ zk@pHKX)u*$)xF~qQ6W|Lf=dM5O4WrY!qS0|BQJmA{rwjbA_nK&2M|rwZ4?MXG$JY_?^7O@32OaTCZpr)I7AI(yQ zN@+M^CQYj9ROztBOk2EJa=%(#2pQGD<9mTbdbMBJdr;pbOjfOCw2@VrYl>I}*4B##t%e8y003ZP{sGR{4+=?@ RCJ+Ds002ovPDHLkV1jQFDhdDq diff --git a/tests/images/test_264x176_TSLA_3mo_defaults.png b/tests/images/test_264x176_TSLA_3mo_defaults.png new file mode 100644 index 0000000000000000000000000000000000000000..f40b2d83e48a500d352d1fef29fc3acff51afdc4 GIT binary patch literal 1824 zcmeAS@N?(olHy`uVBq!ia0y~yVB`R@H*hcm$s@-Y5*Zkn{s#Djxc>kDA1DICqhK@y zMpy`hCLCJKz`%UZ)5S5QV$R#y-9^m`0 zdc()B?XhK5MBwE`*7q&AfVSFaIxo`;=xV`0!9_so*31 zOt-EZy$b@uW_hg5_3jBcnX7k;ZI1GyfAf`;xLp~#7~~7=oF?As)F~~>$-Es^Hs_pn zlM(|^;zoeAq1}U&%6%^dLAFIiwMcAxx^ma;`?t5PO?_UmyhAt%Bwq1+`gReykYMUvUILA}eM{n#p{e}Y{M5C@;=6!0zjDRTXV&-DZ@?ML&A=Qp41 z`XG7!scV62#!lns9Fad|CN={tZ#Z}J!@4KzdvA0t;BXLZ06Pb221r1`uV_W=PX5>qRi!7YHl%wm%CUL=iZOb`grA2x3cj+!D z$=DJTuuqLMXIlL~=kSGJv-7xjs5os{*pYv`{Ek?`+Y2W?8woyN$RVG!?Kaa6WUD>8 zb~Mg(y~E|&Q0X$qKikX^)nzkWk4&ANI1A?DhN~_vGuM5{_7W6l_MP{83DDgS+ysl$ z4_C)VYJO0uPN-j=tY__(Q)1H z$J&Ge6r3>s08J-TQgYnSu&8Pgg&ebxsLQ0ODxM_W%F@ literal 0 HcmV?d00001 diff --git a/tests/test_chart_rendering.py b/tests/test_chart_rendering.py index 4865c08c..a920b27c 100644 --- a/tests/test_chart_rendering.py +++ b/tests/test_chart_rendering.py @@ -59,8 +59,11 @@ class disk_output_renderers: 'currency': {'stock_symbol': 'AAPL'}, 'display': {'candle_width': '1mo'}, }, - "APPLE_3mo_defaults": { - 'currency': {'stock_symbol': 'TSLA'}, + "TSLA_3mo_defaults": { + 'currency': { + 'chart_since': '2016-04-22T00:00:00Z', + 'stock_symbol': 'TSLA' + }, 'display': {'candle_width': '3mo'}, }, "MSFT_3mo_defaults_with_entry": { @@ -217,11 +220,11 @@ class LargeChartRenderingTests(unittest.TestCase, output=disk_output_renderers.d __metaclass__ = TestRenderingMeta -# @unittest.skip("needs a waveshare display") -# class Wave27bChartRenderingTests(unittest.TestCase, output=screen_output_renderers.wave27b, metaclass=TestRenderingMeta): -# __metaclass__ = TestRenderingMeta +@unittest.skip("needs a waveshare display") +class Wave27bChartRenderingTests(unittest.TestCase, output=screen_output_renderers.wave27b, metaclass=TestRenderingMeta): + __metaclass__ = TestRenderingMeta -# @unittest.skip("needs an inky display") -# class InkyChartRenderingTests(unittest.TestCase, output=screen_output_renderers.inky, metaclass=TestRenderingMeta): -# __metaclass__ = TestRenderingMeta +@unittest.skip("needs an inky display") +class InkyChartRenderingTests(unittest.TestCase, output=screen_output_renderers.inky, metaclass=TestRenderingMeta): + __metaclass__ = TestRenderingMeta From acc96cf1850ef06ca40db7c5324384e5220f45a9 Mon Sep 17 00:00:00 2001 From: Chris Bingham Date: Sun, 29 Dec 2024 11:44:24 +0000 Subject: [PATCH 11/13] correct test graphs --- ...test_400x300_MSFT_3mo_defaults_with_entry.png | Bin 0 -> 2753 bytes tests/images/test_400x300_TSLA_3mo_defaults.png | Bin 0 -> 2604 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/images/test_400x300_MSFT_3mo_defaults_with_entry.png create mode 100644 tests/images/test_400x300_TSLA_3mo_defaults.png diff --git a/tests/images/test_400x300_MSFT_3mo_defaults_with_entry.png b/tests/images/test_400x300_MSFT_3mo_defaults_with_entry.png new file mode 100644 index 0000000000000000000000000000000000000000..8a449144895533b55e6e4ac0c12ff5bd9b813ec6 GIT binary patch literal 2753 zcmeHJi#OD1A0LFD!TODH8Cvo)6b(5;(V$^|xs2nI+f<0;vKysw4@t={J5!c%i7q4! zNyu(d5-MiL?I3owLW&vn%3WqCf~v=wzeihzSuwQ9|r!<7@+w#u~8`L zGY&Qs&$#@lg5+HGc2II;bN3Wpm$(u2kT2aICYwc9TWvfp+% z$N9vJZQ}^qo*py=D8OVgNCdT|V2(8d2Ver1LT0Y+FZ({quc|~IXycca|8Eyr+Mw&} zlUH{s%CSk{9cm50LQxC=Mgj`ZvO#U>F|E0qZ)m%C=dv|*t1<+5Z8*UPIAGMSaO&)8 z*EF`72KMewE!QfJkUK!3@^E+0vAD!TlF*NVwe8L1Cg;bYXI^JT_F9e+tEHhUb?t#i z6=36-OD)eek)Ze6Gm7>AZNJep_vWzOGPHC&4#dm3rQ7Y%J~^;#32NJwu}B%d{X4he zgF@XbH8>rDO__t_Fbki6{#ZkyIF$5>u4+1^fGjA^8%-=28Qh%O4#U55pc65Iy5)*< z_jS1?eFS}#(bD8M+l+?OKqC{Fti!S0>Uo}nz2NIudMP?XAoKpCV%RhxOiv}?R;6Av zQv9f@x@i8!G_TsK_*TX2_UYag^0;s)hvptog;5+d`#m zTNl5m?EUe~xd((yJO|oj8e$N1%PrzCf)S~sL`^b*TMupswN9`c!uX4N>!3_I3Tk#x zs=hY@y4wyCk5H(RU-ghf^uh<}nktA=#JfLSskkhBVO{wDzTf&GlIEv%f+?-0f9}K> zIr-v*1hjc!GgbvTepYmcj2V?X>|3?gL!8}A0Mu-(Ao3!OXW9uB7lKx+4`r_jrQs$5 z?@g}$G2@YD?SXU_(-7*ad2-NLe$%*gU0NxdHQt}hI{iszF3NDOy2i7U$hS9x$Obo4 z>N3neC$nbXe+NaqEWiI+H=-+5)Dxy8$N(=7QK%<(auG==6mDEWG#ntd1WpFlakZ$r zm*`BL7T1QQvJP=xk)|}nVW-fUaCgvnN3H>&rs|m6E$gDMv|Ahr7+qknCr2kp|&rSpB>koyn|s=Rl$# zo^PmS^F<3*lGC$<4>aDB;+8XgOA_Z1qf?6=qj^d%z+Tz4Q%Ten&IR2tuT#0Kf{VHe z{wniVrR#Lin!(Mdty_P+tnhZOTHrXBK}xk8*L;YNl#ZFnk1yHEiT%_(ojv?cwSLc( zz;QHR{!97(al1Ize@X$Bu_k~hW;AR%6S21yho+m`P*o>`2lqhJ@p)JRPN#Uu{L`$N z6MdaPSJiov6ws#0#Tc6$Vk~nWd5{1M`x=<89=w3b(tFBP5j`|4uwVS^DG_W|Lk7wJ z;$tM-kLKV3r7)k3US0UG7oHVW2X~f9Mz=-N>=C|qKzhq&~($LiB_y0-jZ%uxt`x=26rK8Bf9(g;}z&}H#*8i)sk_#smmw69UI%2a)yhIMSV>J5+WK; z5nMdvywi>PI5gUO_8uHs-V^ey16@{WS1M^lD1M+`pi*DK8u-hg{%xrf>e?0 zpUpPyj_S6fs2$0hqM~KJ8tg4(<{TgM+E)`Z3JL=((7HqM{On(P&Fnjw+Fq}UMtEjP zJ=U^j09UNDQ(xVP9MacDE^B!Z{ZVcBdtukj0(kUQUhvN>LCzR^i_q^X&pmg!XFpxZ zA|ZXRS7rW0w)f<6zi>RJv+KfItkZ$VqtD0dqBF3Lg|=0ar{?SEdsStDntO&{W)+p-_WUkec@+JphwJ7Tfq| boTY!T^2TfM70vBpzMu}aE;e^z|8xHaB=%a> literal 0 HcmV?d00001 diff --git a/tests/images/test_400x300_TSLA_3mo_defaults.png b/tests/images/test_400x300_TSLA_3mo_defaults.png new file mode 100644 index 0000000000000000000000000000000000000000..7af2716919baac8e13be17a9e6223dba8cc94d88 GIT binary patch literal 2604 zcmeHIc~FyA5D!Ozq#}_fpoqwaiH4()Nu{6=k7vLS3714;ETmW?mZ5-90gVD8`4B3_ z2%#XTlt@6W#sdL4BFLe5ATP7-C){g6iS1yW*a>%wWd7?{}P4Loc5+OTvRHRS~JW4)Bgzk#}L>OP;&-_ z(iw20t!L~zIeaQUEyo;(5~)-VPkrgSUT{b#zB6*)e`SMKx9j~U_LpkSExvY@dsV(^ zKd`pQdG*K@eE5NU=TPUV&I+9Bo{wJYE==DwQB!{hR2T95;zjwnmyXgY(^x~KdfW2x zBEGyq9xU;?K`3x%x0es85=h#vH?We@R^LpsNw!BCDXc=dk4oW6m#9>q|#LtpY3FeR0 zIzPBB;(v-LRnH>jD*H8+i~FdE-wHy?@){+%n0^3LMsGO#Y?w%3!Y?thU)!S&*B0@5 z1)p8csQkiWbn!09&TDKJptji?GQDz7l&`^PKRzOzU*FNS|KSn?AGo!phm;>Y7><(` zzi}i(knkXVdd&X#v1=1Ani|@A7((n1$qNut@1EeVaS_*x?J%o4+l{mMSqb&UZ_EQ!f`w@RRW1A`%T6B=sTyclAL!GAdE|iu-mc2S(A{k~R22m(+Bjg*col?h zoGtVWfiS)I0Dh`g!mt}Ko?&%bhvIAh}Y zRgzk+Vl`|tcZZr!T61j7b|+VHJ*&ZO!SU)(* zgVf9ZkYk)?kF=XDxu?N;9+qQ`+;0xFc~yD`0W}qeP7r4>epMWnk2nbgbeh*0#t}!Z z1DjXOr+0Iv9KEMXS`FhYc~!zf^&=zTG_^>*rS3`%q>~zdz~}E7dpB+GpEQ2!5A}`p zgwL;oN^5=jLhQmSNIRw#{wKS)lbf;>3sh%9?lz$pq3o}0B5B`ZV)nOKWJrBr?%fKl z=&XlD;$#6EfHa=6wne0}iuGi6g)y!0n~gS0eQ_s;ObFW*bbI$hOwe-+$ulz#U!<$4XeawlJ3#B!2vQgdEwCnH9M8NQjw( zjpDXb?Ojhx`u4!u5D>p=I2*0gRZ#umS$ zfC3+F-Nb#vpa))gl$8}FHGUyD{%z*~scqo07u>$gP`YrJ2OJTI_xTBAfA+{TEulA~ z>?=4a_lX@LhrUY?Jz|u6tfobeRnZqsC^&T*7#YLHkc{FZk@hZW=kfOTP@vK1^P?RM zO6Y6O7D1VU+>O5K!1qXBlqd^5%?oiBaVYlS*0TQjwW)QcMBK!LYnw+dc{i=BnpSVa iQ4b|OCFvb0DRCOx6{hwv`*)Yle)MiGo-{ECIQ%E$03+f6 literal 0 HcmV?d00001 From c07f4fb105f04d81494e74dbcdf5fc0639ec2aac Mon Sep 17 00:00:00 2001 From: Chris Bingham Date: Sun, 29 Dec 2024 12:10:13 +0000 Subject: [PATCH 12/13] better failures --- tests/test_chart_rendering.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_chart_rendering.py b/tests/test_chart_rendering.py index a920b27c..93377a2c 100644 --- a/tests/test_chart_rendering.py +++ b/tests/test_chart_rendering.py @@ -195,7 +195,7 @@ def test(self): # os.system("code '" + file_name + "'") # if changes[1] is not None: # os.system("code '" + changes[1] + "'") - assert False, f"Image diff check: '{changes[1]}'" + assert False, f"Image diff check: '{changes}'" return test From 6d29c886befa644fc9c952bdc3c96a21da430688 Mon Sep 17 00:00:00 2001 From: Chris Bingham Date: Sun, 29 Dec 2024 12:24:44 +0000 Subject: [PATCH 13/13] updated large tests --- ...test_640x448_MSFT_3mo_defaults_with_entry.png | Bin 0 -> 3659 bytes tests/images/test_640x448_TSLA_3mo_defaults.png | Bin 0 -> 3331 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/images/test_640x448_MSFT_3mo_defaults_with_entry.png create mode 100644 tests/images/test_640x448_TSLA_3mo_defaults.png diff --git a/tests/images/test_640x448_MSFT_3mo_defaults_with_entry.png b/tests/images/test_640x448_MSFT_3mo_defaults_with_entry.png new file mode 100644 index 0000000000000000000000000000000000000000..cd5522570e812ad1a7c5ba2b5c7a2bff522977f5 GIT binary patch literal 3659 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 literal 0 HcmV?d00001