├── .gitignore ├── dockerfile_mt5 ├── root │ └── defaults │ │ ├── autostart │ │ └── menu.xml ├── Dockerfile └── Metatrader │ └── start.sh ├── dockerfile_reverseproxy ├── Dockerfile └── nginx.conf ├── dockerfile_superset └── Dockerfile ├── .env ├── dockerfile_jupyter_notebook ├── requirements.txt └── Dockerfile ├── dockerfile_airflow ├── requirements.txt ├── Dockerfile └── plot.py ├── starter_script.sh ├── ibgateway ├── components │ ├── services │ │ ├── socat │ │ │ └── run │ │ ├── ibc │ │ │ └── run │ │ ├── xvfb │ │ │ └── run │ │ └── vnc │ │ │ └── run │ ├── ibgateway │ │ └── jts.ini │ └── ibc │ │ ├── ibc.ini │ │ └── config.ini └── Dockerfile ├── README.md ├── docker-compose.yaml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .env 3 | .env 4 | -------------------------------------------------------------------------------- /dockerfile_mt5/root/defaults/autostart: -------------------------------------------------------------------------------- 1 | /Metatrader/start.sh -------------------------------------------------------------------------------- /dockerfile_reverseproxy/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | COPY nginx.conf /etc/nginx/nginx.conf 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /dockerfile_superset/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM apache/superset:4.1.1-py311 2 | USER root 3 | RUN apt-get update && apt-get install -y \ 4 | bash vim \ 5 | g++ make wget git curl gcc gnupg2 6 | #USER superset 7 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | WD=/home/mariano/docker 2 | ND=/home/mariano/docker/notebooks 3 | TWSUSERID=user 4 | TWSPASSWORD=password 5 | TRADING_MODE=paper 6 | TZ=America/Bogota 7 | AIRFLOW_UID=1000 8 | AIRFLOW_GID=0 9 | CUSTOM_USER=username 10 | PASSWORD= 11 | MAPBOX_API_KEY= 12 | -------------------------------------------------------------------------------- /dockerfile_jupyter_notebook/requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | pandas 3 | psycopg2-binary 4 | scikit-learn 5 | backtrader2 6 | ib_insync 7 | bs4 8 | peewee 9 | adjustText 10 | xlrd 11 | ffn 12 | pyfolio 13 | quantstats 14 | riskfolio_lib==4.4.2 15 | black_box 16 | statsmodels 17 | yfinance 18 | scipy 19 | requests 20 | pandas_market_calendars 21 | pandas_datareader 22 | graphviz 23 | pandas_profiling 24 | tqdm 25 | ib_insync 26 | pytz 27 | tabulate 28 | joblib 29 | ccxt 30 | ibapi 31 | PyYAML 32 | xverse 33 | chart-studio 34 | -------------------------------------------------------------------------------- /dockerfile_mt5/root/defaults/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | /usr/bin/xterm 5 | /usr/bin/wine "/config/.wine/drive_c/Program Files/MetaTrader 5/terminal64.exe" 6 | 7 | 8 | -------------------------------------------------------------------------------- /dockerfile_airflow/requirements.txt: -------------------------------------------------------------------------------- 1 | pandas==2.2.3 2 | numpy==2.1.3 3 | matplotlib 4 | plotly==5.24.1 5 | seaborn 6 | riskfolio-lib==4.4.2 7 | cvxpy[ecos] 8 | vectorbt 9 | backtrader2 10 | pandas_market_calendars 11 | pandas_datareader 12 | yfinance 13 | metastock2pd 14 | TA_Lib 15 | humanize 16 | websocket 17 | tables 18 | kaleido 19 | git+https://github.com/ultra1971/mt5linux.git 20 | psycopg2-binary 21 | scikit-learn 22 | lightgbm 23 | xgboost 24 | ib_insync 25 | bs4 26 | peewee 27 | adjustText 28 | xlrd 29 | ffn 30 | quantstats 31 | black_box 32 | statsmodels 33 | shap 34 | scipy 35 | graphviz 36 | pandas_profiling 37 | tqdm 38 | pytz 39 | tabulate 40 | joblib 41 | pyyamL 42 | lxml 43 | cssselect 44 | xverse 45 | pykalman 46 | dataframe_image 47 | chart-studio 48 | metastock2pd 49 | mplfinance 50 | rich 51 | -------------------------------------------------------------------------------- /starter_script.sh: -------------------------------------------------------------------------------- 1 | echo "create folders needed" 2 | mkdir ./airflow 3 | mkdir ./airflow/dags 4 | mkdir ./airflow/logs 5 | mkdir ./airflow/plugins 6 | mkdir ./mlflow 7 | mkdir ./notebooks 8 | mkdir ./pgadmin 9 | mkdir ./postgress_db 10 | mkdir ./superset 11 | 12 | echo "build algotrading image" 13 | docker-compose build 14 | 15 | echo "run algotrading image" 16 | docker-compose up -d 17 | 18 | echo "scripts on superset" 19 | docker exec -it superset superset fab create-admin \ 20 | --username admin \ 21 | --firstname Superset \ 22 | --lastname Admin \ 23 | --email admin@superset.com \ 24 | --password admin 25 | docker exec -it superset superset db upgrade 26 | docker exec -it superset superset init 27 | docker exec -it superset superset load_examples 28 | 29 | -------------------------------------------------------------------------------- /ibgateway/components/services/socat/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | shopt -s expand_aliases 4 | 5 | declare -r service_name='socat' 6 | 7 | source /etc/profile 8 | 9 | alias debug="while read data; do echo -e "${data}" | log debug; done" 10 | alias info="while read data; do echo -e "${data}" | log info; done" 11 | alias warn="while read data; do echo -e "${data}" | log warning; done" 12 | alias err="while read data; do echo -e "${data}" | log err; done" 13 | 14 | function log(){ 15 | # ATENCIÓN: NUNCA USAR DIRECTAMENTE, en su lugar utilizar los alias 'debug', 'info', 'warn' y 'err' 16 | local _severity=${1}; shift; 17 | logger -p syslog.${_severity} -t ${service_name} -- "${data}" 18 | } 19 | 20 | echo "Starting Interactive Brokers Controller" | info 21 | exec socat TCP-LISTEN:4001,fork TCP:127.0.0.1:4002 2>&1 | info 22 | -------------------------------------------------------------------------------- /ibgateway/components/services/ibc/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | shopt -s expand_aliases 4 | 5 | declare -r service_name='ibcontroller' 6 | 7 | source /etc/profile 8 | 9 | alias debug="while read data; do echo -e "${data}" | log debug; done" 10 | alias info="while read data; do echo -e "${data}" | log info; done" 11 | alias warn="while read data; do echo -e "${data}" | log warning; done" 12 | alias err="while read data; do echo -e "${data}" | log err; done" 13 | 14 | function log(){ 15 | # ATENCIÓN: NUNCA USAR DIRECTAMENTE, en su lugar utilizar los alias 'debug', 'info', 'warn' y 'err' 16 | local _severity=${1}; shift; 17 | logger -p syslog.${_severity} -t ${service_name} -- "${data}" 18 | } 19 | 20 | echo "Starting Interactive Brokers Controller" | info 21 | exec /opt/IBC/scripts/displaybannerandlaunch.sh 2>&1 | info 22 | -------------------------------------------------------------------------------- /ibgateway/components/ibgateway/jts.ini: -------------------------------------------------------------------------------- 1 | [IBGateway] 2 | WriteDebug=false 3 | TrustedIPs=127.0.0.1 4 | MainWindow.Height=550 5 | RemoteHostOrderRouting=hdc1.ibllc.com 6 | RemotePortOrderRouting=4000 7 | LocalServerPort=4000 8 | ApiOnly=true 9 | MainWindow.Width=700 10 | 11 | [Logon] 12 | useRemoteSettings=false 13 | UserNameToDirectory=eedkjkhbocomaodbogcfjlfpfmgonliddbpihlmh,godbekfnldoaidmjmlfijhfenlbgmpmnjodpbjpj 14 | TimeZone=America/Argentina/Buenos_Aires 15 | tradingMode=p 16 | colorPalletName=dark 17 | Steps=7 18 | Locale=en 19 | SupportsSSL=gdc1.ibllc.com:4000,true,20200709,false;cdc1.ibllc.com:4000,true,20200716,false 20 | UseSSL=true 21 | os_titlebar=false 22 | s3store=true 23 | ibkrBranding=pro 24 | 25 | [ns] 26 | darykq=1 27 | 28 | [Communication] 29 | SettingsDir=/root/Jts 30 | Peer=cdc1.ibllc.com:4001 31 | Region=hk 32 | 33 | -------------------------------------------------------------------------------- /ibgateway/components/services/xvfb/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | shopt -s expand_aliases 4 | 5 | declare -r service_name='xvfb' 6 | 7 | source /etc/profile 8 | 9 | alias debug="while read data; do echo -e "${data}" | log debug; done" 10 | alias info="while read data; do echo -e "${data}" | log info; done" 11 | alias warn="while read data; do echo -e "${data}" | log warning; done" 12 | alias err="while read data; do echo -e "${data}" | log err; done" 13 | 14 | function log(){ 15 | # ATENCIÓN: NUNCA USAR DIRECTAMENTE, en su lugar utilizar los alias 'debug', 'info', 'warn' y 'err' 16 | local _severity=${1}; shift; 17 | logger -p syslog.${_severity} -t ${service_name} -- "${data}" 18 | } 19 | 20 | echo "Starting Xvfb - Virtual Framebuffer 'fake' X server" | info 21 | echo -e "$(env)" | debug 22 | exec /usr/bin/Xvfb $DISPLAY -ac -screen 0 1980x1020x24 +extension RANDR 2>&1 | info 23 | -------------------------------------------------------------------------------- /dockerfile_airflow/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM apache/airflow:2.10.5-python3.11 2 | 3 | USER root 4 | RUN apt-get update && apt-get install -y \ 5 | python3 unzip \ 6 | python3-dev \ 7 | libhdf5-serial-dev python3-tables python-tables-doc \ 8 | bash vim font-manager xfonts-utils fontconfig\ 9 | g++ make wget git curl gcc gnupg2 \ 10 | && rm -rf /var/lib/apt/lists/* 11 | 12 | RUN curl -L https://github.com/ta-lib/ta-lib/releases/download/v0.6.4/ta-lib_0.6.4_amd64.deb -o ta-lib_0.6.4_amd64.deb 13 | RUN dpkg -i ta-lib_0.6.4_amd64.deb 14 | 15 | #RUN pip3 install numpy \ 16 | # && pip3 install TA-Lib 17 | 18 | ENV NUMBA_CACHE_DIR=/tmp 19 | 20 | USER airflow 21 | 22 | COPY requirements.txt /tmp/ 23 | RUN pip3 install --requirement /tmp/requirements.txt 24 | 25 | COPY plot.py /home/airflow/.local/lib/python3.9/site-packages/backtrader/plot/plot.py 26 | 27 | ENV PYTHONPATH "${PYTHONPATH}:/usr/local/airflow/dags" 28 | ENV NUMBA_CACHE_DIR=/tmp 29 | -------------------------------------------------------------------------------- /ibgateway/components/services/vnc/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | shopt -s expand_aliases 4 | 5 | declare -r service_name='vnc' 6 | 7 | source /etc/profile 8 | 9 | alias debug="while read data; do echo -e "${data}" | log debug; done" 10 | alias info="while read data; do echo -e "${data}" | log info; done" 11 | alias warn="while read data; do echo -e "${data}" | log warning; done" 12 | alias err="while read data; do echo -e "${data}" | log err; done" 13 | 14 | function log(){ 15 | # ATENCIÓN: NUNCA USAR DIRECTAMENTE, en su lugar utilizar los alias 'debug', 'info', 'warn' y 'err' 16 | local _severity=${1}; shift; 17 | logger -p syslog.${_severity} -t ${service_name} -- "${data}" 18 | } 19 | 20 | echo "Starting VNC server to allow remote access to an existing X session" | info 21 | mkdir /.vnc && x11vnc -storepasswd "${VNC_PASSWORD}" /.vnc/passwd 2>&1 | info 22 | exec /usr/bin/x11vnc -forever -geometry 1980x1020 -rfbport ${VNC_PORT} -rfbauth /.vnc/passwd -display ${DISPLAY} 2>&1 | info 23 | -------------------------------------------------------------------------------- /dockerfile_mt5/Dockerfile: -------------------------------------------------------------------------------- 1 | #FROM ghcr.io/linuxserver/baseimage-kasmvnc:amd64-debianbullseye-8446af38-ls104 2 | FROM ghcr.io/linuxserver/baseimage-kasmvnc:amd64-debianbookworm 3 | 4 | # set version label 5 | ARG BUILD_DATE 6 | ARG VERSION 7 | LABEL build_version="Metatrader Docker:- ${VERSION} Build-date:- ${BUILD_DATE}" 8 | LABEL maintainer="gmartin" 9 | 10 | ENV TITLE=Metatrader5 11 | ENV WINEPREFIX="/config/.wine" 12 | 13 | # Update package lists and upgrade packages 14 | RUN apt-get update && apt-get upgrade -y 15 | 16 | # Install required packages 17 | RUN apt-get install -y \ 18 | python3-pip \ 19 | wget vim curl git pipx 20 | 21 | #RUN pip3 install --upgrade pip 22 | 23 | # Add WineHQ repository key and APT source 24 | # && add-apt-repository 'deb https://dl.winehq.org/wine-builds/debian/ bullseye main' \ 25 | RUN wget -q https://dl.winehq.org/wine-builds/winehq.key \ 26 | && apt-key add winehq.key \ 27 | && add-apt-repository 'deb https://dl.winehq.org/wine-builds/debian/ bookworm main' \ 28 | && rm winehq.key 29 | 30 | # Add i386 architecture and update package lists 31 | RUN dpkg --add-architecture i386 \ 32 | && apt-get update 33 | 34 | # Install WineHQ stable package and dependencies 35 | RUN apt-get install --install-recommends -y \ 36 | winehq-stable winetricks \ 37 | && apt-get clean \ 38 | && rm -rf /var/lib/apt/lists/* 39 | 40 | COPY /Metatrader /Metatrader 41 | RUN chmod +x /Metatrader/start.sh 42 | COPY /root / 43 | 44 | #EXPOSE 3000 8001 45 | #VOLUME /config 46 | -------------------------------------------------------------------------------- /dockerfile_reverseproxy/nginx.conf: -------------------------------------------------------------------------------- 1 | events { } 2 | 3 | http { 4 | server { 5 | listen 80; 6 | server_name algotrading; 7 | 8 | location /airflow/ { 9 | proxy_pass http://172.19.0.12:8080; 10 | } 11 | 12 | location /superset/ { 13 | proxy_set_header X-Real-IP $remote_addr; 14 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 15 | proxy_set_header X-NginX-Proxy true; 16 | proxy_pass http://172.19.0.23:8088; 17 | proxy_ssl_session_reuse off; 18 | proxy_set_header Host $http_host; 19 | proxy_cache_bypass $http_upgrade; 20 | proxy_redirect off; 21 | 22 | } 23 | 24 | location /mlflow { 25 | rewrite ^/projecta(.*) /$1 break; 26 | proxy_pass http://127.0.0.1:5500; 27 | } 28 | location /jupyter { 29 | rewrite ^/projecta(.*) /$1 break; 30 | proxy_pass http://127.0.0.1:8888; 31 | } 32 | location /trading { 33 | alias /etc/nginx/html/CEREBRO/OUTPUT/; 34 | index RECOMENDATIONS.html; 35 | } 36 | location /health { 37 | access_log off; 38 | return 200 "healthy\n"; 39 | } 40 | } 41 | 42 | } 43 | 44 | 45 | -------------------------------------------------------------------------------- /ibgateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM phusion/baseimage:noble-1.0.1 2 | 3 | USER root 4 | SHELL ["/bin/bash", "-c"] 5 | 6 | ENV DEBIAN_FRONTEND=noninteractive \ 7 | DISABLE_SYSLOG=0 \ 8 | DISABLE_SSH=1 \ 9 | DISABLE_CRON=1 \ 10 | DISPLAY=:0 \ 11 | TZ=America/Bogota 12 | 13 | CMD ["/sbin/my_init"] 14 | 15 | RUN install_clean \ 16 | wget \ 17 | unzip \ 18 | socat \ 19 | xvfb \ 20 | libxrender1 \ 21 | libxtst6 \ 22 | libxi6 \ 23 | x11vnc \ 24 | && echo "source /etc/profile" >> /root/.bashrc 25 | 26 | ENV VNC_PASSWORD=123456 \ 27 | VNC_PORT=5900 28 | COPY components/services/xvfb /etc/service/00_xvfb 29 | COPY components/services/vnc /etc/service/01_vnc 30 | COPY components/services/ibc /etc/service/02_ibc 31 | COPY components/services/socat /etc/service/03_socat 32 | 33 | RUN chmod a+x /etc/service/*/* 34 | 35 | ###### Instalacion del IBGateway ####### 36 | ENV IBGATEWAY_PKG_URL="https://download2.interactivebrokers.com/installers/ibgateway/stable-standalone/ibgateway-stable-standalone-linux-x64.sh" 37 | ADD ${IBGATEWAY_PKG_URL} /tmp/ibgateway.sh 38 | RUN chmod a+x /tmp/ibgateway.sh \ 39 | && echo -e "\nn" | /tmp/ibgateway.sh -c \ 40 | && rm -f /tmp/ibgateway.sh \ 41 | && stat /usr/local/i4j_jres/**/**/bin | grep File | awk '{print "export JAVA_PATH="$2}' > /etc/profile.d/java.sh 42 | COPY components/ibgateway/* /root/Jts/ 43 | 44 | ##### Instalacion del IBController ##### 45 | ENV IBC_PKG_URL="https://github.com/IbcAlpha/IBC/releases/download/3.21.2/IBCLinux-3.21.2.zip" \ 46 | IBC_INI=/root/IBC/config.ini \ 47 | IBC_PATH=/opt/IBC \ 48 | TWS_MAJOR_VRSN=1030 \ 49 | TWS_PATH=/root/Jts \ 50 | TWS_CONFIG_PATH=/root/Jts \ 51 | LOG_PATH=/root/IBC/Logs \ 52 | TRADING_MODE=paper \ 53 | FIXUSERID='' \ 54 | FIXPASSWORD='' \ 55 | TWSUSERID="" \ 56 | TWSPASSWORD="" \ 57 | APP=GATEWAY 58 | 59 | ADD ${IBC_PKG_URL} /tmp/ibc.zip 60 | RUN mkdir -p /{root,opt}/IBC/Logs \ 61 | && unzip /tmp/ibc.zip -d /opt/IBC/ \ 62 | && cd /opt/IBC/ \ 63 | && chmod o+x *.sh */*.sh \ 64 | && sed -i 's/ >> \"\${log_file}\" 2>\&1/ 2>\&1/g' scripts/displaybannerandlaunch.sh \ 65 | && sed -i 's/light_red=.*/light_red=""/g' scripts/displaybannerandlaunch.sh \ 66 | && sed -i 's/light_green=.*/light_green=""/g' scripts/displaybannerandlaunch.sh \ 67 | && rm -f /tmp/ibc.zip 68 | COPY components/ibc/* /root/IBC/ 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /dockerfile_jupyter_notebook/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jupyter/datascience-notebook:x86_64-python-3.11.6 2 | 3 | USER root 4 | 5 | RUN apt-get update && apt-get install -y \ 6 | python3 \ 7 | python3-dev \ 8 | bash vim \ 9 | g++ make wget git curl gcc gnupg2 \ 10 | && rm -rf /var/lib/apt/lists/ 11 | 12 | #RUN pip install numpy 13 | # TA-Lib 14 | #RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz && \ 15 | # tar -xvzf ta-lib-0.4.0-src.tar.gz && \ 16 | # cd ta-lib/ && \ 17 | # ./configure --prefix=/usr && \ 18 | # make && \ 19 | # make install 20 | #RUN rm -R ta-lib ta-lib-0.4.0-src.tar.gz 21 | 22 | RUN curl -L https://github.com/ta-lib/ta-lib/releases/download/v0.6.4/ta-lib_0.6.4_amd64.deb 23 | RUN dpkg -i ta-lib_0.6.4_amd64.deb 24 | 25 | RUN pip3 install numpy \ 26 | && pip3 install TA-Lib 27 | 28 | COPY requirements.txt /tmp/ 29 | RUN pip install --requirement /tmp/requirements.txt && \ 30 | fix-permissions $CONDA_DIR && \ 31 | fix-permissions /home/$NB_USER && \ 32 | apt update && \ 33 | apt-get install curl -y 34 | 35 | ENV PYTHONPATH "${PYTHONPATH}:/home/jovyan/work" 36 | 37 | RUN python -m pip install --upgrade --no-deps --force-reinstall notebook 38 | 39 | RUN python -m pip install jupyterthemes 40 | RUN python -m pip install --upgrade jupyterthemes 41 | RUN python -m pip install jupyter_contrib_nbextensions 42 | # RUN jupyter contrib nbextension install --user 43 | # enable the Nbextensions 44 | #RUN jupyter nbextension enable contrib_nbextensions_help_item/main 45 | #RUN jupyter nbextension enable autosavetime/main 46 | #RUN jupyter nbextension enable codefolding/main 47 | #RUN jupyter nbextension enable code_font_size/code_font_size 48 | #RUN jupyter nbextension enable code_prettify/code_prettify 49 | #RUN jupyter nbextension enable collapsible_headings/main 50 | #RUN jupyter nbextension enable comment-uncomment/main 51 | #RUN jupyter nbextension enable equation-numbering/main 52 | #RUN jupyter nbextension enable execute_time/ExecuteTime 53 | #RUN jupyter nbextension enable gist_it/main 54 | #RUN jupyter nbextension enable hide_input/main 55 | #RUN jupyter nbextension enable spellchecker/main 56 | #RUN jupyter nbextension enable toc2/main 57 | #RUN jupyter nbextension enable toggle_all_line_numbers/main 58 | 59 | CMD ["jupyter", "notebook", "--no-browser","--NotebookApp.token=''","--NotebookApp.password=''", "--allow-root"] 60 | 61 | RUN sudo groupadd -g 1020 airflow 62 | RUN sudo useradd -s /bin/false -u 1010 -g 1020 airflow 63 | 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Algotrading Stack 2 | 3 | 4 | ## Comenzando 🚀 5 | 6 | Uno de las actividades recurrentes es armar un maquina que disponga de todos los servicios necesarios para empezar a desarrollar o poner en produccion estrategias de algotrading, el objetivo de este proyecto es proveer toda la infraestructura necesaria sin necesidad de instalar software o componentes locales sino a traves de un entorno contenerizado y virtualizado con la tecnologia provista por docker. 7 | 8 | ### Pre-requisitos 📋 9 | 10 | Solo tienes que tenes instalado docker en una maquina (linux, obvio) 11 | 12 | Aqui encontraras una guia para instalar docker en Ubuntu 20.04 13 | https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04-es 14 | 15 | Si insistes con Windows, quizas esta guia te pueda ayudar 16 | https://www.simplilearn.com/tutorials/docker-tutorial/install-docker-on-windows 17 | 18 | Luego toda la instalación esta armada con un stack completo e integrado de docker, que utiliza partir de utilizar comando docker-compose arma todo el stack de microservicios 19 | 20 | ### Instalación 🔧 21 | 22 | La instalación es muy sencilla, veamos... 23 | 24 | 1) Copiar todo el contenido en una carpeta, ejemplo 'algotrading' 25 | 26 | 2) Edita las variables del archivo .env, ya que se uilizan en el archivo docker-compose.yaml para configurar los servicios de los contenedores 27 | 28 | WD="directorio de trabajo, donde reside esta misma carpeta, en el ejemplo seria la ruta completa de 'algotrading'" 29 | 30 | ND="directorio donde se alojaron los notebooks y demas archivos python que seran accesibles desde los contenedores" 31 | 32 | TWSUSERID="usuario del gateway de Interactive Brokers" 33 | 34 | TWSPASSWORD="password del gateway de Interactive Brokers" 35 | 36 | TRADING_MODE="modalidad del gateway de Interactive Brokers paper o live" 37 | 38 | TZ="timezone del gateway de Interactive Brokers" 39 | 40 | CUSTOM_USER="usuario de Metatrader Web" 41 | 42 | PASSWORD="password de Metatrader Web, si se deja en blanco entonces no se pedira autenticacion" 43 | 44 | 3) Debes crear la network algotrading_stack_default en docker con la subnet correspondiente para asignar las IPs correspondientes a cada servicio 45 | 46 | 4) Ejecutar el script 47 | ./starter_script.sh 48 | 49 | ### Gestión de los contenedores 🔩 50 | 51 | Para ver el status y administrar los contenedores puedes usar el comando docker-compose o tambien la interfaz web provista por Portainer: 52 | 53 | * Portainer: http://localhost:9000 54 | 55 | La primera vez que ingreses te va a solicitar crear una password para la cuenta admin, y luego tendras acceso a la gestión y administracion de los contenedores, acceder a las consolas de los mismos, monitorear recursos y status de los servicios. 56 | 57 | ### Servicios incluidos ⌨️ 58 | 59 | Los diferentes servicios del stack se pueden acceder a traves: 60 | 61 | * Airflow: http://localhost:8080 62 | * Superset: http://localhost:8088 63 | * Celery Flower: http://localhost:5555 64 | * PgAdmin: http://localhost:1234 65 | * Metatrader http://localhost:3000 66 | mt5linux via host localhost port 8001 67 | * IBGateway via vnc localhost:5999 68 | 69 | ## Paquetes y librerias 📦 70 | 71 | En los archivos requirements.txt encontraras las librerias que se han incluido, he incluido entre otras: 72 | 73 | * sklearn 74 | * xgboost 75 | * ib_insync 76 | * backtrader2 (fork de backtrader con bugfixes) 77 | * vectorbt 78 | * ffn 79 | * pyfolio 80 | * quantstats 81 | * riskfolio-lib 82 | * mt5linux 83 | 84 | ## Agradecimientos 📢 85 | 86 | * A @paduel por la inspiración y el empujoncito en armar este stack 🤓 87 | * Basado en las ideas y en el trabajo de @saeed349 88 | https://github.com/saeed349/Microservices-Based-Algorithmic-Trading-System 89 | * Para Metatrader he integrado la imagen desarrollada por @gmag11 90 | https://github.com/gmag11/MetaTrader5-Docker-Image 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /dockerfile_mt5/Metatrader/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Configuration variables 4 | mt5file='/config/.wine/drive_c/Program Files/MetaTrader 5/terminal64.exe' 5 | WINEPREFIX='/config/.wine' 6 | wine_executable="wine" 7 | metatrader_version="5.0.4874" 8 | mt5server_port="8001" 9 | mono_url="https://dl.winehq.org/wine/wine-mono/10.0.0/wine-mono-10.0.0-x86.msi" 10 | git_url="https://github.com/git-for-windows/git/releases/download/v2.49.0.windows.1/Git-2.49.0-64-bit.exe" 11 | python_url="https://www.python.org/ftp/python/3.11.9/python-3.11.9-amd64.exe" 12 | mt5setup_url="https://download.mql5.com/cdn/web/metaquotes.software.corp/mt5/mt5setup.exe" 13 | 14 | export WINEDEBUG=-all 15 | # Function to display a graphical message 16 | show_message() { 17 | echo $1 18 | } 19 | 20 | # Function to check if a dependency is installed 21 | check_dependency() { 22 | if ! command -v $1 &> /dev/null; then 23 | echo "$1 is not installed. Please install it to continue." 24 | exit 1 25 | fi 26 | } 27 | 28 | # Function to check if a Python package is installed 29 | is_python_package_installed() { 30 | python3 -c "import pkg_resources; exit(not pkg_resources.require('$1'))" 2>/dev/null 31 | return $? 32 | } 33 | 34 | # Function to check if a Python package is installed in Wine 35 | is_wine_python_package_installed() { 36 | $wine_executable python -c "import pkg_resources; exit(not pkg_resources.require('$1'))" 2>/dev/null 37 | return $? 38 | } 39 | 40 | # Check for necessary dependencies 41 | check_dependency "curl" 42 | check_dependency "$wine_executable" 43 | 44 | # Install Mono if not present 45 | if [ ! -e "/config/.wine/drive_c/windows/mono" ]; then 46 | show_message "[1/7] Downloading and installing Mono..." 47 | curl -o /config/.wine/drive_c/mono.msi $mono_url 48 | WINEDLLOVERRIDES=mscoree=d $wine_executable msiexec /i /config/.wine/drive_c/mono.msi /qn 49 | rm /config/.wine/drive_c/mono.msi 50 | show_message "[1/7] Mono installed." 51 | else 52 | show_message "[1/7] Mono is already installed." 53 | fi 54 | 55 | # Check if MetaTrader 5 is already installed 56 | if [ -e "$mt5file" ]; then 57 | show_message "[2/7] File $mt5file already exists." 58 | else 59 | show_message "[2/7] File $mt5file is not installed. Installing..." 60 | 61 | # Fix for ucrtbase.dll.crealf 62 | winetricks -q vcrun2015 63 | # Set Windows 10 mode in Wine and download and install MT5 64 | $wine_executable reg add "HKEY_CURRENT_USER\\Software\\Wine" /v Version /t REG_SZ /d "win10" /f 65 | show_message "[3/7] Downloading MT5 installer..." 66 | curl -o /config/.wine/drive_c/mt5setup.exe $mt5setup_url 67 | show_message "[3/7] Installing MetaTrader 5..." 68 | $wine_executable "/config/.wine/drive_c/mt5setup.exe" "/auto" & 69 | wait 70 | rm -f /config/.wine/drive_c/mt5setup.exe 71 | fi 72 | 73 | # Recheck if MetaTrader 5 is installed 74 | if [ -e "$mt5file" ]; then 75 | show_message "[4/7] File $mt5file is installed. Running MT5..." 76 | $wine_executable "$mt5file" & 77 | else 78 | show_message "[4/7] File $mt5file is not installed. MT5 cannot be run." 79 | fi 80 | 81 | 82 | # Install Python in Wine if not present 83 | #if ! $wine_executable python --version 2>/dev/null; then 84 | if ! $wine_executable python -c 'import sys; assert sys.version_info >= (3,11)' > /dev/null; then 85 | show_message "[5/7] Installing Python in Wine..." 86 | curl -L $python_url -o /tmp/python-installer.exe 87 | $wine_executable /tmp/python-installer.exe /quiet InstallAllUsers=1 PrependPath=1 88 | rm /tmp/python-installer.exe 89 | show_message "[5/7] Python installed in Wine." 90 | else 91 | show_message "[5/7] Python is already installed in Wine." 92 | fi 93 | 94 | # Upgrade pip and install required packages 95 | show_message "[6/7] Installing Python libraries" 96 | $wine_executable python -m pip install --upgrade --no-cache-dir pip 97 | # Install MetaTrader5 library in Windows if not installed 98 | show_message "[6/7] Installing MetaTrader5 library in Windows" 99 | if ! is_wine_python_package_installed "MetaTrader5==$metatrader_version"; then 100 | $wine_executable python -m pip install --no-cache-dir MetaTrader5==$metatrader_version 101 | fi 102 | 103 | # Install git in Windows if not installed 104 | show_message "[6/7] Checking and installing git in Windows if necessary" 105 | if ! $wine_executable git --version 2>&1 >/dev/null; then 106 | curl -L $git_url -o /tmp/git-installer.exe 107 | $wine_executable /tmp/git-installer.exe /verysilent 108 | fi 109 | 110 | # Install mt5linux library in Windows if not installed 111 | show_message "[6/7] Checking and installing mt5linux library in Windows if necessary" 112 | if ! is_wine_python_package_installed "mt5linux"; then 113 | $wine_executable python -m pip install --no-cache-dir git+https://github.com/ultra1971/mt5linux.git --ignore-requires-python 114 | fi 115 | 116 | # Install mt5linux library in Linux if not installed 117 | show_message "[6/7] Checking and installing mt5linux library in Linux if necessary" 118 | if ! is_python_package_installed "mt5linux"; then 119 | pip install git+https://github.com/ultra1971/mt5linux.git --ignore-requires-python --break-system-packages 120 | fi 121 | 122 | # Install pyxdg library in Linux if not installed 123 | show_message "[6/7] Checking and installing pyxdg library in Linux if necessary" 124 | if ! is_python_package_installed "pyxdg"; then 125 | pip install pyxdg --break-system-packages 126 | fi 127 | 128 | # Start the MT5 server on Linux 129 | show_message "[7/7] Starting the mt5linux server..." 130 | python3 -m mt5linux --host 0.0.0.0 -p $mt5server_port -w $wine_executable python.exe & 131 | 132 | # Give the server some time to start 133 | sleep 5 134 | 135 | # Check if the server is running 136 | if ss -tuln | grep ":$mt5server_port" > /dev/null; then 137 | show_message "[7/7] The mt5linux server is running on port $mt5server_port." 138 | else 139 | show_message "[7/7] Failed to start the mt5linux server on port $mt5server_port." 140 | fi 141 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | x-airflow-common: 4 | &airflow-common 5 | build: 6 | context: ./dockerfile_airflow 7 | environment: 8 | &airflow-common-env 9 | AIRFLOW__CORE__EXECUTOR: CeleryExecutor 10 | AIRFLOW__CORE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@pg-airflow/airflow 11 | AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@pg-airflow/airflow 12 | AIRFLOW__CELERY__BROKER_URL: redis://:@redis:6379/0 13 | AIRFLOW__CORE__FERNET_KEY: '' 14 | AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true' 15 | AIRFLOW__CORE__LOAD_EXAMPLES: 'false' 16 | TZ: ${TZ} 17 | volumes: 18 | - ${WD}/airflow/dags:/opt/airflow/dags 19 | - ${WD}/airflow/logs:/opt/airflow/logs 20 | - ${WD}/airflow/plugins:/opt/airflow/plugins 21 | - ${ND}:/home/airflow/notebooks 22 | - /home/algotrading:/home/airflow/algotrading 23 | user: "${AIRFLOW_UID:-50000}:${AIRFLOW_GID:-50000}" 24 | depends_on: 25 | redis: 26 | condition: service_healthy 27 | pg-airflow: 28 | condition: service_healthy 29 | 30 | services: 31 | 32 | portainer: 33 | container_name: "portainer" 34 | image: portainer/portainer-ce:latest 35 | command: -H unix:///var/run/docker.sock 36 | restart: always 37 | ports: 38 | - 9000:9000 39 | - 8000:8000 40 | environment: 41 | TZ: ${TZ} 42 | #healthcheck: 43 | # test: ["CMD", "curl", "--fail", "http://localhost:9000/api/status"] 44 | # interval: 60s 45 | # timeout: 5s 46 | # retries: 3 47 | volumes: 48 | - /var/run/docker.sock:/var/run/docker.sock 49 | - portainer-volume:/data 50 | networks: 51 | default: 52 | ipv4_address: 172.20.0.2 53 | 54 | redis: 55 | image: redis 56 | container_name: "redis" 57 | volumes: 58 | - redis-volume:/data 59 | ports: 60 | - 6379:6379 61 | environment: 62 | TZ: ${TZ} 63 | healthcheck: 64 | test: ["CMD", "redis-cli", "ping"] 65 | interval: 60s 66 | timeout: 30s 67 | retries: 10 68 | restart: always 69 | networks: 70 | default: 71 | ipv4_address: 172.20.0.3 72 | 73 | reverseproxy: 74 | container_name: "reverseproxy" 75 | build: 76 | context: ./dockerfile_reverseproxy 77 | ports: 78 | - 80:80 79 | healthcheck: 80 | test: ["CMD", "curl", "--silent", "--fail", "http://localhost:80/health"] 81 | interval: 60s 82 | timeout: 30s 83 | retries: 5 84 | environment: 85 | TZ: ${TZ} 86 | volumes: 87 | - ${ND}:/etc/nginx/html/ 88 | restart: always 89 | networks: 90 | default: 91 | ipv4_address: 172.20.0.4 92 | 93 | pg-airflow: 94 | image: postgres 95 | container_name: "pg-airflow" 96 | environment: 97 | POSTGRES_USER: airflow 98 | POSTGRES_PASSWORD: airflow 99 | POSTGRES_DB: airflow 100 | TZ: ${TZ} 101 | ports: 102 | - 5432:5432 103 | volumes: 104 | - pg-airflow-volume:/var/lib/postgresql/data 105 | healthcheck: 106 | test: ["CMD", "pg_isready", "-U", "airflow"] 107 | interval: 5s 108 | retries: 5 109 | restart: always 110 | networks: 111 | default: 112 | ipv4_address: 172.20.0.5 113 | 114 | pg-master: 115 | image: postgres:13 116 | restart: always 117 | container_name: "pg-master" 118 | ports: 119 | - 5431:5431 120 | environment: 121 | SHARED_PASSWORD: password 122 | POSTGRES_PASSWORD: posgres349 123 | TZ: ${TZ} 124 | volumes: 125 | - ${WD}/postgress_db/scripts/:/docker-entrypoint-initdb.d/ 126 | - pg-master-volume:/var/lib/postgresql/data 127 | healthcheck: 128 | test: ["CMD", "pg_isready", "-U", "postgres"] 129 | interval: 5s 130 | retries: 5 131 | restart: always 132 | networks: 133 | default: 134 | ipv4_address: 172.20.0.6 135 | 136 | pgadmin: 137 | image: dpage/pgadmin4 138 | container_name: "pg-admin" 139 | environment: 140 | PGADMIN_DEFAULT_EMAIL: "guest@guest.com" 141 | PGADMIN_DEFAULT_PASSWORD: "guest" 142 | TZ: ${TZ} 143 | volumes: 144 | - ${WD}/pgadmin/:/var/lib/pgadmin 145 | ports: 146 | - 1234:80 147 | depends_on: 148 | - reverseproxy 149 | depends_on: 150 | - pg-master 151 | networks: 152 | default: 153 | ipv4_address: 172.20.0.7 154 | 155 | airflow-scheduler: 156 | <<: *airflow-common 157 | container_name: "airflow-scheduler" 158 | command: scheduler 159 | restart: always 160 | networks: 161 | default: 162 | ipv4_address: 172.20.0.10 163 | 164 | airflow-worker: 165 | <<: *airflow-common 166 | container_name: "airflow-worker" 167 | command: celery worker 168 | restart: always 169 | networks: 170 | default: 171 | ipv4_address: 172.20.0.11 172 | 173 | airflow-webserver: 174 | <<: *airflow-common 175 | container_name: "airflow-webserver" 176 | command: webserver 177 | ports: 178 | - 8080:8080 179 | depends_on: 180 | - reverseproxy 181 | - redis 182 | - airflow-worker 183 | - airflow-scheduler 184 | - pg-airflow 185 | healthcheck: 186 | test: ["CMD", "curl", "--fail", "http://localhost:8080/health"] 187 | interval: 30s 188 | timeout: 30s 189 | retries: 5 190 | restart: always 191 | networks: 192 | default: 193 | ipv4_address: 172.20.0.12 194 | 195 | airflow-flower: 196 | <<: *airflow-common 197 | container_name: "airflow-flower" 198 | command: celery flower 199 | ports: 200 | - 5555:5555 201 | healthcheck: 202 | test: ["CMD", "curl", "--fail", "http://localhost:5555/"] 203 | interval: 30s 204 | timeout: 30s 205 | retries: 5 206 | restart: always 207 | networks: 208 | default: 209 | ipv4_address: 172.20.0.13 210 | 211 | airflow-init: 212 | <<: *airflow-common 213 | container_name: "airflow-init" 214 | command: version 215 | environment: 216 | <<: *airflow-common-env 217 | _AIRFLOW_DB_UPGRADE: 'true' 218 | _AIRFLOW_WWW_USER_CREATE: 'true' 219 | _AIRFLOW_WWW_USER_USERNAME: ${_AIRFLOW_WWW_USER_USERNAME:-airflow} 220 | _AIRFLOW_WWW_USER_PASSWORD: ${_AIRFLOW_WWW_USER_PASSWORD:-airflow} 221 | networks: 222 | default: 223 | ipv4_address: 172.20.0.19 224 | 225 | ib-gateway: 226 | container_name: "ib-gateway" 227 | build: 228 | context: ./ibgateway 229 | restart: always 230 | ports: 231 | - "7497:7497" 232 | - "4001:4001" 233 | - "5999:5999" 234 | environment: 235 | TZ: ${TZ} 236 | VNC_PASSWORD: ib 237 | VNC_PORT: 5999 238 | TWSUSERID: ${TWSUSERID} 239 | TWSPASSWORD: ${TWSPASSWORD} 240 | TRADING_MODE: ${TRADING_MODE} 241 | networks: 242 | default: 243 | ipv4_address: 172.20.0.20 244 | 245 | mt5: 246 | container_name: "mt5" 247 | build: 248 | context: ./dockerfile_mt5 249 | restart: always 250 | volumes: 251 | - ${WD}/config/:/config 252 | healthcheck: 253 | test: ["CMD", "curl", "--fail", "http://localhost:3000"] 254 | interval: 30s 255 | timeout: 30s 256 | retries: 5 257 | ports: 258 | - "3000:3000" 259 | - "8001:8001" 260 | environment: 261 | UID: 1000 262 | GID: 1000 263 | CUSTOM_USER: ${CUSTOM_USER} 264 | PASSWORD: ${PASSWORD} 265 | networks: 266 | default: 267 | ipv4_address: 172.20.0.21 268 | 269 | superset: 270 | container_name: "superset" 271 | build: 272 | context: ./dockerfile_superset 273 | restart: always 274 | depends_on: 275 | - redis 276 | environment: 277 | MAPBOX_API_KEY: ${MAPBOX_API_KEY} 278 | SUPERSET_HOME: /etc/superset 279 | SUPERSET_ENV: production 280 | SUPERSET_SECRET_KEY: TwGiSenUQ0oUGEQT+E1o6IZzWzNDClPLppLmamPR7DN7DXhqZHdYJuRA 281 | TZ: ${TZ} 282 | ports: 283 | - "8088:8088" 284 | depends_on: 285 | - reverseproxy 286 | volumes: 287 | - ${WD}/superset/:/etc/superset 288 | - ${WD}/superset/:/var/lib/superset 289 | - ${ND}:/home/airflow/notebooks 290 | depends_on: 291 | - pg-master 292 | networks: 293 | default: 294 | ipv4_address: 172.20.0.23 295 | 296 | volumes: 297 | redis-volume: 298 | external: false 299 | pg-master-volume: 300 | external: false 301 | pg-airflow-volume: 302 | external: false 303 | portainer-volume: 304 | external: false 305 | 306 | networks: 307 | default: 308 | external: 309 | name: algotrading_stack_default 310 | 311 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /ibgateway/components/ibc/ibc.ini: -------------------------------------------------------------------------------- 1 | # Note that in the comments in this file, TWS refers to either the Trader 2 | # Workstation or the Gateway for the IB API. 3 | # 4 | # When referred to below, the default value for a setting is the value 5 | # assumed if either the setting is included but no value is specified, or 6 | # the setting is not included at all. 7 | # 8 | # IBController may also be used to start the FIX CTCI Gateway. All settings 9 | # relating to this have names prefixed with FIX. 10 | # 11 | # The IB API Gateway and the FIX CTCI Gateway share the same code. Which 12 | # gateway actually runs is governed by an option on the initial gateway 13 | # login screen. The FIX setting described under IBController Startup 14 | # Settings below controls this. 15 | 16 | 17 | # 1. IBController Startup Settings 18 | # ---------------------------------- 19 | # 20 | # IBController logs information during operating that can be useful when 21 | # diagnosing problems. If set to 'yes', all logging output from 22 | # IBController is to the console and may be directed into a file using 23 | # the standard > or >> command line redirection operators. If set to 'no', 24 | # output from IBController that is logged after it has loaded TWS appears 25 | # in the TWS logfile: this helps to correlate IBController log entries with 26 | # TWS activity. The default is 'no'. 27 | 28 | LogToConsole=yes 29 | 30 | 31 | # IBController may be used to start the IB Gateway for the FIX CTCI. This 32 | # setting must be set to 'yes' if you want to run the FIX CTCI gateway. The 33 | # default is 'no'. 34 | 35 | FIX=no 36 | 37 | 38 | # 2. Authentication Settings 39 | # ---------------------------- 40 | # 41 | # TWS and the IB API gateway require a single username and password. 42 | # You may specify the username and password using the following settings: 43 | # 44 | # IbLoginId 45 | # IbPassword 46 | # 47 | # Alternatively, you can specify the username and password in the command 48 | # files used to start TWS or the Gateway, but this is not recommended for 49 | # security reasons. 50 | # 51 | # If you don't specify them, you will be prompted for them in the usual 52 | # login dialog when TWS starts (but whatever you have specified will be 53 | # included in the dialog automatically: for example you may specify the 54 | # username but not the password, and then you will be prompted for the 55 | # password via the login dialog). Note that if you specify either 56 | # the username or the password (or both) in the command file, then 57 | # IbLoginId and IbPassword settings defined in this file are ignored. 58 | # 59 | # 60 | # The FIX CTCI gateway requires one username and password for FIX order 61 | # routing, and optionally a separate username and password for market 62 | # data connections. You may specify the usernames and passwords using 63 | # the following settings: 64 | # 65 | # FIXLoginId 66 | # FIXPassword 67 | # IbLoginId (optional - for market data connections) 68 | # IbPassword (optional - for market data connections) 69 | # 70 | # Alternatively you can specify the FIX username and password in the 71 | # command file used to start the FIX CTCI Gateway, but this is not 72 | # recommended for security reasons. 73 | # 74 | # If you don't specify them, you will be prompted for them in the usual 75 | # login dialog when FIX CTCI gateway starts (but whatever you have 76 | # specified will be included in the dialog automatically: for example 77 | # you may specify the usernames but not the passwords, and then you will 78 | # be prompted for the passwords via the login dialog). Note that if you 79 | # specify either the FIX username or the FIX password (or both) on the 80 | # command line, then FIXLoginId and FIXPassword settings defined in this 81 | # file are ignored; he same applies to the market data username and 82 | # password. 83 | 84 | # 85 | # Passwords may be specified in an 'encrypted' form. This makes 86 | # it tricky (but by no means impossible) for someone looking at this 87 | # file to know what your password is. It is recommended that you don't 88 | # use this feature, because the protection it gives is minimal. Instead 89 | # you should make sure that this .ini file is protected by your 90 | # operating system from unauthorised access, and prefereably it 91 | # should be encrypted. 92 | # 93 | # Use these settings to indicate whether the password(s) are encrypted: 94 | # 95 | # PasswordEncrypted 96 | # FIXPasswordEncrypted 97 | # 98 | # If you want to use the encrypted form, set the relevant setting to 99 | # 'yes', otherwise set it to 'no'. If you omit this setting, 'yes' is 100 | # assumed. 101 | # 102 | # To get the encrypted form, edit the IBControllerEncrypt.bat file 103 | # and replace "aaaaa" with your password; then run 104 | # IBControllerEncrypt.bat to display the encrypted version of 105 | # your password. 106 | # 107 | 108 | # IB API Authentication Settings 109 | # ------------------------------ 110 | 111 | # Your TWS username: 112 | 113 | IbLoginId=edemo 114 | 115 | 116 | # Your TWS password (encrypted if appropriate): 117 | 118 | IbPassword=demouser 119 | PasswordEncrypted=no 120 | 121 | 122 | # FIX CTCI Authentication Settings 123 | # -------------------------------- 124 | 125 | # Your FIX CTCI username: 126 | 127 | FIXLoginId= 128 | 129 | 130 | # Your FIX CTCI password (encrypted if appropriate): 131 | 132 | FIXPassword= 133 | FIXPasswordEncrypted=yes 134 | 135 | 136 | # Trading Mode 137 | # ------------ 138 | # 139 | # TWS 955 introduced a new Trading Mode combo box on its login 140 | # dialog. This indicates whether the live account or the paper 141 | # trading account corresponding to the supplied credentials is 142 | # to be used. The allowed values are 'live' (the default) and 143 | # 'paper'. Note that if the supplied credentials are for a 144 | # paper trading account, the setting is ignored. For earlier 145 | # versions of TWS this setting has no effect. 146 | 147 | #TradingMode=live 148 | TradingMode=paper 149 | 150 | 151 | # 3. TWS Startup Settings 152 | # ------------------------- 153 | # 154 | # Path to the directory where TWS should store its settings. This is 155 | # normally the folder in which TWS is installed. However you may set 156 | # it to some other location if you wish (for example if you want to 157 | # run multiple instances of TWS with different settings). 158 | # 159 | # It is recommended for clarity that you use an absolute path. The 160 | # effect of using a relative path is undefined. 161 | # 162 | # Linux and OS X users should use the appropriate path syntax. 163 | # 164 | # Note that, for Windows users, you MUST use double separator 165 | # characters to separate the elements of the folder path: for 166 | # example, IbDir=C:\\IBLiveSettings is valid, but 167 | # IbDir=C:\IBLiveSettings is NOT valid and will give unexpected 168 | # results. Linux and OS X users need not use double separators, 169 | # but they are acceptable. 170 | # 171 | # The default is the current working directory when IBController is 172 | # started. 173 | 174 | IbDir=/root/Jts 175 | 176 | 177 | # If you wish to store your TWS settings on IB's servers rather 178 | # than locally on your computer, set this to 'yes' 179 | 180 | StoreSettingsOnServer=no 181 | 182 | 183 | # Set to 'yes' to minimise TWS when it starts: 184 | 185 | MinimizeMainWindow=no 186 | 187 | 188 | # When TWS logs on it checks to see whether the account is already 189 | # logged in at another computer. If so it displays a dialog: this 190 | # setting instructs TWS how to proceed. If set to 'primary', TWS 191 | # ends the existing session and continues with the new session. If 192 | # set to 'secondary', TWS exits so that the existing session is 193 | # unaffected. If set to 'manual', the user must handle the dialog. 194 | # Note that when set to 'primary', if another TWS session is 195 | # started and manually told to end the primary session, the primary 196 | # session is automatically reconnected (provided the other session 197 | # is in a TWS version higher than 949). Also note that if two 198 | # primary sessions are started, they will both keep reconnecting 199 | # and disconnecting the other: therefore care needs to be exercised 200 | # in the use of this setting. The default is 'manual'. 201 | 202 | ExistingSessionDetectedAction=primary 203 | 204 | 205 | # If set to 'accept', IBController automatically accepts incoming 206 | # API connection dialogs. If set to 'reject', IBController 207 | # automatically rejects incoming API connection dialogs. If 208 | # set to 'manual', the user must decide whether to accept or reject 209 | # incoming API connection dialogs. The default is 'accept'. 210 | # NB: it is recommended to set this to 'reject', and to explicitly 211 | # configure which IP addresses can connect to the API in TWS's API 212 | # configuration page, as this is much more secure (in this case, no 213 | # incoming API connection dialogs will occur for those IP addresses). 214 | 215 | AcceptIncomingConnectionAction=accept 216 | 217 | 218 | # NB: ShowAllTrades is deprecated starting with TWS 955. This is 219 | # because IB have changed the Trades window in such a way that it 220 | # is no longer possible to programmatically set the desired option. 221 | # However the user's setting is now persisted within and across 222 | # TWS sessions, so ShowAllTrades is no longer really needed. If 223 | # ShowAllTrades is set to 'yes', it will have no observable effect. 224 | # 225 | # The following description applies to TWS versions BEFORE TWS 955: 226 | # 227 | # If ShowAllTrades is set to 'yes', IBController causes TWS to 228 | # display the Trades log at startup, and sets the 'All' checkbox 229 | # to ensure that the API reports all executions that have occurred 230 | # during the past week. Moreover, any attempt by the user to change 231 | # any of the 'Show trades' checkboxes is ignored; similarly if the 232 | # user closes the Trades log, it is immediately re-displayed with 233 | # the 'All' checkbox set. If set to 'no', IBController does not 234 | # interact with the Trades log, and only the current session's 235 | # executions are returned via the API (unless the user changes the 236 | # Trades log checkboxes). The default is 'no'. 237 | 238 | ShowAllTrades=no 239 | 240 | 241 | # If ForceTwsApiPort is set to an integer, it forces IBController to 242 | # change the TWS API Socket Port to that number shortly after 243 | # startup. This setting is most commonly required if you are running 244 | # multiple IBController instances for different accounts and would 245 | # like different ports to be configured automatically (ie without you 246 | # needing do this manually in the TWS window that IBController would 247 | # open). Leaving the setting blank will make no change, so 4001 or 248 | # 7496 would be the usual default (or whatever you manually change 249 | # the port number to). 250 | 251 | ForceTwsApiPort=4002 252 | 253 | 254 | # If ReadOnlyLogin is set to 'yes', and the user is enrolled in IB's 255 | # account security programme, the user will not be asked to supply 256 | # the security code, and login to TWS will occur automatically in 257 | # read-only mode: in this mode, placing or managing orders is not 258 | # allowed. If set to 'no', and the user is enrolled in IB's account 259 | # security programme, the user must supply the relevant security 260 | # code to complete the login. If the user is not enrolled in IB's 261 | # account security programme, this setting is ignored. The default 262 | # is 'no'. 263 | 264 | ReadOnlyLogin=no 265 | 266 | 267 | # Logging in to a paper-trading account results in TWS displaying 268 | # a dialog asking the user to confirm that they are aware that this 269 | # is not a brokerage account. Until this dialog has been accepted, 270 | # TWS will not allow API connections to succeed. Setting this 271 | # to 'yes' (the default) will cause IBController to automatically 272 | # confirm acceptance. Setting it to 'no' will leave the dialog 273 | # on display, and the user will have to deal with it manually. 274 | 275 | AcceptNonBrokerageAccountWarning=yes 276 | 277 | 278 | # 4. TWS Auto-Closedown 279 | # ----------------------- 280 | # 281 | # Set to yes or no (lower case): 282 | # 283 | # yes means allow TWS to shut down automatically at its 284 | # specified shutdown time, which is set via the TWS 285 | # configuration menu. 286 | # 287 | # no means TWS never shuts down automatically. 288 | # 289 | # NB: IB recommends that you do not keep TWS running 290 | # continuously. If you set this setting to 'no', you may 291 | # experience incorrect TWS operation. 292 | 293 | IbAutoClosedown=no 294 | 295 | 296 | 297 | # 5. TWS Tidy Closedown Time 298 | # ---------------------------- 299 | # 300 | # To tell IBController to tidily close TWS at a 301 | # specified day of the week and time, set this value 302 | # to 303 | # for example: 304 | # ClosedownAt=Friday 22:00 305 | # 306 | # Note that the day of the week must be specified using your 307 | # default locale. Also note that Java will only accept 308 | # characters encoded to ISO 8859-1 (Latin-1). This means that 309 | # if the day name in your default locale uses any non-Latin-1 310 | # characters you need to encode them using Unicode escapes 311 | # (see http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.3 312 | # for details). For example, to tidily close TWS at 12:00 on 313 | # Saturday where the default locale is Simplified Chinese, 314 | # use the following: 315 | # #ClosedownAt=\u661F\u671F\u516D 12:00 316 | 317 | ClosedownAt= 318 | 319 | 320 | 321 | # 6. Other TWS Settings 322 | # ----------------------- 323 | # 324 | # If you attempt to place an order for a contract for which 325 | # you have no market data subscription, TWS displays a dialog 326 | # to warn you against such blind trading. 327 | # 328 | # yes means the dialog is dismissed as though the user had 329 | # clicked the 'Ok' button: this means that you accept 330 | # the risk and want the order to be submitted. 331 | # 332 | # no means the dialog remains on display and must be 333 | # handled by the user. 334 | 335 | AllowBlindTrading=no 336 | 337 | 338 | # Indian versions of TWS may display a password expiry 339 | # notification dialog and a NSE Compliance dialog. These can be 340 | # dismissed by setting the following to yes. By default the 341 | # password expiry notice is not dismissed, but the NSE Compliance 342 | # notice is dismissed. 343 | 344 | # Warning: setting DismissPasswordExpiryWarning=yes will mean 345 | # you will not be notified when your password is about to expire. 346 | # You must then take other measures to ensure that your password 347 | # is changed within the expiry period, otherwise IBController will 348 | # not be able to login successfully. 349 | 350 | DismissPasswordExpiryWarning=no 351 | DismissNSEComplianceNotice=yes 352 | 353 | 354 | # Since TWS 906, orders placed using the BookTrader have to be confirmed 355 | # via a dialog box (this was due to a legal dispute between IB and Trading 356 | # Technologies who claimed IB were infringing a patent). If 357 | # AutoConfirmOrders=yes, then when orders are placed using the BookTrader, 358 | # the confirmation dialog is automatically handled, thereby effectively 359 | # restoring the one-click trading. The default is 'no', requiring the user 360 | # to manually confirm each trade. 361 | # 362 | # NB: this setting has been removed as the dispute has been resolved and 363 | # TWS users now have the option to request the order confirmation dialog 364 | # to not be displayed. 365 | # 366 | #AutoConfirmOrders=no 367 | 368 | 369 | # You can tell TWS to automatically save its settings on a schedule 370 | # of your choosing. You can specify one of more specific times, 371 | # like this: 372 | # 373 | # SaveTwsSettingsAt=HH;MM [ HH:MM]... 374 | # 375 | # for example: 376 | # SaveTwsSettingsAt=08:00 12:30 17:30 377 | # 378 | # Or you can specify an interval at which settings are to be saved, 379 | # optionally starting at a specific time and continuing until another 380 | # time, like this: 381 | # 382 | #SaveTwsSettingsAt=Every n [{mins | hours}] [hh:mm] [hh:mm] 383 | # 384 | # where the first hh:mm is the shart time and the second is the end 385 | # time. If you don't specify the end time, settings are saved regularly 386 | # from the start time till midnight. If you don't specify the start time. 387 | # settings are saved reularly all day, beginning at 00:00. Note that 388 | # settings will always be saved at the end time, even if that is not 389 | # exactly one interval later than the previous time. Examples: 390 | # 391 | # To save every 30 minutes all day starting at 00:00 392 | #SaveTwsSettingsAt=Every 30 393 | #SaveTwsSettingsAt=Every 30 mins 394 | # 395 | # To save every hour starting at 08:00 and ending at midnight 396 | #SaveTwsSettingsAt=Every 1 hours 08:00 397 | #SaveTwsSettingsAt=Every 1 hours 08:00 00:00 398 | # 399 | # To save every 90 minutes starting at 08:00 up to and including 17:43 400 | #SaveTwsSettingsAt=Every 90 08:00 17:43 401 | 402 | SaveTwsSettingsAt= 403 | 404 | 405 | 406 | # 6. IBController Server Settings 407 | # --------------------------------- 408 | # 409 | # Do NOT CHANGE THE FOLLOWING SETTINGS unless you 410 | # intend to issue commands to IBController (for example 411 | # using telnet). Note that these settings have nothing to 412 | # do with running programs that use the TWS API. 413 | 414 | # The port that IBController listens on for commands 415 | # such as "STOP". DO NOT set this to the port number 416 | # used for TWS API connections. There is no good reason 417 | # to change this setting unless the port is used by 418 | # some other application. 419 | 420 | IbControllerPort=7462 421 | 422 | 423 | # A comma separated list of ip addresses, or host names, 424 | # which are allowed addresses for sending commands to 425 | # IBController. Commands can always be sent from the 426 | # same host as IBController is running on. 427 | 428 | IbControlFrom= 429 | 430 | 431 | # Specifies the IP address on which the IBController Server 432 | # is to listen. For a multi-homed host, this can be used 433 | # to specify that connection requests are only to be 434 | # accepted on the specified address. The default is to 435 | # accept connection requests on all local addresses. 436 | 437 | IbBindAddress=0.0.0.0 438 | 439 | 440 | # The specified string is output by the server when 441 | # the connection is first opened and after the completion 442 | # of each command. This can be useful if sending commands 443 | # using an interactive program such as telnet. The default 444 | # is that no prompt is output. 445 | # For example: 446 | # 447 | # CommandPrompt=> 448 | 449 | CommandPrompt= 450 | 451 | 452 | # Some commands can return intermediate information about 453 | # their progress. This setting controls whether such 454 | # information is sent. The default is that such information 455 | # is not sent. 456 | 457 | SuppressInfoMessages=yes 458 | 459 | 460 | 461 | # 7. Diagnostic Settings 462 | # ------------------------ 463 | 464 | # If LogComponents is set to 'open' or 'yes' or 'true', 465 | # IBController logs information about the structure of each 466 | # TWS window it detects the first time it is encountered. If 467 | # set to 'activate', the information is logged every time 468 | # a TWS window is made active. If set to 'never' or 'no' or 469 | # 'false', this information is never logged. The default is 470 | # 'never'. 471 | # 472 | # The logged information shows the hierarchical organisation 473 | # of all the components of the window, and includes the 474 | # current values of text boxes and labels. 475 | # 476 | # Note that when set to 'open', 'activate' or'yes', there is 477 | # a small performance impact due to the extra logging. Also 478 | # logfile size may be significantly increased, especially if 479 | # set to 'activate' and the user is actively using TWS. It 480 | # is therefore recommended that the setting be left at 'no' 481 | # unless there is a specific reason that this information is 482 | # needed. 483 | 484 | LogComponents=never 485 | -------------------------------------------------------------------------------- /ibgateway/components/ibc/config.ini: -------------------------------------------------------------------------------- 1 | # Note that in the comments in this file, TWS refers to both the Trader 2 | # Workstation and the IB Gateway, unless explicitly stated otherwise. 3 | # 4 | # When referred to below, the default value for a setting is the value 5 | # assumed if either the setting is included but no value is specified, or 6 | # the setting is not included at all. 7 | # 8 | # IBC may also be used to start the FIX CTCI Gateway. All settings 9 | # relating to this have names prefixed with FIX. 10 | # 11 | # The IB API Gateway and the FIX CTCI Gateway share the same code. Which 12 | # gateway actually runs is governed by an option on the initial gateway 13 | # login screen. The FIX setting described under IBC Startup 14 | # Settings below controls this. 15 | 16 | 17 | 18 | # ============================================================================= 19 | # 1. IBC Startup Settings 20 | # ============================================================================= 21 | 22 | 23 | # IBC may be used to start the IB Gateway for the FIX CTCI. This 24 | # setting must be set to 'yes' if you want to run the FIX CTCI gateway. The 25 | # default is 'no'. 26 | 27 | FIX=no 28 | 29 | 30 | 31 | # ============================================================================= 32 | # 2. Authentication Settings 33 | # ============================================================================= 34 | 35 | # TWS and the IB API gateway require a single username and password. 36 | # You may specify the username and password using the following settings: 37 | # 38 | # IbLoginId 39 | # IbPassword 40 | # 41 | # Alternatively, you can specify the username and password in the command 42 | # files used to start TWS or the Gateway, but this is not recommended for 43 | # security reasons. 44 | # 45 | # If you don't specify them, you will be prompted for them in the usual 46 | # login dialog when TWS starts (but whatever you have specified will be 47 | # included in the dialog automatically: for example you may specify the 48 | # username but not the password, and then you will be prompted for the 49 | # password via the login dialog). Note that if you specify either 50 | # the username or the password (or both) in the command file, then 51 | # IbLoginId and IbPassword settings defined in this file are ignored. 52 | # 53 | # 54 | # The FIX CTCI gateway requires one username and password for FIX order 55 | # routing, and optionally a separate username and password for market 56 | # data connections. You may specify the usernames and passwords using 57 | # the following settings: 58 | # 59 | # FIXLoginId 60 | # FIXPassword 61 | # IbLoginId (optional - for market data connections) 62 | # IbPassword (optional - for market data connections) 63 | # 64 | # Alternatively you can specify the FIX username and password in the 65 | # command file used to start the FIX CTCI Gateway, but this is not 66 | # recommended for security reasons. 67 | # 68 | # If you don't specify them, you will be prompted for them in the usual 69 | # login dialog when FIX CTCI gateway starts (but whatever you have 70 | # specified will be included in the dialog automatically: for example 71 | # you may specify the usernames but not the passwords, and then you will 72 | # be prompted for the passwords via the login dialog). Note that if you 73 | # specify either the FIX username or the FIX password (or both) on the 74 | # command line, then FIXLoginId and FIXPassword settings defined in this 75 | # file are ignored; he same applies to the market data username and 76 | # password. 77 | 78 | # IB API Authentication Settings 79 | # ------------------------------ 80 | 81 | # Your TWS username: 82 | 83 | IbLoginId=edemo 84 | 85 | 86 | # Your TWS password: 87 | 88 | IbPassword=demouser 89 | 90 | 91 | # FIX CTCI Authentication Settings 92 | # -------------------------------- 93 | 94 | # Your FIX CTCI username: 95 | 96 | FIXLoginId= 97 | 98 | 99 | # Your FIX CTCI password: 100 | 101 | FIXPassword= 102 | 103 | 104 | # Second Factor Authentication Timeout Settings 105 | # --------------------------------------------- 106 | # 107 | # If you use the IBKR Mobile app for second factor authentication, 108 | # and you fail to complete the process before the time limit imposed 109 | # by IBKR, you can use this setting to tell IBC to exit: arrangements 110 | # can then be made to automatically restart IBC in order to initiate 111 | # the login sequence afresh. Otherwise, manual intervention at TWS's 112 | # Second Factor Authentication dialog is needed to complete the 113 | # login. 114 | # 115 | # Permitted values are 'yes' and 'no'. The default is 'no'. 116 | # 117 | # Note that the scripts provided with the IBC zips for Windows and 118 | # Linux provide options to automatically restart in these 119 | # circumstances, but only if this setting is also set to 'yes'. 120 | 121 | ExitAfterSecondFactorAuthenticationTimeout=no 122 | 123 | 124 | # This setting is only relevant if 125 | # ExitAfterSecondFactorAuthenticationTimeout is set to 'yes'. 126 | # 127 | # It controls how long (in seconds) IBC waits for login to complete 128 | # after the user acknowledges the second factor authentication 129 | # alert at the IBKR Mobile app. If login has not completed after 130 | # this time, IBC terminates. 131 | # The default value is 40. 132 | 133 | SecondFactorAuthenticationExitInterval= 134 | 135 | 136 | # Trading Mode 137 | # ------------ 138 | # 139 | # TWS 955 introduced a new Trading Mode combo box on its login 140 | # dialog. This indicates whether the live account or the paper 141 | # trading account corresponding to the supplied credentials is 142 | # to be used. The allowed values are 'live' (the default) and 143 | # 'paper'. For earlier versions of TWS this setting has no 144 | # effect. 145 | 146 | #TradingMode=live 147 | TradingMode=paper 148 | 149 | 150 | # Paper-trading Account Warning 151 | # ----------------------------- 152 | # 153 | # Logging in to a paper-trading account results in TWS displaying 154 | # a dialog asking the user to confirm that they are aware that this 155 | # is not a brokerage account. Until this dialog has been accepted, 156 | # TWS will not allow API connections to succeed. Setting this 157 | # to 'yes' (the default) will cause IBC to automatically 158 | # confirm acceptance. Setting it to 'no' will leave the dialog 159 | # on display, and the user will have to deal with it manually. 160 | 161 | AcceptNonBrokerageAccountWarning=yes 162 | 163 | 164 | # Login Dialog Display Timeout 165 | #----------------------------- 166 | # 167 | # In some circumstances, starting TWS may result in failure to display 168 | # the login dialog. Restarting TWS may help to resolve this situation, 169 | # and IBC does this automatically. 170 | # 171 | # This setting controls how long (in seconds) IBC waits for the login 172 | # dialog to appear before restarting TWS. 173 | # 174 | # Note that in normal circumstances with a reasonably specified 175 | # computer the time to displaying the login dialog is typically less 176 | # than 20 seconds, and frequently much less. However many factors can 177 | # influence this, and it is unwise to set this value too low. 178 | # 179 | # The default value is 60. 180 | 181 | LoginDialogDisplayTimeout = 60 182 | 183 | 184 | 185 | # ============================================================================= 186 | # 3. TWS Startup Settings 187 | # ============================================================================= 188 | 189 | # Path to settings store 190 | # ---------------------- 191 | # 192 | # Path to the directory where TWS should store its settings. This is 193 | # normally the folder in which TWS is installed. However you may set 194 | # it to some other location if you wish (for example if you want to 195 | # run multiple instances of TWS with different settings). 196 | # 197 | # It is recommended for clarity that you use an absolute path. The 198 | # effect of using a relative path is undefined. 199 | # 200 | # Linux and macOS users should use the appropriate path syntax. 201 | # 202 | # Note that, for Windows users, you MUST use double separator 203 | # characters to separate the elements of the folder path: for 204 | # example, IbDir=C:\\IBLiveSettings is valid, but 205 | # IbDir=C:\IBLiveSettings is NOT valid and will give unexpected 206 | # results. Linux and macOS users need not use double separators, 207 | # but they are acceptable. 208 | # 209 | # The default is the current working directory when IBC is 210 | # started. 211 | 212 | IbDir=/root/Jts 213 | 214 | 215 | # Store settings on server 216 | # ------------------------ 217 | # 218 | # If you wish to store a copy of your TWS settings on IB's 219 | # servers as well as locally on your computer, set this to 220 | # 'yes': this enables you to run TWS on different computers 221 | # with the same configuration, market data lines, etc. If set 222 | # to 'no', running TWS on different computers will not share the 223 | # same settings. If no value is specified, TWS will obtain its 224 | # settings from the same place as the last time this user logged 225 | # in (whether manually or using IBC). 226 | 227 | StoreSettingsOnServer=no 228 | 229 | 230 | # Minimize TWS on startup 231 | # ----------------------- 232 | # 233 | # Set to 'yes' to minimize TWS when it starts: 234 | 235 | MinimizeMainWindow=no 236 | 237 | 238 | # Existing Session Detected Action 239 | # -------------------------------- 240 | # 241 | # When a user logs on to an IBKR account for trading purposes by any means, the 242 | # IBKR account server checks to see whether the account is already logged in 243 | # elsewhere. If so, a dialog is displayed to both the users that enables them 244 | # to determine what happens next. The 'ExistingSessionDetectedAction' setting 245 | # instructs TWS how to proceed when it displays this dialog: 246 | # 247 | # * If the new TWS session is set to 'secondary', the existing session continues 248 | # and the new session terminates. Thus a secondary TWS session can never 249 | # override any other session. 250 | # 251 | # * If the existing TWS session is set to 'primary', the existing session 252 | # continues and the new session terminates (even if the new session is also 253 | # set to primary). Thus a primary TWS session can never be overridden by 254 | # any new session). 255 | # 256 | # * If both the existing and the new TWS sessions are set to 'primaryoverride', 257 | # the existing session terminates and the new session proceeds. 258 | # 259 | # * If the existing TWS session is set to 'manual', the user must handle the 260 | # dialog. 261 | # 262 | # The difference between 'primary' and 'primaryoverride' is that a 263 | # 'primaryoverride' session can be overriden over by a new 'primary' session, 264 | # but a 'primary' session cannot be overriden by any other session. 265 | # 266 | # When set to 'primary', if another TWS session is started and manually told to 267 | # end the 'primary' session, the 'primary' session is automatically reconnected. 268 | # 269 | # The default is 'manual'. 270 | 271 | ExistingSessionDetectedAction=primary 272 | 273 | 274 | # Override TWS API Port Number 275 | # ---------------------------- 276 | # 277 | # If OverrideTwsApiPort is set to an integer, IBC changes the 278 | # 'Socket port' in TWS's API configuration to that number shortly 279 | # after startup. Leaving the setting blank will make no change to 280 | # the current setting. This setting is only intended for use in 281 | # certain specialized situations where the port number needs to 282 | # be set dynamically at run-time: most users will never need it, 283 | # so don't use it unless you know you need it. 284 | 285 | OverrideTwsApiPort=4002 286 | 287 | 288 | # Read-only Login 289 | # --------------- 290 | # 291 | # If ReadOnlyLogin is set to 'yes', and the user is enrolled in IB's 292 | # account security programme, the user will not be asked to perform 293 | # the second factor authentication action, and login to TWS will 294 | # occur automatically in read-only mode: in this mode, placing or 295 | # managing orders is not allowed. If set to 'no', and the user is 296 | # enrolled in IB's account security programme, the user must perform 297 | # the relevant second factor authentication action to complete the 298 | # login. 299 | 300 | # If the user is not enrolled in IB's account security programme, 301 | # this setting is ignored. The default is 'no'. 302 | 303 | ReadOnlyLogin=no 304 | 305 | 306 | # Read-only API 307 | # ------------- 308 | # 309 | # If ReadOnlyApi is set to 'yes', API programs cannot submit, modify 310 | # or cancel orders. If set to 'no', API programs can do these things. 311 | # If not set, the existing TWS/Gateway configuration is unchanged. 312 | # NB: this setting is really only supplied for the benefit of new TWS 313 | # or Gateway instances that are being automatically installed and 314 | # started without user intervention (eg Docker containers). Where 315 | # a user is involved, they should use the Global Configuration to 316 | # set the relevant checkbox (this only needs to be done once) and 317 | # not provide a value for this setting. 318 | 319 | ReadOnlyApi=no 320 | 321 | 322 | 323 | # ============================================================================= 324 | # 4. TWS Auto-Closedown 325 | # ============================================================================= 326 | # 327 | # IMPORTANT NOTE: Starting with TWS 974, this setting no longer 328 | # works properly, because IB have changed the way TWS handles its 329 | # autologoff mechanism. 330 | # 331 | # You should now configure the TWS autologoff time to something 332 | # convenient for you, and restart IBC each day. 333 | # 334 | # Alternatively, discontinue use of IBC and use the auto-relogin 335 | # mechanism within TWS 974 and later versions (note that the 336 | # auto-relogin mechanism provided by IB is not available if you 337 | # use IBC). 338 | 339 | # Set to yes or no (lower case). 340 | # 341 | # yes means allow TWS to shut down automatically at its 342 | # specified shutdown time, which is set via the TWS 343 | # configuration menu. 344 | # 345 | # no means TWS never shuts down automatically. 346 | # 347 | # NB: IB recommends that you do not keep TWS running 348 | # continuously. If you set this setting to 'no', you may 349 | # experience incorrect TWS operation. 350 | # 351 | # NB: the default for this setting is 'no'. Since this will 352 | # only work properly with TWS versions earlier than 974, you 353 | # should explicitly set this to 'yes' for version 974 and later. 354 | 355 | IbAutoClosedown=no 356 | 357 | 358 | 359 | # ============================================================================= 360 | # 5. TWS Tidy Closedown Time 361 | # ============================================================================= 362 | # 363 | # NB: starting with TWS 974 this is no longer a useful option 364 | # because both TWS and Gateway now have the same auto-logoff 365 | # mechanism, and IBC can no longer avoid this. 366 | # 367 | # Note that giving this setting a value does not change TWS's 368 | # auto-logoff in any way: any setting will be additional to the 369 | # TWS auto-logoff. 370 | # 371 | # To tell IBC to tidily close TWS at a specified time every 372 | # day, set this value to , for example: 373 | # ClosedownAt=22:00 374 | # 375 | # To tell IBC to tidily close TWS at a specified day and time 376 | # each week, set this value to , for example: 377 | # ClosedownAt=Friday 22:00 378 | # 379 | # Note that the day of the week must be specified using your 380 | # default locale. Also note that Java will only accept 381 | # characters encoded to ISO 8859-1 (Latin-1). This means that 382 | # if the day name in your default locale uses any non-Latin-1 383 | # characters you need to encode them using Unicode escapes 384 | # (see http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.3 385 | # for details). For example, to tidily close TWS at 12:00 on 386 | # Saturday where the default locale is Simplified Chinese, 387 | # use the following: 388 | # #ClosedownAt=\u661F\u671F\u516D 12:00 389 | 390 | ClosedownAt= 391 | 392 | 393 | 394 | # ============================================================================= 395 | # 6. Other TWS Settings 396 | # ============================================================================= 397 | 398 | # Accept Incoming Connection 399 | # -------------------------- 400 | # 401 | # If set to 'accept', IBC automatically accepts incoming 402 | # API connection dialogs. If set to 'reject', IBC 403 | # automatically rejects incoming API connection dialogs. If 404 | # set to 'manual', the user must decide whether to accept or reject 405 | # incoming API connection dialogs. The default is 'manual'. 406 | # NB: it is recommended to set this to 'reject', and to explicitly 407 | # configure which IP addresses can connect to the API in TWS's API 408 | # configuration page, as this is much more secure (in this case, no 409 | # incoming API connection dialogs will occur for those IP addresses). 410 | 411 | AcceptIncomingConnectionAction=accept 412 | 413 | 414 | # Allow Blind Trading 415 | # ------------------- 416 | # 417 | # If you attempt to place an order for a contract for which 418 | # you have no market data subscription, TWS displays a dialog 419 | # to warn you against such blind trading. 420 | # 421 | # yes means the dialog is dismissed as though the user had 422 | # clicked the 'Ok' button: this means that you accept 423 | # the risk and want the order to be submitted. 424 | # 425 | # no means the dialog remains on display and must be 426 | # handled by the user. 427 | 428 | AllowBlindTrading=no 429 | 430 | 431 | # Save Settings on a Schedule 432 | # --------------------------- 433 | # 434 | # You can tell TWS to automatically save its settings on a schedule 435 | # of your choosing. You can specify one or more specific times, 436 | # like this: 437 | # 438 | # SaveTwsSettingsAt=HH:MM [ HH:MM]... 439 | # 440 | # for example: 441 | # SaveTwsSettingsAt=08:00 12:30 17:30 442 | # 443 | # Or you can specify an interval at which settings are to be saved, 444 | # optionally starting at a specific time and continuing until another 445 | # time, like this: 446 | # 447 | #SaveTwsSettingsAt=Every n [{mins | hours}] [hh:mm] [hh:mm] 448 | # 449 | # where the first hh:mm is the start time and the second is the end 450 | # time. If you don't specify the end time, settings are saved regularly 451 | # from the start time till midnight. If you don't specify the start time. 452 | # settings are saved regularly all day, beginning at 00:00. Note that 453 | # settings will always be saved at the end time, even if that is not 454 | # exactly one interval later than the previous time. If neither 'mins' 455 | # nor 'hours' is specified, 'mins' is assumed. Examples: 456 | # 457 | # To save every 30 minutes all day starting at 00:00 458 | #SaveTwsSettingsAt=Every 30 459 | #SaveTwsSettingsAt=Every 30 mins 460 | # 461 | # To save every hour starting at 08:00 and ending at midnight 462 | #SaveTwsSettingsAt=Every 1 hours 08:00 463 | #SaveTwsSettingsAt=Every 1 hours 08:00 00:00 464 | # 465 | # To save every 90 minutes starting at 08:00 up to and including 17:43 466 | #SaveTwsSettingsAt=Every 90 08:00 17:43 467 | 468 | SaveTwsSettingsAt= 469 | 470 | 471 | 472 | # ============================================================================= 473 | # 7. Settings Specific to Indian Versions of TWS 474 | # ============================================================================= 475 | 476 | # Indian versions of TWS may display a password expiry 477 | # notification dialog and a NSE Compliance dialog. These can be 478 | # dismissed by setting the following to yes. By default the 479 | # password expiry notice is not dismissed, but the NSE Compliance 480 | # notice is dismissed. 481 | 482 | # Warning: setting DismissPasswordExpiryWarning=yes will mean 483 | # you will not be notified when your password is about to expire. 484 | # You must then take other measures to ensure that your password 485 | # is changed within the expiry period, otherwise IBC will 486 | # not be able to login successfully. 487 | 488 | DismissPasswordExpiryWarning=no 489 | DismissNSEComplianceNotice=yes 490 | 491 | 492 | 493 | # ============================================================================= 494 | # 8. IBC Command Server Settings 495 | # ============================================================================= 496 | 497 | # Do NOT CHANGE THE FOLLOWING SETTINGS unless you 498 | # intend to issue commands to IBC (for example 499 | # using telnet). Note that these settings have nothing to 500 | # do with running programs that use the TWS API. 501 | 502 | # Command Server Port Number 503 | # -------------------------- 504 | # 505 | # The port number that IBC listens on for commands 506 | # such as "STOP". DO NOT set this to the port number 507 | # used for TWS API connections. There is no good reason 508 | # to change this setting unless the port is used by 509 | # some other application (typically another instance of 510 | # IBC). The default value is 0, which tells IBC not to 511 | # start the command server 512 | 513 | CommandServerPort=7462 514 | 515 | 516 | # Permitted Command Sources 517 | # ------------------------- 518 | # 519 | # A comma separated list of IP addresses, or host names, 520 | # which are allowed addresses for sending commands to 521 | # IBC. Commands can always be sent from the 522 | # same host as IBC is running on. 523 | 524 | ControlFrom= 525 | 526 | 527 | # Address for Receiving Commands 528 | # ------------------------------ 529 | # 530 | # Specifies the IP address on which the Command Server 531 | # is to listen. For a multi-homed host, this can be used 532 | # to specify that connection requests are only to be 533 | # accepted on the specified address. The default is to 534 | # accept connection requests on all local addresses. 535 | 536 | BindAddress=0.0.0.0 537 | 538 | 539 | # Command Prompt 540 | # -------------- 541 | # 542 | # The specified string is output by the server when 543 | # the connection is first opened and after the completion 544 | # of each command. This can be useful if sending commands 545 | # using an interactive program such as telnet. The default 546 | # is that no prompt is output. 547 | # For example: 548 | # 549 | # CommandPrompt=> 550 | 551 | CommandPrompt= 552 | 553 | 554 | # Suppress Command Server Info Messages 555 | # ------------------------------------- 556 | # 557 | # Some commands can return intermediate information about 558 | # their progress. This setting controls whether such 559 | # information is sent. The default is that such information 560 | # is not sent. 561 | 562 | SuppressInfoMessages=yes 563 | 564 | 565 | 566 | # ============================================================================= 567 | # 9. Diagnostic Settings 568 | # ============================================================================= 569 | # 570 | # IBC can log information about the structure of windows 571 | # displayed by TWS. This information is useful when adding 572 | # new features to IBC or when behaviour is not as expected. 573 | # 574 | # The logged information shows the hierarchical organisation 575 | # of all the components of the window, and includes the 576 | # current values of text boxes and labels. 577 | # 578 | # Note that this structure logging has a small performance 579 | # impact, and depending on the settings can cause the logfile 580 | # size to be significantly increased. It is therefore 581 | # recommended that the LogStructureWhen setting be set to 582 | # 'never' (the default) unless there is a specific reason 583 | # that this information is needed. 584 | 585 | 586 | # Scope of Structure Logging 587 | # -------------------------- 588 | # 589 | # The LogStructureScope setting indicates which windows are 590 | # eligible for structure logging: 591 | # 592 | # - if set to 'known', only windows that IBC recognizes 593 | # are eligible - these are windows that IBC has some 594 | # interest in monitoring, usually to take some action 595 | # on the user's behalf; 596 | # 597 | # - if set to 'unknown', only windows that IBC does not 598 | # recognize are eligible. Most windows displayed by 599 | # TWS fall into this category; 600 | # 601 | # - if set to 'untitled', only windows that IBC does not 602 | # recognize and that have no title are eligible. These 603 | # are usually message boxes or similar small windows, 604 | # 605 | # - if set to 'all', then every window displayed by TWS 606 | # is eligible. 607 | # 608 | # The default value is 'known'. 609 | 610 | LogStructureScope=known 611 | 612 | 613 | # When to Log Window Structure 614 | # ---------------------------- 615 | # 616 | # The LogStructureWhen setting specifies the circumstances 617 | # when eligible TWS windows have their structure logged: 618 | # 619 | # - if set to 'open' or 'yes' or 'true', IBC logs the 620 | # structure of an eligible window the first time it 621 | # is encountered; 622 | # 623 | # - if set to 'activate', the structure is logged every 624 | # time an eligible window is made active; 625 | # 626 | # - if set to 'never' or 'no' or 'false', structure 627 | # information is never logged. 628 | # 629 | # The default value is 'never'. 630 | 631 | LogStructureWhen=never 632 | 633 | 634 | # DEPRECATED SETTING 635 | # ------------------ 636 | # 637 | # LogComponents - THIS SETTING WILL BE REMOVED IN A FUTURE 638 | # RELEASE 639 | # 640 | # If LogComponents is set to any value, this is equivalent 641 | # to setting LogStructureWhen to that same value and 642 | # LogStructureScope to 'all': the actual values of those 643 | # settings are ignored. The default is that the values 644 | # of LogStructureScope and LogStructureWhen are honoured. 645 | 646 | #LogComponents= -------------------------------------------------------------------------------- /dockerfile_airflow/plot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8; py-indent-offset:4 -*- 3 | ############################################################################### 4 | # 5 | # Copyright (C) 2015-2023 Daniel Rodriguez 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | # 20 | ############################################################################### 21 | from __future__ import (absolute_import, division, print_function, 22 | unicode_literals) 23 | 24 | import bisect 25 | import collections 26 | import datetime 27 | import itertools 28 | import math 29 | import operator 30 | import sys 31 | 32 | import matplotlib 33 | import numpy as np # guaranteed by matplotlib 34 | import matplotlib.dates as mdates 35 | import matplotlib.font_manager as mfontmgr 36 | import matplotlib.legend as mlegend 37 | import matplotlib.ticker as mticker 38 | 39 | from ..utils.py3 import range, with_metaclass, string_types, integer_types 40 | from .. import AutoInfoClass, MetaParams, TimeFrame, date2num 41 | 42 | from .finance import plot_candlestick, plot_ohlc, plot_volume, plot_lineonclose 43 | from .formatters import (MyVolFormatter, MyDateFormatter, getlocator) 44 | from . import locator as loc 45 | from .multicursor import MultiCursor 46 | from .scheme import PlotScheme 47 | from .utils import tag_box_style 48 | 49 | 50 | class PInfo(object): 51 | def __init__(self, sch): 52 | self.sch = sch 53 | self.nrows = 0 54 | self.row = 0 55 | self.clock = None 56 | self.x = None 57 | self.xlen = 0 58 | self.sharex = None 59 | self.figs = list() 60 | self.cursors = list() 61 | self.daxis = collections.OrderedDict() 62 | self.vaxis = list() 63 | self.zorder = dict() 64 | self.coloridx = collections.defaultdict(lambda: -1) 65 | self.handles = collections.defaultdict(list) 66 | self.labels = collections.defaultdict(list) 67 | self.legpos = collections.defaultdict(int) 68 | 69 | self.prop = mfontmgr.FontProperties(size=self.sch.subtxtsize) 70 | 71 | def newfig(self, figid, numfig, mpyplot): 72 | fig = mpyplot.figure(figid + numfig) 73 | self.figs.append(fig) 74 | self.daxis = collections.OrderedDict() 75 | self.vaxis = list() 76 | self.row = 0 77 | self.sharex = None 78 | return fig 79 | 80 | def nextcolor(self, ax): 81 | self.coloridx[ax] += 1 82 | return self.coloridx[ax] 83 | 84 | def color(self, ax): 85 | return self.sch.color(self.coloridx[ax]) 86 | 87 | def zordernext(self, ax): 88 | z = self.zorder[ax] 89 | if self.sch.zdown: 90 | return z * 0.9999 91 | return z * 1.0001 92 | 93 | def zordercur(self, ax): 94 | return self.zorder[ax] 95 | 96 | 97 | class Plot_OldSync(with_metaclass(MetaParams, object)): 98 | params = (('scheme', PlotScheme()),) 99 | 100 | def __init__(self, **kwargs): 101 | for pname, pvalue in kwargs.items(): 102 | setattr(self.p.scheme, pname, pvalue) 103 | if not hasattr(self.p.scheme, 'locbg'): 104 | setattr(self.p.scheme, 'locbg', 'white') 105 | setattr(self.p.scheme, 'locbgother', 'white') 106 | 107 | def drawtag(self, ax, x, y, facecolor, edgecolor, alpha=0.9, **kwargs): 108 | 109 | txt = ax.text(x, y, '%.2f' % y, va='center', ha='left', 110 | fontsize=self.pinf.sch.subtxtsize, 111 | bbox=dict(boxstyle=tag_box_style, 112 | facecolor=facecolor, 113 | edgecolor=edgecolor, 114 | alpha=alpha), 115 | # 3.0 is the minimum default for text 116 | zorder=self.pinf.zorder[ax] + 3.0, 117 | **kwargs) 118 | 119 | def plot(self, strategy, figid=0, numfigs=1, iplot=True, 120 | start=None, end=None, **kwargs): 121 | # pfillers={}): 122 | if not strategy.datas: 123 | return 124 | 125 | if not len(strategy): 126 | return 127 | 128 | if iplot: 129 | if 'ipykernel' in sys.modules: 130 | matplotlib.use('nbagg') 131 | 132 | # this import must not happen before matplotlib.use 133 | import matplotlib.pyplot as mpyplot 134 | self.mpyplot = mpyplot 135 | 136 | self.pinf = PInfo(self.p.scheme) 137 | self.sortdataindicators(strategy) 138 | self.calcrows(strategy) 139 | 140 | st_dtime = strategy.lines.datetime.plot() 141 | if start is None: 142 | start = 0 143 | if end is None: 144 | end = len(st_dtime) 145 | 146 | if isinstance(start, datetime.date): 147 | start = bisect.bisect_left(st_dtime, date2num(start)) 148 | 149 | if isinstance(end, datetime.date): 150 | end = bisect.bisect_right(st_dtime, date2num(end)) 151 | 152 | if end < 0: 153 | end = len(st_dtime) + 1 + end # -1 = len() -2 = len() - 1 154 | 155 | slen = len(st_dtime[start:end]) 156 | d, m = divmod(slen, numfigs) 157 | pranges = list() 158 | for i in range(numfigs): 159 | a = d * i + start 160 | if i == (numfigs - 1): 161 | d += m # add remainder to last stint 162 | b = a + d 163 | 164 | pranges.append([a, b, d]) 165 | 166 | figs = [] 167 | 168 | for numfig in range(numfigs): 169 | # prepare a figure 170 | fig = self.pinf.newfig(figid, numfig, self.mpyplot) 171 | figs.append(fig) 172 | 173 | self.pinf.pstart, self.pinf.pend, self.pinf.psize = pranges[numfig] 174 | self.pinf.xstart = self.pinf.pstart 175 | self.pinf.xend = self.pinf.pend 176 | 177 | self.pinf.clock = strategy 178 | self.pinf.xreal = self.pinf.clock.datetime.plot( 179 | self.pinf.pstart, self.pinf.psize) 180 | self.pinf.xlen = len(self.pinf.xreal) 181 | self.pinf.x = list(range(self.pinf.xlen)) 182 | # self.pinf.pfillers = {None: []} 183 | # for key, val in pfillers.items(): 184 | # pfstart = bisect.bisect_left(val, self.pinf.pstart) 185 | # pfend = bisect.bisect_right(val, self.pinf.pend) 186 | # self.pinf.pfillers[key] = val[pfstart:pfend] 187 | 188 | # Do the plotting 189 | # Things that go always at the top (observers) 190 | self.pinf.xdata = self.pinf.x 191 | for ptop in self.dplotstop: 192 | self.plotind(None, ptop, subinds=self.dplotsover[ptop]) 193 | 194 | # Create the rest on a per data basis 195 | dt0, dt1 = self.pinf.xreal[0], self.pinf.xreal[-1] 196 | for data in strategy.datas: 197 | if not data.plotinfo.plot: 198 | continue 199 | 200 | self.pinf.xdata = self.pinf.x 201 | xd = data.datetime.plotrange(self.pinf.xstart, self.pinf.xend) 202 | if len(xd) < self.pinf.xlen: 203 | self.pinf.xdata = xdata = [] 204 | xreal = self.pinf.xreal 205 | dts = data.datetime.plot() 206 | xtemp = list() 207 | for dt in (x for x in dts if dt0 <= x <= dt1): 208 | dtidx = bisect.bisect_left(xreal, dt) 209 | xdata.append(dtidx) 210 | xtemp.append(dt) 211 | 212 | self.pinf.xstart = bisect.bisect_left(dts, xtemp[0]) 213 | self.pinf.xend = bisect.bisect_right(dts, xtemp[-1]) 214 | 215 | for ind in self.dplotsup[data]: 216 | self.plotind( 217 | data, 218 | ind, 219 | subinds=self.dplotsover[ind], 220 | upinds=self.dplotsup[ind], 221 | downinds=self.dplotsdown[ind]) 222 | 223 | self.plotdata(data, self.dplotsover[data]) 224 | 225 | for ind in self.dplotsdown[data]: 226 | self.plotind( 227 | data, 228 | ind, 229 | subinds=self.dplotsover[ind], 230 | upinds=self.dplotsup[ind], 231 | downinds=self.dplotsdown[ind]) 232 | 233 | cursor = MultiCursor( 234 | fig.canvas, list(self.pinf.daxis.values()), 235 | useblit=True, 236 | horizOn=True, vertOn=True, 237 | horizMulti=False, vertMulti=True, 238 | horizShared=True, vertShared=False, 239 | color='black', lw=1, ls=':') 240 | 241 | self.pinf.cursors.append(cursor) 242 | 243 | # Put the subplots as indicated by hspace 244 | fig.subplots_adjust(hspace=self.pinf.sch.plotdist, 245 | top=0.98, left=0.05, bottom=0.05, right=0.95) 246 | 247 | laxis = list(self.pinf.daxis.values()) 248 | 249 | # Find last axis which is not a twinx (date locator fails there) 250 | i = -1 251 | while True: 252 | lastax = laxis[i] 253 | if lastax not in self.pinf.vaxis: 254 | break 255 | 256 | i -= 1 257 | 258 | self.setlocators(lastax) # place the locators/fmts 259 | 260 | # Applying fig.autofmt_xdate if the data axis is the last one 261 | # breaks the presentation of the date labels. why? 262 | # Applying the manual rotation with setp cures the problem 263 | # but the labels from all axis but the last have to be hidden 264 | for ax in laxis: 265 | self.mpyplot.setp(ax.get_xticklabels(), visible=True) 266 | 267 | self.mpyplot.setp(lastax.get_xticklabels(), visible=True, 268 | rotation=self.pinf.sch.tickrotation) 269 | 270 | # Things must be tight along the x axis (to fill both ends) 271 | axtight = 'x' if not self.pinf.sch.ytight else 'both' 272 | self.mpyplot.autoscale(enable=True, axis=axtight, tight=True) 273 | 274 | return figs 275 | 276 | def setlocators(self, ax): 277 | clock = sorted(self.pinf.clock.datas, 278 | key=lambda x: (x._timeframe, x._compression))[0] 279 | 280 | comp = getattr(clock, '_compression', 1) 281 | tframe = getattr(clock, '_timeframe', TimeFrame.Days) 282 | 283 | if self.pinf.sch.fmt_x_data is None: 284 | if tframe == TimeFrame.Years: 285 | fmtdata = '%Y' 286 | elif tframe == TimeFrame.Months: 287 | fmtdata = '%Y-%m' 288 | elif tframe == TimeFrame.Weeks: 289 | fmtdata = '%Y-%m-%d' 290 | elif tframe == TimeFrame.Days: 291 | fmtdata = '%Y-%m-%d' 292 | elif tframe == TimeFrame.Minutes: 293 | fmtdata = '%Y-%m-%d %H:%M' 294 | elif tframe == TimeFrame.Seconds: 295 | fmtdata = '%Y-%m-%d %H:%M:%S' 296 | elif tframe == TimeFrame.MicroSeconds: 297 | fmtdata = '%Y-%m-%d %H:%M:%S.%f' 298 | elif tframe == TimeFrame.Ticks: 299 | fmtdata = '%Y-%m-%d %H:%M:%S.%f' 300 | else: 301 | fmtdata = self.pinf.sch.fmt_x_data 302 | 303 | fordata = MyDateFormatter(self.pinf.xreal, fmt=fmtdata) 304 | for dax in self.pinf.daxis.values(): 305 | dax.fmt_xdata = fordata 306 | 307 | # Major locator / formatter 308 | locmajor = loc.AutoDateLocator(self.pinf.xreal) 309 | ax.xaxis.set_major_locator(locmajor) 310 | if self.pinf.sch.fmt_x_ticks is None: 311 | autofmt = loc.AutoDateFormatter(self.pinf.xreal, locmajor) 312 | else: 313 | autofmt = MyDateFormatter(self.pinf.xreal, 314 | fmt=self.pinf.sch.fmt_x_ticks) 315 | ax.xaxis.set_major_formatter(autofmt) 316 | 317 | def calcrows(self, strategy): 318 | # Calculate the total number of rows 319 | rowsmajor = self.pinf.sch.rowsmajor 320 | rowsminor = self.pinf.sch.rowsminor 321 | nrows = 0 322 | 323 | datasnoplot = 0 324 | for data in strategy.datas: 325 | if not data.plotinfo.plot: 326 | # neither data nor indicators nor volume add rows 327 | datasnoplot += 1 328 | self.dplotsup.pop(data, None) 329 | self.dplotsdown.pop(data, None) 330 | self.dplotsover.pop(data, None) 331 | 332 | else: 333 | pmaster = data.plotinfo.plotmaster 334 | if pmaster is data: 335 | pmaster = None 336 | if pmaster is not None: 337 | # data doesn't add a row, but volume may 338 | if self.pinf.sch.volume: 339 | nrows += rowsminor 340 | else: 341 | # data adds rows, volume may 342 | nrows += rowsmajor 343 | if self.pinf.sch.volume and not self.pinf.sch.voloverlay: 344 | nrows += rowsminor 345 | 346 | if False: 347 | # Datas and volumes 348 | nrows += (len(strategy.datas) - datasnoplot) * rowsmajor 349 | if self.pinf.sch.volume and not self.pinf.sch.voloverlay: 350 | nrows += (len(strategy.datas) - datasnoplot) * rowsminor 351 | 352 | # top indicators/observers 353 | nrows += len(self.dplotstop) * rowsminor 354 | 355 | # indicators above datas 356 | nrows += sum(len(v) for v in self.dplotsup.values()) 357 | nrows += sum(len(v) for v in self.dplotsdown.values()) 358 | 359 | self.pinf.nrows = nrows 360 | 361 | def newaxis(self, obj, rowspan): 362 | ax = self.mpyplot.subplot2grid( 363 | (self.pinf.nrows, 1), (self.pinf.row, 0), 364 | rowspan=rowspan, sharex=self.pinf.sharex) 365 | 366 | # update the sharex information if not available 367 | if self.pinf.sharex is None: 368 | self.pinf.sharex = ax 369 | 370 | # update the row index with the taken rows 371 | self.pinf.row += rowspan 372 | 373 | # save the mapping indicator - axis and return 374 | self.pinf.daxis[obj] = ax 375 | 376 | # Activate grid in all axes if requested 377 | ax.yaxis.tick_right() 378 | ax.grid(self.pinf.sch.grid, which='both') 379 | 380 | return ax 381 | 382 | def plotind(self, iref, ind, 383 | subinds=None, upinds=None, downinds=None, 384 | masterax=None): 385 | 386 | sch = self.p.scheme 387 | 388 | # check subind 389 | subinds = subinds or [] 390 | upinds = upinds or [] 391 | downinds = downinds or [] 392 | 393 | # plot subindicators on self with independent axis above 394 | for upind in upinds: 395 | self.plotind(iref, upind) 396 | 397 | # Get an axis for this plot 398 | ax = masterax or self.newaxis(ind, rowspan=self.pinf.sch.rowsminor) 399 | 400 | indlabel = ind.plotlabel() 401 | 402 | # Scan lines quickly to find out if some lines have to be skipped for 403 | # legend (because matplotlib reorders the legend) 404 | toskip = 0 405 | for lineidx in range(ind.size()): 406 | line = ind.lines[lineidx] 407 | linealias = ind.lines._getlinealias(lineidx) 408 | lineplotinfo = getattr(ind.plotlines, '_%d' % lineidx, None) 409 | if not lineplotinfo: 410 | lineplotinfo = getattr(ind.plotlines, linealias, None) 411 | if not lineplotinfo: 412 | lineplotinfo = AutoInfoClass() 413 | pltmethod = lineplotinfo._get('_method', 'plot') 414 | if pltmethod != 'plot': 415 | toskip += 1 - lineplotinfo._get('_plotskip', False) 416 | 417 | if toskip >= ind.size(): 418 | toskip = 0 419 | 420 | for lineidx in range(ind.size()): 421 | line = ind.lines[lineidx] 422 | linealias = ind.lines._getlinealias(lineidx) 423 | 424 | lineplotinfo = getattr(ind.plotlines, '_%d' % lineidx, None) 425 | if not lineplotinfo: 426 | lineplotinfo = getattr(ind.plotlines, linealias, None) 427 | 428 | if not lineplotinfo: 429 | lineplotinfo = AutoInfoClass() 430 | 431 | if lineplotinfo._get('_plotskip', False): 432 | continue 433 | 434 | # Legend label only when plotting 1st line 435 | if masterax and not ind.plotinfo.plotlinelabels: 436 | label = indlabel * (not toskip) or '_nolegend' 437 | else: 438 | label = (indlabel + '\n') * (not toskip) 439 | label += lineplotinfo._get('_name', '') or linealias 440 | 441 | toskip -= 1 # one line less until legend can be added 442 | 443 | # plot data 444 | lplot = line.plotrange(self.pinf.xstart, self.pinf.xend) 445 | 446 | # Global and generic for indicator 447 | if self.pinf.sch.linevalues and ind.plotinfo.plotlinevalues: 448 | plotlinevalue = lineplotinfo._get('_plotvalue', True) 449 | if plotlinevalue and not math.isnan(lplot[-1]): 450 | label += ' %.2f' % lplot[-1] 451 | 452 | plotkwargs = dict() 453 | linekwargs = lineplotinfo._getkwargs(skip_=True) 454 | 455 | if linekwargs.get('color', None) is None: 456 | if not lineplotinfo._get('_samecolor', False): 457 | self.pinf.nextcolor(ax) 458 | plotkwargs['color'] = self.pinf.color(ax) 459 | 460 | plotkwargs.update(dict(aa=True, label=label)) 461 | plotkwargs.update(**linekwargs) 462 | 463 | if ax in self.pinf.zorder: 464 | plotkwargs['zorder'] = self.pinf.zordernext(ax) 465 | 466 | pltmethod = getattr(ax, lineplotinfo._get('_method', 'plot')) 467 | 468 | xdata, lplotarray = self.pinf.xdata, lplot 469 | if lineplotinfo._get('_skipnan', False): 470 | # Get the full array and a mask to skipnan 471 | lplotarray = np.array(lplot) 472 | lplotmask = np.isfinite(lplotarray) 473 | 474 | # Get both the axis and the data masked 475 | lplotarray = lplotarray[lplotmask] 476 | xdata = np.array(xdata)[lplotmask] 477 | 478 | plottedline = pltmethod(xdata, lplotarray, **plotkwargs) 479 | try: 480 | plottedline = plottedline[0] 481 | except: 482 | # Possibly a container of artists (when plotting bars) 483 | pass 484 | 485 | self.pinf.zorder[ax] = plottedline.get_zorder() 486 | 487 | vtags = lineplotinfo._get('plotvaluetags', True) 488 | if self.pinf.sch.valuetags and vtags: 489 | linetag = lineplotinfo._get('_plotvaluetag', True) 490 | if linetag and not math.isnan(lplot[-1]): 491 | # line has valid values, plot a tag for the last value 492 | self.drawtag(ax, len(self.pinf.xreal), lplot[-1], 493 | facecolor=self.pinf.sch.locbgother, 494 | edgecolor=self.pinf.color(ax)) 495 | 496 | farts = (('_gt', operator.gt), ('_lt', operator.lt), ('', None),) 497 | for fcmp, fop in farts: 498 | fattr = '_fill' + fcmp 499 | fref, fcol = lineplotinfo._get(fattr, (None, None)) 500 | if fref is not None: 501 | y1 = np.array(lplot) 502 | if isinstance(fref, integer_types): 503 | y2 = np.full_like(y1, fref) 504 | else: # string, naming a line, nothing else is supported 505 | l2 = getattr(ind, fref) 506 | prl2 = l2.plotrange(self.pinf.xstart, self.pinf.xend) 507 | y2 = np.array(prl2) 508 | kwargs = dict() 509 | if fop is not None: 510 | kwargs['where'] = fop(y1, y2) 511 | 512 | falpha = self.pinf.sch.fillalpha 513 | if isinstance(fcol, (list, tuple)): 514 | fcol, falpha = fcol 515 | 516 | ax.fill_between(self.pinf.xdata, y1, y2, 517 | facecolor=fcol, 518 | alpha=falpha, 519 | interpolate=True, 520 | **kwargs) 521 | 522 | # plot subindicators that were created on self 523 | for subind in subinds: 524 | self.plotind(iref, subind, subinds=self.dplotsover[subind], 525 | masterax=ax) 526 | 527 | if not masterax: 528 | # adjust margin if requested ... general of particular 529 | ymargin = ind.plotinfo._get('plotymargin', 0.0) 530 | ymargin = max(ymargin, self.pinf.sch.yadjust) 531 | if ymargin: 532 | ax.margins(y=ymargin) 533 | 534 | # Set specific or generic ticks 535 | yticks = ind.plotinfo._get('plotyticks', []) 536 | if not yticks: 537 | yticks = ind.plotinfo._get('plotyhlines', []) 538 | 539 | if yticks: 540 | ax.set_yticks(yticks) 541 | else: 542 | locator = mticker.MaxNLocator(nbins=4, prune='both') 543 | ax.yaxis.set_major_locator(locator) 544 | 545 | # Set specific hlines if asked to 546 | hlines = ind.plotinfo._get('plothlines', []) 547 | if not hlines: 548 | hlines = ind.plotinfo._get('plotyhlines', []) 549 | for hline in hlines: 550 | ax.axhline(hline, color=self.pinf.sch.hlinescolor, 551 | ls=self.pinf.sch.hlinesstyle, 552 | lw=self.pinf.sch.hlineswidth) 553 | 554 | if self.pinf.sch.legendind and \ 555 | ind.plotinfo._get('plotlegend', True): 556 | 557 | handles, labels = ax.get_legend_handles_labels() 558 | # Ensure that we have something to show 559 | if labels: 560 | # location can come from the user 561 | loc = ind.plotinfo.legendloc or self.pinf.sch.legendindloc 562 | 563 | # Legend done here to ensure it includes all plots 564 | legend = ax.legend(loc=loc, 565 | numpoints=1, frameon=False, 566 | shadow=False, fancybox=False, 567 | prop=self.pinf.prop) 568 | 569 | # legend.set_title(indlabel, prop=self.pinf.prop) 570 | # hack: if title is set. legend has a Vbox for the labels 571 | # which has a default "center" set 572 | legend._legend_box.align = 'left' 573 | 574 | # plot subindicators on self with independent axis below 575 | for downind in downinds: 576 | self.plotind(iref, downind) 577 | 578 | def plotvolume(self, data, opens, highs, lows, closes, volumes, label): 579 | pmaster = data.plotinfo.plotmaster 580 | if pmaster is data: 581 | pmaster = None 582 | voloverlay = (self.pinf.sch.voloverlay and pmaster is None) 583 | 584 | # if sefl.pinf.sch.voloverlay: 585 | if voloverlay: 586 | rowspan = self.pinf.sch.rowsmajor 587 | else: 588 | rowspan = self.pinf.sch.rowsminor 589 | 590 | ax = self.newaxis(data.volume, rowspan=rowspan) 591 | 592 | # if self.pinf.sch.voloverlay: 593 | if voloverlay: 594 | volalpha = self.pinf.sch.voltrans 595 | else: 596 | volalpha = 1.0 597 | 598 | maxvol = volylim = max(volumes) 599 | if maxvol: 600 | 601 | # Plot the volume (no matter if as overlay or standalone) 602 | vollabel = label 603 | volplot, = plot_volume(ax, self.pinf.xdata, opens, closes, volumes, 604 | colorup=self.pinf.sch.volup, 605 | colordown=self.pinf.sch.voldown, 606 | alpha=volalpha, label=vollabel) 607 | 608 | nbins = 6 609 | prune = 'both' 610 | # if self.pinf.sch.voloverlay: 611 | if voloverlay: 612 | # store for a potential plot over it 613 | nbins = int(nbins / self.pinf.sch.volscaling) 614 | prune = None 615 | 616 | volylim /= self.pinf.sch.volscaling 617 | ax.set_ylim(0, volylim, auto=True) 618 | else: 619 | # plot a legend 620 | handles, labels = ax.get_legend_handles_labels() 621 | if handles: 622 | 623 | # location can come from the user 624 | loc = data.plotinfo.legendloc or self.pinf.sch.legendindloc 625 | 626 | # Legend done here to ensure it includes all plots 627 | legend = ax.legend(loc=loc, 628 | numpoints=1, frameon=False, 629 | shadow=False, fancybox=False, 630 | prop=self.pinf.prop) 631 | 632 | locator = mticker.MaxNLocator(nbins=nbins, prune=prune) 633 | ax.yaxis.set_major_locator(locator) 634 | ax.yaxis.set_major_formatter(MyVolFormatter(maxvol)) 635 | 636 | if not maxvol: 637 | ax.set_yticks([]) 638 | return None 639 | 640 | return volplot 641 | 642 | def plotdata(self, data, indicators): 643 | for ind in indicators: 644 | upinds = self.dplotsup[ind] 645 | for upind in upinds: 646 | self.plotind(data, upind, 647 | subinds=self.dplotsover[upind], 648 | upinds=self.dplotsup[upind], 649 | downinds=self.dplotsdown[upind]) 650 | 651 | opens = data.open.plotrange(self.pinf.xstart, self.pinf.xend) 652 | highs = data.high.plotrange(self.pinf.xstart, self.pinf.xend) 653 | lows = data.low.plotrange(self.pinf.xstart, self.pinf.xend) 654 | closes = data.close.plotrange(self.pinf.xstart, self.pinf.xend) 655 | volumes = data.volume.plotrange(self.pinf.xstart, self.pinf.xend) 656 | 657 | vollabel = 'Volume' 658 | pmaster = data.plotinfo.plotmaster 659 | if pmaster is data: 660 | pmaster = None 661 | 662 | datalabel = '' 663 | if hasattr(data, '_name') and data._name: 664 | datalabel += data._name 665 | 666 | voloverlay = (self.pinf.sch.voloverlay and pmaster is None) 667 | 668 | if not voloverlay: 669 | vollabel += ' ({})'.format(datalabel) 670 | 671 | # if self.pinf.sch.volume and self.pinf.sch.voloverlay: 672 | axdatamaster = None 673 | if self.pinf.sch.volume and voloverlay: 674 | volplot = self.plotvolume( 675 | data, opens, highs, lows, closes, volumes, vollabel) 676 | axvol = self.pinf.daxis[data.volume] 677 | ax = axvol.twinx() 678 | self.pinf.daxis[data] = ax 679 | self.pinf.vaxis.append(ax) 680 | else: 681 | if pmaster is None: 682 | ax = self.newaxis(data, rowspan=self.pinf.sch.rowsmajor) 683 | elif getattr(data.plotinfo, 'sameaxis', False): 684 | axdatamaster = self.pinf.daxis[pmaster] 685 | ax = axdatamaster 686 | else: 687 | axdatamaster = self.pinf.daxis[pmaster] 688 | ax = axdatamaster.twinx() 689 | self.pinf.vaxis.append(ax) 690 | 691 | if hasattr(data, '_compression') and \ 692 | hasattr(data, '_timeframe'): 693 | tfname = TimeFrame.getname(data._timeframe, data._compression) 694 | datalabel += ' (%d %s)' % (data._compression, tfname) 695 | 696 | plinevalues = getattr(data.plotinfo, 'plotlinevalues', True) 697 | if self.pinf.sch.style.startswith('line'): 698 | if self.pinf.sch.linevalues and plinevalues: 699 | datalabel += ' C:%.2f' % closes[-1] 700 | 701 | if axdatamaster is None: 702 | color = self.pinf.sch.loc 703 | else: 704 | self.pinf.nextcolor(axdatamaster) 705 | color = self.pinf.color(axdatamaster) 706 | 707 | plotted = plot_lineonclose( 708 | ax, self.pinf.xdata, closes, 709 | color=color, label=datalabel) 710 | else: 711 | if self.pinf.sch.linevalues and plinevalues: 712 | datalabel += ' O:%.2f H:%.2f L:%.2f C:%.2f' % \ 713 | (opens[-1], highs[-1], lows[-1], closes[-1]) 714 | if self.pinf.sch.style.startswith('candle'): 715 | plotted = plot_candlestick( 716 | ax, self.pinf.xdata, opens, highs, lows, closes, 717 | colorup=self.pinf.sch.barup, 718 | colordown=self.pinf.sch.bardown, 719 | label=datalabel, 720 | alpha=self.pinf.sch.baralpha, 721 | fillup=self.pinf.sch.barupfill, 722 | filldown=self.pinf.sch.bardownfill) 723 | 724 | elif self.pinf.sch.style.startswith('bar') or True: 725 | # final default option -- should be "else" 726 | plotted = plot_ohlc( 727 | ax, self.pinf.xdata, opens, highs, lows, closes, 728 | colorup=self.pinf.sch.barup, 729 | colordown=self.pinf.sch.bardown, 730 | label=datalabel) 731 | 732 | self.pinf.zorder[ax] = plotted[0].get_zorder() 733 | 734 | # Code to place a label at the right hand side with the last value 735 | vtags = data.plotinfo._get('plotvaluetags', True) 736 | if self.pinf.sch.valuetags and vtags: 737 | self.drawtag(ax, len(self.pinf.xreal), closes[-1], 738 | facecolor=self.pinf.sch.locbg, 739 | edgecolor=self.pinf.sch.loc) 740 | 741 | ax.yaxis.set_major_locator(mticker.MaxNLocator(prune='both')) 742 | # make sure "over" indicators do not change our scale 743 | if data.plotinfo._get('plotylimited', True): 744 | if axdatamaster is None: 745 | ax.set_ylim(ax.get_ylim()) 746 | 747 | if self.pinf.sch.volume: 748 | # if not self.pinf.sch.voloverlay: 749 | if not voloverlay: 750 | self.plotvolume( 751 | data, opens, highs, lows, closes, volumes, vollabel) 752 | else: 753 | # Prepare overlay scaling/pushup or manage own axis 754 | if self.pinf.sch.volpushup: 755 | # push up overlaid axis by lowering the bottom limit 756 | axbot, axtop = ax.get_ylim() 757 | axbot *= (1.0 - self.pinf.sch.volpushup) 758 | ax.set_ylim(axbot, axtop) 759 | 760 | for ind in indicators: 761 | self.plotind(data, ind, subinds=self.dplotsover[ind], masterax=ax) 762 | 763 | handles, labels = ax.get_legend_handles_labels() 764 | a = axdatamaster or ax 765 | if handles: 766 | # put data and volume legend entries in the 1st positions 767 | # because they are "collections" they are considered after Line2D 768 | # for the legend entries, which is not our desire 769 | # if self.pinf.sch.volume and self.pinf.sch.voloverlay: 770 | 771 | ai = self.pinf.legpos[a] 772 | if self.pinf.sch.volume and voloverlay: 773 | if volplot: 774 | # even if volume plot was requested, there may be no volume 775 | labels.insert(ai, vollabel) 776 | handles.insert(ai, volplot) 777 | 778 | didx = labels.index(datalabel) 779 | labels.insert(ai, labels.pop(didx)) 780 | handles.insert(ai, handles.pop(didx)) 781 | 782 | if axdatamaster is None: 783 | self.pinf.handles[ax] = handles 784 | self.pinf.labels[ax] = labels 785 | else: 786 | self.pinf.handles[axdatamaster] = handles 787 | self.pinf.labels[axdatamaster] = labels 788 | # self.pinf.handles[axdatamaster].extend(handles) 789 | # self.pinf.labels[axdatamaster].extend(labels) 790 | 791 | h = self.pinf.handles[a] 792 | l = self.pinf.labels[a] 793 | 794 | axlegend = a 795 | loc = data.plotinfo.legendloc or self.pinf.sch.legenddataloc 796 | legend = axlegend.legend(h, l, 797 | loc=loc, 798 | frameon=False, shadow=False, 799 | fancybox=False, prop=self.pinf.prop, 800 | numpoints=1, ncol=1) 801 | 802 | # hack: if title is set. legend has a Vbox for the labels 803 | # which has a default "center" set 804 | legend._legend_box.align = 'left' 805 | 806 | for ind in indicators: 807 | downinds = self.dplotsdown[ind] 808 | for downind in downinds: 809 | self.plotind(data, downind, 810 | subinds=self.dplotsover[downind], 811 | upinds=self.dplotsup[downind], 812 | downinds=self.dplotsdown[downind]) 813 | 814 | self.pinf.legpos[a] = len(self.pinf.handles[a]) 815 | 816 | if data.plotinfo._get('plotlog', False): 817 | a = axdatamaster or ax 818 | a.set_yscale('log') 819 | 820 | def show(self): 821 | self.mpyplot.show() 822 | 823 | def savefig(self, fig, filename, width=16, height=9, dpi=300, tight=True): 824 | fig.set_size_inches(width, height) 825 | bbox_inches = 'tight' * tight or None 826 | fig.savefig(filename, dpi=dpi, bbox_inches=bbox_inches) 827 | 828 | def sortdataindicators(self, strategy): 829 | # These lists/dictionaries hold the subplots that go above each data 830 | self.dplotstop = list() 831 | self.dplotsup = collections.defaultdict(list) 832 | self.dplotsdown = collections.defaultdict(list) 833 | self.dplotsover = collections.defaultdict(list) 834 | 835 | # Sort observers in the different lists/dictionaries 836 | for x in strategy.getobservers(): 837 | if not x.plotinfo.plot or x.plotinfo.plotskip: 838 | continue 839 | 840 | if x.plotinfo.subplot: 841 | self.dplotstop.append(x) 842 | else: 843 | key = getattr(x._clock, 'owner', x._clock) 844 | self.dplotsover[key].append(x) 845 | 846 | # Sort indicators in the different lists/dictionaries 847 | for x in strategy.getindicators(): 848 | if not hasattr(x, 'plotinfo'): 849 | # no plotting support - so far LineSingle derived classes 850 | continue 851 | 852 | if not x.plotinfo.plot or x.plotinfo.plotskip: 853 | continue 854 | 855 | x._plotinit() # will be plotted ... call its init function 856 | 857 | # support LineSeriesStub which has "owner" to point to the data 858 | key = getattr(x._clock, 'owner', x._clock) 859 | if key is strategy: # a LinesCoupler 860 | key = strategy.data 861 | 862 | if getattr(x.plotinfo, 'plotforce', False): 863 | if key not in strategy.datas: 864 | datas = strategy.datas 865 | while True: 866 | if key not in strategy.datas: 867 | key = key._clock 868 | else: 869 | break 870 | 871 | xpmaster = x.plotinfo.plotmaster 872 | if xpmaster is x: 873 | xpmaster = None 874 | if xpmaster is not None: 875 | key = xpmaster 876 | 877 | if x.plotinfo.subplot and xpmaster is None: 878 | if x.plotinfo.plotabove: 879 | self.dplotsup[key].append(x) 880 | else: 881 | self.dplotsdown[key].append(x) 882 | else: 883 | self.dplotsover[key].append(x) 884 | 885 | 886 | Plot = Plot_OldSync 887 | --------------------------------------------------------------------------------