├── results └── .gitkeep ├── tests ├── __init__.py ├── data │ ├── __init__.py │ └── test_data.py ├── experiments │ ├── __init__.py │ └── test_simple_experiment.py ├── test_indicators.py └── test_filters.py ├── ta_scanner ├── __init__.py ├── data │ ├── __init__.py │ ├── base_connector.py │ ├── csv_file_fetcher.py │ ├── constants.py │ ├── ib.py │ └── data.py ├── experiements │ ├── __init__.py │ └── simple.py ├── experiments │ ├── __init__.py │ └── simple_experiment.py ├── version.py ├── signals.py ├── reports.py ├── models.py ├── filters.py └── indicators.py ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md └── workflows │ └── conda-run-tests.yml ├── lab ├── csv_file_load.py ├── query_futures_data.py ├── fetch_futures_data.py ├── experiment_futures.py └── TradingDay.ipynb ├── .vscode └── settings.json ├── RELEASE_NOTES.md ├── docker-compose.yml ├── Makefile ├── requirements.dev ├── environment.yml ├── examples ├── db_stocks.py ├── db_futures.py ├── moving_average_crossover_stocks.py ├── moving_average_crossover_futures.py ├── moving_average_crossover_range_stocks.py ├── moving_average_crossover_range_futures.py └── combine_indcators.py ├── .gitignore ├── LICENSE ├── setup.py └── README.md /results/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ta_scanner/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/data/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ta_scanner/data/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/experiments/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ta_scanner/experiements/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ta_scanner/experiments/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ta_scanner/version.py: -------------------------------------------------------------------------------- 1 | VERSION = "0.0.1" 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | changes include: 2 | - 3 | -------------------------------------------------------------------------------- /ta_scanner/signals.py: -------------------------------------------------------------------------------- 1 | class Signal: 2 | def __init__(self): 3 | pass 4 | -------------------------------------------------------------------------------- /ta_scanner/data/base_connector.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | # python3 4 | class DataFetcherBase(object, metaclass=ABCMeta): 5 | pass 6 | -------------------------------------------------------------------------------- /tests/experiments/test_simple_experiment.py: -------------------------------------------------------------------------------- 1 | from ta_scanner.experiments.simple_experiment import SimpleExperiment 2 | 3 | 4 | def test_x(): 5 | assert SimpleExperiment.x() == "x" 6 | -------------------------------------------------------------------------------- /lab/csv_file_load.py: -------------------------------------------------------------------------------- 1 | from ta_scanner.data.data import aggregate_bars 2 | from ta_scanner.data.csv_file_fetcher import CsvFileFetcher 3 | 4 | data = CsvFileFetcher("example.csv") 5 | df = data.request_instrument() 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "ECBOT", 4 | "GLOBEX", 5 | "NYMEX", 6 | "groupby", 7 | "insync", 8 | "iterrows", 9 | "sfilter", 10 | "strftime" 11 | ] 12 | } -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | ## (in progress) Version 0.1 2 | - Create Indicator objects 3 | - Create Filter objects 4 | - Pull live data from IB (and optionally cache) 5 | - Create ranges of param permutations and range of PnL 6 | 7 | ## Version 0.0.1 8 | - started 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Expected Behavior 2 | 3 | 4 | ## Actual Behavior 5 | 6 | 7 | ## Steps to Reproduce the Problem 8 | 9 | 1. 10 | 1. 11 | 1. 12 | 13 | ## Specifications 14 | 15 | - Version: 16 | - Platform: 17 | - Subsystem: 18 | 19 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.0' 2 | 3 | services: 4 | pg: 5 | image: postgres:12.2 6 | restart: always 7 | ports: 8 | - 65432:5432 9 | volumes: 10 | - ..:/db_data 11 | environment: 12 | - POSTGRES_USER=ta_scanner 13 | - POSTGRES_PASSWORD=ta_scanner 14 | 15 | volumes: 16 | db_data: -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CONDA_ENV ?= ta_scanner 2 | 3 | test: 4 | @pytest -s . 5 | 6 | release: 7 | @python setup.py sdist 8 | @twine upload dist/* 9 | 10 | example: 11 | @python examples/moving_average_crossover.py 12 | 13 | 14 | env.create: 15 | @conda create -y -n ${CONDA_ENV} python=3.7 16 | 17 | env.update: 18 | @conda env update -n ${CONDA_ENV} -f environment.yml 19 | 20 | -------------------------------------------------------------------------------- /requirements.dev: -------------------------------------------------------------------------------- 1 | # Keep these development dependencies out of the requirements.txt & environment.yml files; 2 | # conda cannot install some of these development libs so use pip to install 3 | # them into a conda env after conda installs everything else. 4 | 5 | # dev utils 6 | ipdb 7 | ipython 8 | 9 | # docs 10 | sphinx 11 | 12 | # tests 13 | codecov 14 | pytest 15 | pytest-benchmark 16 | pytest-cov 17 | pytest-datadir 18 | pytest-mock 19 | pytest-voluptuous 20 | 21 | # static analysis and style 22 | black 23 | pylint 24 | 25 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: ta_scanner 2 | 3 | channels: 4 | - default 5 | 6 | dependencies: 7 | - python=3.7 8 | - click 9 | - sqlalchemy 10 | - pandas >= 1.0.0 11 | - psycopg2 12 | - numpy 13 | - requests 14 | - pytz 15 | - matplotlib 16 | # test resources 17 | - pytest 18 | - pip 19 | - pip: 20 | - twine 21 | - ipdb 22 | - TA-Lib 23 | - loguru 24 | - ib_insync 25 | - trading_calendars 26 | - "--editable ." 27 | # testing resources -------------------------------------------------------------------------------- /examples/db_stocks.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from loguru import logger 3 | from ta_scanner.data.data import load_and_cache 4 | from ta_scanner.data.ib import IbDataFetcher 5 | 6 | ib_data_fetcher = IbDataFetcher() 7 | 8 | symbols = ["SPY", "QQQ", "AAPL"] 9 | 10 | for symbol in symbols: 11 | df = load_and_cache( 12 | symbol, 13 | ib_data_fetcher, 14 | start_date=date(2020, 6, 1), 15 | end_date=date(2020, 6, 4), 16 | use_rth=False, 17 | groupby_minutes=15, 18 | ) 19 | logger.info(f"{symbol} - {len(df.index)}") 20 | -------------------------------------------------------------------------------- /ta_scanner/reports.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from typing import Tuple 3 | 4 | 5 | class BasicReport: 6 | def __init__(self): 7 | pass 8 | 9 | def analyze(self, df, field_name) -> Tuple[np.float64, int, np.float64, np.float64]: 10 | trades = df.query(f"0 < {field_name} or {field_name} < 0") 11 | 12 | trades.to_csv("trades.csv") 13 | 14 | pnl = trades[field_name].sum() 15 | count = trades[field_name].count() 16 | average = np.average(trades[field_name]) 17 | median = np.median(trades[field_name]) 18 | 19 | return pnl, count, average, median 20 | -------------------------------------------------------------------------------- /tests/data/test_data.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from ta_scanner.data.data import ( 3 | __gen_values, 4 | __gen_cols, 5 | db_insert_df_conflict_on_do_nothing, 6 | ) 7 | 8 | 9 | def fake_df_ab(): 10 | data = {"a": [1, 2, 3], "b": [11, 22, 33]} 11 | df = pd.DataFrame(data, columns=["a", "b"]) 12 | return df 13 | 14 | 15 | def test_gen_values(): 16 | df = fake_df_ab() 17 | expected_values = [("1", "11"), ("2", "22"), ("3", "33")] 18 | assert __gen_values(df) == expected_values 19 | 20 | 21 | def test_gen_cols(): 22 | df = fake_df_ab() 23 | expected_values = ["a", "b"] 24 | assert __gen_cols(df) == expected_values 25 | -------------------------------------------------------------------------------- /lab/query_futures_data.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from loguru import logger 3 | import sys 4 | 5 | from ta_scanner.data.data import db_data_fetch_between, aggregate_bars 6 | from ta_scanner.models import gen_engine 7 | 8 | 9 | symbol = "/MES" 10 | sd = datetime.date(2020, 9, 18) 11 | ed = sd 12 | 13 | engine = gen_engine() 14 | groupby_minutes = 1 15 | 16 | 17 | def query_data(engine, symbol, sd, ed, groupby_minutes): 18 | df = db_data_fetch_between(engine, symbol, sd, ed) 19 | df.set_index("ts", inplace=True) 20 | df = aggregate_bars(df, groupby_minutes=groupby_minutes) 21 | df["ts"] = df.index 22 | return df 23 | 24 | 25 | df = query_data(engine, symbol, sd, ed, groupby_minutes) -------------------------------------------------------------------------------- /lab/fetch_futures_data.py: -------------------------------------------------------------------------------- 1 | from loguru import logger 2 | from ta_scanner.data.data import load_and_cache 3 | from ta_scanner.data.ib import IbDataFetcher 4 | import datetime 5 | 6 | ib_data_fetcher = IbDataFetcher() 7 | 8 | # symbols = ["/MES", "/MNQ", "/MGC"] 9 | symbols = ["/MES"] 10 | 11 | get_last_n_days = 1 12 | get_last_n_days = 5 13 | 14 | sd = datetime.date.today() - datetime.timedelta(days=get_last_n_days) 15 | ed = datetime.date.today() - datetime.timedelta(days=1) 16 | 17 | for symbol in symbols: 18 | params = dict( 19 | start_date=sd, 20 | end_date=ed, 21 | use_rth=False, 22 | groupby_minutes=1, 23 | ) 24 | df = load_and_cache(symbol, ib_data_fetcher, **params) 25 | 26 | logger.info("Done") 27 | -------------------------------------------------------------------------------- /examples/db_futures.py: -------------------------------------------------------------------------------- 1 | from loguru import logger 2 | from ta_scanner.data.data import load_and_cache 3 | from ta_scanner.data.ib import IbDataFetcher 4 | import datetime 5 | 6 | ib_data_fetcher = IbDataFetcher() 7 | 8 | symbol = "/MES" 9 | sd = datetime.date(2020, 9, 2) 10 | ed = datetime.date(2020, 9, 10) 11 | params = dict( 12 | start_date=sd, 13 | end_date=ed, 14 | use_rth=False, 15 | groupby_minutes=1, 16 | ) 17 | 18 | df = load_and_cache(symbol, ib_data_fetcher, **params) 19 | logger.info(f"{symbol} - All hours / 1min bars - {len(df.index)}") 20 | 21 | params["use_rth"] = True 22 | df = load_and_cache(symbol, ib_data_fetcher, **params) 23 | logger.info(f"{symbol} - Only RTH / 1min bars - {len(df.index)}") 24 | 25 | params["use_rth"] = False 26 | params["groupby_minutes"] = 12 27 | df = load_and_cache(symbol, ib_data_fetcher, **params) 28 | logger.info(f"{symbol} - All hours / 12min bars - {len(df.index)}") 29 | logger.info(f"\n{df.head(10)}") 30 | -------------------------------------------------------------------------------- /tests/test_indicators.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | from typing import Any, Dict 4 | 5 | from ta_scanner.indicators import ( 6 | IndicatorSmaCrossover, 7 | IndicatorParams, 8 | IndicatorException, 9 | ) 10 | 11 | 12 | def gen_df_zeros(field_name="some_field_name"): 13 | return pd.DataFrame(0, index=[1, 2, 3], columns=[field_name]) 14 | 15 | 16 | def test_abstract_methods_present(): 17 | field_name, params = "field_name", [] 18 | IndicatorSmaCrossover(field_name=field_name, params=params) 19 | 20 | 21 | def test_ensure_required_filter_options(): 22 | field_name = "fake_some_name" 23 | fake_df = gen_df_zeros(field_name) 24 | 25 | params = { 26 | IndicatorParams.fast_sma: 20, 27 | # IndicatorParams.slow_sma: 50, # intentionally missing param 28 | } 29 | 30 | sma_crossover = IndicatorSmaCrossover(field_name=field_name, params=params) 31 | 32 | with pytest.raises(IndicatorException) as e: 33 | sma_crossover.apply(fake_df) 34 | 35 | expected_message = "IndicatorSmaCrossover requires key = IndicatorParams.slow_sma" 36 | assert expected_message == str(e.value) 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # Installer logs 29 | pip-log.txt 30 | pip-delete-this-directory.txt 31 | 32 | # Unit test / coverage reports 33 | htmlcov/ 34 | .tox/ 35 | .coverage 36 | .coverage.* 37 | .cache 38 | nosetests.xml 39 | coverage.xml 40 | *.cover 41 | .hypothesis/ 42 | .pytest_cache/ 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | local_settings.py 51 | db.sqlite3 52 | 53 | # Flask stuff: 54 | instance/ 55 | .webassets-cache 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | 63 | # Jupyter Notebook 64 | .ipynb_checkpoints 65 | 66 | # pyenv 67 | .python-version 68 | 69 | # celery beat schedule file 70 | celerybeat-schedule 71 | 72 | # SageMath parsed files 73 | *.sage.py 74 | 75 | # Environments 76 | .env 77 | .venv 78 | env/ 79 | venv/ 80 | ENV/ 81 | env.bak/ 82 | venv.bak/ 83 | 84 | # mkdocs documentation 85 | /site 86 | 87 | # mypy 88 | .mypy_cache/ 89 | 90 | *.egg-info 91 | *.csv 92 | *.txt 93 | 94 | results/ 95 | -------------------------------------------------------------------------------- /ta_scanner/models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.ext.declarative import declarative_base 2 | from sqlalchemy import ( 3 | create_engine, 4 | Column, 5 | Integer, 6 | Numeric, 7 | String, 8 | DateTime, 9 | Index, 10 | Boolean, 11 | ) 12 | 13 | 14 | Base = declarative_base() 15 | 16 | 17 | NUMERIC_OPTIONS = dict(precision=8, scale=2, decimal_return_scale=None, asdecimal=True) 18 | 19 | 20 | def gen_engine(): 21 | connection_string: str = ( 22 | "postgresql://ta_scanner:ta_scanner@localhost:65432/ta_scanner" 23 | ) 24 | engine = create_engine(connection_string, convert_unicode=True) 25 | return engine 26 | 27 | 28 | def init_db(): 29 | engine = gen_engine() 30 | Base.metadata.create_all(bind=engine) 31 | 32 | 33 | class Quote(Base): 34 | __tablename__ = "quote" 35 | 36 | id = Column(Integer, primary_key=True) 37 | ts = Column(DateTime(timezone=True), index=True) 38 | symbol = Column(String(10)) 39 | open = Column(Numeric(**NUMERIC_OPTIONS)) 40 | close = Column(Numeric(**NUMERIC_OPTIONS)) 41 | high = Column(Numeric(**NUMERIC_OPTIONS)) 42 | low = Column(Numeric(**NUMERIC_OPTIONS)) 43 | average = Column(Numeric(**NUMERIC_OPTIONS)) 44 | volume = Column(Integer) 45 | bar_count = Column(Integer) 46 | rth = Column(Boolean) 47 | 48 | __table_args__ = (Index("ix_quote_symbol_ts", symbol, ts, unique=True),) 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020, Weston Platter 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /ta_scanner/data/csv_file_fetcher.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from loguru import logger 3 | 4 | import datetime 5 | from trading_calendars import get_calendar, TradingCalendar 6 | from typing import Optional, Dict, Any, List, Tuple, Optional 7 | 8 | from ta_scanner.data.base_connector import DataFetcherBase 9 | from ta_scanner.data.constants import ( 10 | TimezoneNames, 11 | WhatToShow, 12 | Exchange, 13 | Calendar, 14 | Currency, 15 | ) 16 | 17 | 18 | class CsvFileFetcher(DataFetcherBase): 19 | def __init__(self, file_path: str): 20 | self.file_path = file_path 21 | self.df = None 22 | self._load_data_from_file() 23 | 24 | def _load_data_from_file(self): 25 | df = pd.read_csv(self.file_path) 26 | self.df = self._prepare_columns(df) 27 | 28 | def _prepare_columns(self, ddf): 29 | rename_columns = { 30 | "Date": "date", 31 | " Time": "time", 32 | " Open": "open", 33 | " High": "high", 34 | " Low": "low", 35 | " Last": "close", 36 | " Volume": "volume", 37 | " Bid Volume": "bid_volume", 38 | " Ask Volume": "ask_volume", 39 | " Close": "cumulative_delta_bars", 40 | } 41 | ddf = ddf.rename(columns=rename_columns) 42 | ddf["ts"] = pd.to_datetime(ddf["date"].map(str) + ddf["time"].map(str)) 43 | ddf.set_index("ts", drop=False, inplace=True) 44 | ddf = ddf.tz_localize("US/Mountain") 45 | return ddf 46 | 47 | def request_instrument(self): 48 | return self.df 49 | -------------------------------------------------------------------------------- /tests/test_filters.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | from typing import Any, Dict 4 | 5 | from ta_scanner.filters import FilterCumsum, FilterOptions, FilterException 6 | 7 | 8 | def gen_df_zeros(field_name="some_field_name"): 9 | return pd.DataFrame(0, index=[1, 2, 3], columns=[field_name]) 10 | 11 | 12 | def test_abstract_methods_present(): 13 | field_name, result_field_name, params = "some_field_name", "result", [] 14 | FilterCumsum( 15 | field_name=field_name, result_field_name=result_field_name, params=params 16 | ) 17 | 18 | 19 | def test_required_filter_options(): 20 | field_name = "indicator_name" 21 | result_field_name = f"{field_name}_png" 22 | df = gen_df_zeros(field_name) 23 | 24 | params: Dict[FilterOptions, Any] = { 25 | FilterOptions.win_points: 20.0, 26 | FilterOptions.loss_points: 10.0, 27 | # FilterOptions.threshold_intervals: 50, 28 | } 29 | 30 | filter_cumsum = FilterCumsum( 31 | field_name=field_name, result_field_name=result_field_name, params=params 32 | ) 33 | 34 | with pytest.raises(FilterException) as execinfo: 35 | filter_cumsum.apply(df) 36 | assert "FilterOptions.loss_points" in str(excinfo.value) 37 | 38 | params: Dict[FilterOptions, Any] = { 39 | FilterOptions.win_points: 20.0, 40 | FilterOptions.loss_points: 10.0, 41 | FilterOptions.threshold_intervals: 50, 42 | } 43 | 44 | filter_cumsum = FilterCumsum( 45 | field_name=field_name, result_field_name=result_field_name, params=params 46 | ) 47 | 48 | filter_cumsum.apply(df) 49 | -------------------------------------------------------------------------------- /examples/moving_average_crossover_stocks.py: -------------------------------------------------------------------------------- 1 | from loguru import logger 2 | 3 | from ta_scanner.data.data import load_and_cache 4 | from ta_scanner.data.ib import IbDataFetcher 5 | from ta_scanner.indicators import IndicatorSmaCrossover, IndicatorParams 6 | from ta_scanner.signals import Signal 7 | from ta_scanner.filters import FilterCumsum, FilterOptions, FilterNames 8 | from ta_scanner.reports import BasicReport 9 | 10 | 11 | # get SPY data 12 | ib_data_fetcher = IbDataFetcher() 13 | df = load_and_cache( 14 | "SPY", 15 | ib_data_fetcher, 16 | start_date=date(2020, 7, 1), 17 | end_date=date(2020, 7, 20), 18 | use_rth=True, 19 | ) 20 | 21 | # store signals in this field 22 | field_name = "moving_avg_cross" 23 | result_field_name = f"{field_name}_pnl" 24 | 25 | indicator_sma_cross = IndicatorSmaCrossover( 26 | field_name=field_name, 27 | params={ 28 | IndicatorParams.fast_sma: 20, 29 | IndicatorParams.slow_sma: 50, 30 | }, 31 | ) 32 | 33 | # apply indicator to generate signals 34 | indicator_sma_cross.apply(df) 35 | 36 | # initialize filter 37 | sfilter = FilterCumsum( 38 | field_name=field_name, 39 | result_field_name=result_field_name, 40 | params={ 41 | FilterOptions.win_points: 10, 42 | FilterOptions.loss_points: 5, 43 | FilterOptions.threshold_intervals: 30, 44 | }, 45 | ) 46 | 47 | # generate results 48 | results = sfilter.apply(df) 49 | 50 | # analyze results 51 | basic_report = BasicReport() 52 | pnl = basic_report.analyze(df, FilterNames.filter_cumsum.value) 53 | 54 | logger.info("------------------------") 55 | 56 | logger.info(f"Final PnL = {pnl}") 57 | -------------------------------------------------------------------------------- /ta_scanner/experiements/simple.py: -------------------------------------------------------------------------------- 1 | class Simple: 2 | def __init__( 3 | self, df_train, df_test, indicator, indicator_params, sfilter, sfilter_params 4 | ): 5 | self.df_train = df_train 6 | self.df_test = df_test 7 | self.indicator = indicator 8 | self.indicator_params = indicator_params 9 | self.sfilter = sfilter 10 | self.sfilter_params = sfilter_params 11 | 12 | # the goal here is to 13 | # - apply range of indicators configs to the train data 14 | # - pick a couple of the bottom, middle, and top results 15 | # - apply those to the test data 16 | # - analyze how well they translate 17 | 18 | 19 | # indicator_sma_cross = IndicatorSmaCrossover() 20 | 21 | # # store signals in this field 22 | # field_name = "moving_avg_cross" 23 | 24 | # # Moving Average Crossover, 20 vs 50 25 | # indicator_params = { 26 | # IndicatorParams.fast_sma: 30, 27 | # IndicatorParams.slow_sma: 60, 28 | # } 29 | # # apply indicator to generate signals 30 | # indicator_sma_cross.apply(df, field_name, indicator_params) 31 | 32 | # # initialize filter 33 | # sfilter = FilterCumsum() 34 | 35 | # filter_options = { 36 | # FilterOptions.win_points: 10, 37 | # FilterOptions.loss_points: 3, 38 | # FilterOptions.threshold_intervals: 20, 39 | # } 40 | 41 | # # generate results 42 | # results = sfilter.apply(df, field_name, filter_options) 43 | 44 | # # analyze results 45 | # basic_report = BasicReport() 46 | # pnl, count, average, median = basic_report.analyze(df, FilterNames.filter_cumsum.value) 47 | 48 | # logger.info("------------------------") 49 | 50 | # logger.info(f"Final PnL = {pnl}") 51 | -------------------------------------------------------------------------------- /.github/workflows/conda-run-tests.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - "issue-**" 7 | pull_request: 8 | branches: 9 | - "dev" 10 | 11 | jobs: 12 | example-1: 13 | name: Ex1 (${{ matrix.python-version }}, ${{ matrix.os }}) 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: ["ubuntu-latest"] #, "macos-latest", "windows-latest"] 19 | python-version: ["3.7"] #, "2.7"] 20 | 21 | steps: 22 | - name: Install xmllint 23 | run: | 24 | sudo apt install build-essential wget -y 25 | wget https://artiya4u.keybase.pub/TA-lib/ta-lib-0.4.0-src.tar.gz 26 | tar -xvf ta-lib-0.4.0-src.tar.gz 27 | cd ta-lib/ 28 | ./configure --prefix=/usr 29 | make 30 | sudo make install 31 | - uses: actions/checkout@v2 32 | - name: Cache Conda 33 | uses: actions/cache@v1 34 | with: 35 | path: /usr/share/miniconda/envs/ta_scanner 36 | key: ${{ runner.os }}-conda-${{ hashFiles('environment.yml') }} 37 | restore-keys: | 38 | ${{ runner.os }}-conda- 39 | - name: Setup Conda dependencies 40 | uses: goanpeca/setup-miniconda@v1 41 | with: 42 | activate-environment: ta_scanner 43 | environment-file: environment.yml 44 | python-version: "${{ matrix.python-version }}" 45 | auto-activate-base: false 46 | - name: Conda info 47 | shell: bash -l {0} 48 | run: | 49 | conda info 50 | conda list 51 | - name: Run tests 52 | shell: bash -l {0} 53 | run: | 54 | pytest 55 | -------------------------------------------------------------------------------- /examples/moving_average_crossover_futures.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, date 2 | from loguru import logger 3 | 4 | from ta_scanner.data.data import load_and_cache 5 | from ta_scanner.data.ib import IbDataFetcher 6 | from ta_scanner.indicators import IndicatorSmaCrossover, IndicatorParams 7 | from ta_scanner.signals import Signal 8 | from ta_scanner.filters import FilterCumsum, FilterOptions, FilterNames 9 | from ta_scanner.reports import BasicReport 10 | 11 | 12 | ib_data_fetcher = IbDataFetcher() 13 | df = load_and_cache( 14 | "/MES", 15 | ib_data_fetcher, 16 | start_date=date(2020, 7, 10), 17 | end_date=date(2020, 7, 20), 18 | use_rth=True, 19 | ) 20 | 21 | # store signals in this field 22 | field_name = "moving_avg_cross" 23 | 24 | # Moving Average Crossover, 20 vs 50 25 | indicator_params = { 26 | IndicatorParams.fast_sma: 30, 27 | IndicatorParams.slow_sma: 60, 28 | } 29 | 30 | # init 31 | indicator_sma_cross = IndicatorSmaCrossover( 32 | field_name=field_name, params=indicator_params 33 | ) 34 | 35 | # apply indicator to generate signals 36 | indicator_sma_cross.apply(df) 37 | 38 | 39 | filter_options = { 40 | FilterOptions.win_points: 10, 41 | FilterOptions.loss_points: 3, 42 | FilterOptions.threshold_intervals: 20, 43 | } 44 | # initialize filter 45 | result_field_name = f"{field_name}_pnl" 46 | sfilter = FilterCumsum( 47 | field_name=field_name, result_field_name=result_field_name, params=filter_options 48 | ) 49 | 50 | # generate results 51 | results = sfilter.apply(df) 52 | 53 | # analyze results 54 | basic_report = BasicReport() 55 | pnl, count, average, median = basic_report.analyze(df, FilterNames.filter_cumsum.value) 56 | 57 | logger.info("------------------------") 58 | 59 | logger.info(f"Final PnL = {pnl}") 60 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from setuptools import setup 3 | from setuptools.command.test import test as TestCommand 4 | 5 | 6 | class PyTest(TestCommand): 7 | user_options = [("pytest-args=", "a", "Arguments to pass into pytest")] 8 | 9 | def initialize_options(self): 10 | TestCommand.initialize_options(self) 11 | self.pytest_args = "-n auto" 12 | 13 | def run_tests(self): 14 | import shlex 15 | import pytest 16 | 17 | errno = pytest.main(shlex.split(self.pytest_args)) 18 | sys.exit(errno) 19 | 20 | 21 | # version_contents = {} 22 | # with open("fast_arrow/version.py", "r", encoding="utf-8") as f: 23 | # exec(f.read(), version_contents) 24 | 25 | 26 | with open("README.md", "r") as f: 27 | long_description = f.read() 28 | 29 | 30 | # deps = [ 31 | # "datetime", 32 | # "deprecation", 33 | # "pathlib2", 34 | # "requests>=2.20.0", 35 | # "pandas>=0.23.2", 36 | # "numpy", 37 | # "yarl", 38 | # "urllib3>=1.24.2", 39 | # ] 40 | 41 | 42 | # test_deps = ["pipenv", "pytest", "pytest-cov", "detox", "flake8", "vcrpy"] 43 | 44 | 45 | setup( 46 | name="ta_scanner", 47 | version="0.0.1", 48 | description="Technical analysis scanner and bavhior matcher", 49 | long_description=long_description, 50 | long_description_content_type="text/markdown", 51 | author="Weston Platter", 52 | author_email="westonplatter+github@gmail.com", 53 | url="https://github.com/westonplatter/ta_scanner/", 54 | license="BSD-3-Clause", 55 | python_requires=">=3.5", 56 | packages=["ta_scanner"], 57 | cmdclass={"test": PyTest}, 58 | project_urls={ 59 | "Issue Tracker": "https://github.com/westonplatter/ta_scanner/issues", 60 | "Source Code": "https://github.com/westonplatter/ta_scanner", 61 | }, 62 | ) 63 | -------------------------------------------------------------------------------- /ta_scanner/experiments/simple_experiment.py: -------------------------------------------------------------------------------- 1 | from abc import ABC 2 | 3 | 4 | class BaseExperiment(ABC): 5 | @staticmethod 6 | def x() -> str: 7 | return "x" 8 | 9 | 10 | class SimpleExperiment(BaseExperiment): 11 | def __init__( 12 | self, df_train, df_test, indicator, indicator_params, sfilter, sfilter_params 13 | ): 14 | self.df_train = df_train 15 | self.df_test = df_test 16 | self.indicator = indicator 17 | self.indicator_params = indicator_params 18 | self.sfilter = sfilter 19 | self.sfilter_params = sfilter_params 20 | 21 | # the goal here is to 22 | # - apply range of indicators configs to the train data 23 | # - pick a couple of the bottom, middle, and top results 24 | # - apply those to the test data 25 | # - analyze how well they translate 26 | 27 | 28 | # indicator_sma_cross = IndicatorSmaCrossover() 29 | 30 | # # store signals in this field 31 | # field_name = "moving_avg_cross" 32 | 33 | # # Moving Average Crossover, 20 vs 50 34 | # indicator_params = { 35 | # IndicatorParams.fast_sma: 30, 36 | # IndicatorParams.slow_sma: 60, 37 | # } 38 | # # apply indicator to generate signals 39 | # indicator_sma_cross.apply(df, field_name, indicator_params) 40 | 41 | # # initialize filter 42 | # sfilter = FilterCumsum() 43 | 44 | # filter_options = { 45 | # FilterOptions.win_points: 10, 46 | # FilterOptions.loss_points: 3, 47 | # FilterOptions.threshold_intervals: 20, 48 | # } 49 | 50 | # # generate results 51 | # results = sfilter.apply(df, field_name, filter_options) 52 | 53 | # # analyze results 54 | # basic_report = BasicReport() 55 | # pnl, count, average, median = basic_report.analyze(df, FilterNames.filter_cumsum.value) 56 | 57 | # logger.info("------------------------") 58 | 59 | # logger.info(f"Final PnL = {pnl}") 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Technical Analaysis Scanner 2 | 3 | ## Goals 4 | 5 | This software attempts to provide a framework that does a combination of 6 | (1) scanning and, (2) backtesting to ask and answer such questions as, 7 | 8 | - which instruments have responded well to the 4 hr MACD(26, 9, 12) in the 9 | last quarter? 10 | 11 | - which stocks are getting close to their 20/50 Moving Average Crossover, and 12 | of those stocks, which have responded more than +/- 2.5% in the past? 13 | 14 | - which instruments in the last 2 weeks after trended out of their opening 15 | ranges? What is the 1 and 2 std dev band for each isntrument at 30 minute 16 | intervals? 17 | 18 | ## Features 19 | 20 | - [x] Pull data from IB 21 | - [ ] Apply various indicators against single or multiple instruments 22 | - [ ] Simple Moving Average Crossover 23 | - [ ] MACD Crossover 24 | 25 | ### Digging a little deeper 26 | 27 | Technical analysis sometimes works, and sometimes doesn't. The goal of this 28 | codebase is to provide means/methods for measuring a universe of instruments 29 | and determine which ones are behaving in line with various TA patterns 30 | 31 | It's intended to work differently than a traditional backtester (eg, a 32 | Quantopian and QuantConnect). From what I undestand about backtesting, the 33 | goal is to provide predetermined entry and exit rules, and measure the 34 | results for a single or multiple instruments. This software is didferent 35 | it that it intends to experiment with the entry and exit rules and see how 36 | those adjustments impact results. 37 | 38 | ## Structure 39 | 40 | Core Framework lives in this repo, and your secret sauce parameter/configs, 41 | research findings live in another one. 42 | 43 | This repo will feel like a mono repo to many, and focuses on 44 | 45 | - downloading market data (currently using IB API Gateway) 46 | - exposing market data 47 | - running multi-variate simulations 48 | - reporting results 49 | 50 | 51 | ## Releasing 52 | 53 | Pypi release commands, 54 | 55 | ``` 56 | python setup.py sdist bdist_wheel 57 | twine upload dist/* 58 | ``` 59 | -------------------------------------------------------------------------------- /examples/moving_average_crossover_range_stocks.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from loguru import logger 3 | import sys 4 | 5 | from ta_scanner.data.data import load_and_cache 6 | from ta_scanner.data.ib import IbDataFetcher 7 | from ta_scanner.indicators import IndicatorSmaCrossover, IndicatorParams 8 | from ta_scanner.signals import Signal 9 | from ta_scanner.filters import FilterCumsum, FilterOptions, FilterNames 10 | from ta_scanner.reports import BasicReport 11 | 12 | 13 | # mute the noisy data debug statements 14 | logger.remove() 15 | logger.add(sys.stderr, level="INFO") 16 | 17 | # get SPY data 18 | ib_data_fetcher = IbDataFetcher() 19 | df_original = load_and_cache( 20 | "SPY", 21 | ib_data_fetcher, 22 | start_date=date(2020, 7, 1), 23 | end_date=date(2020, 7, 20), 24 | use_rth=True, 25 | ) 26 | 27 | # store signals in this field 28 | field_name = "moving_avg_cross" 29 | result_field_name = f"{field_name}_pnl" 30 | 31 | 32 | def run_cross(fast_sma: int, slow_sma: int): 33 | df = df_original.copy() 34 | 35 | indicator_sma_cross = IndicatorSmaCrossover( 36 | field_name=field_name, 37 | params={ 38 | IndicatorParams.fast_sma: fast_sma, 39 | IndicatorParams.slow_sma: slow_sma, 40 | }, 41 | ) 42 | 43 | # apply indicator to generate signals 44 | indicator_sma_cross.apply(df) 45 | 46 | # initialize filter 47 | sfilter = FilterCumsum( 48 | field_name=field_name, 49 | result_field_name=result_field_name, 50 | params={ 51 | FilterOptions.win_points: 10, 52 | FilterOptions.loss_points: 5, 53 | FilterOptions.threshold_intervals: 30, 54 | }, 55 | ) 56 | 57 | # generate results 58 | results = sfilter.apply(df) 59 | 60 | # get aggregate pnl 61 | basic_report = BasicReport() 62 | pnl = basic_report.analyze(df, FilterNames.filter_cumsum.value) 63 | return pnl 64 | 65 | 66 | slow_sma = 50 67 | 68 | for fast_sma in range(2, slow_sma): 69 | final_pnl = run_cross(fast_sma, slow_sma) 70 | print(f"MA Crx {fast_sma}/{slow_sma}. Final PnL = {final_pnl}") 71 | -------------------------------------------------------------------------------- /examples/moving_average_crossover_range_futures.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from loguru import logger 3 | import sys 4 | 5 | from ta_scanner.data.data import load_and_cache, db_data_fetch_between, aggregate_bars 6 | from ta_scanner.data.ib import IbDataFetcher 7 | from ta_scanner.indicators import IndicatorSmaCrossover, IndicatorParams 8 | from ta_scanner.signals import Signal 9 | from ta_scanner.filters import FilterCumsum, FilterOptions, FilterNames 10 | from ta_scanner.reports import BasicReport 11 | from ta_scanner.models import gen_engine 12 | 13 | 14 | # mute the noisy data debug statements 15 | logger.remove() 16 | logger.add(sys.stderr, level="INFO") 17 | 18 | ib_data_fetcher = IbDataFetcher() 19 | 20 | symbol = "/MGC" 21 | 22 | df_original = load_and_cache( 23 | symbol, 24 | ib_data_fetcher, 25 | start_date=datetime.date(2020, 8, 1), 26 | end_date=datetime.date(2020, 8, 23), 27 | ) 28 | 29 | 30 | def query_data(engine, symbol, sd, ed, groupby_minutes): 31 | df = db_data_fetch_between(engine, symbol, sd, ed) 32 | df.set_index("ts", inplace=True) 33 | df = aggregate_bars(df, groupby_minutes=groupby_minutes) 34 | df["ts"] = df.index 35 | return df 36 | 37 | 38 | engine = gen_engine() 39 | sd, ed = datetime.date(2020, 8, 1), datetime.date(2020, 8, 23) 40 | interval = 1 41 | df_original = query_data(engine, symbol, sd, ed, interval) 42 | 43 | 44 | # store signals in this field 45 | field_name = "moving_avg_cross" 46 | result_field_name = f"{field_name}_pnl" 47 | 48 | 49 | def run_cross(fast_sma: int, slow_sma: int): 50 | df = df_original.copy() 51 | 52 | indicator_params = { 53 | IndicatorParams.fast_sma: fast_sma, 54 | IndicatorParams.slow_sma: slow_sma, 55 | } 56 | indicator = IndicatorSmaCrossover(field_name, indicator_params) 57 | indicator.apply(df) 58 | 59 | filter_options = { 60 | FilterOptions.win_points: 6, 61 | FilterOptions.loss_points: 4, 62 | FilterOptions.threshold_intervals: 30, 63 | } 64 | sfilter = FilterCumsum(field_name, result_field_name, filter_options) 65 | results = sfilter.apply(df) 66 | 67 | # get aggregate pnl 68 | basic_report = BasicReport() 69 | pnl, count, avg, median = basic_report.analyze(df, field_name) 70 | return pnl, count, avg, median 71 | 72 | 73 | slow_sma = 60 74 | 75 | results = [] 76 | 77 | for fast_sma in range(2, slow_sma): 78 | pnl, count, avg, median = run_cross(fast_sma, slow_sma) 79 | results.append([slow_sma, fast_sma, pnl, count, avg, median]) 80 | print(f"MA_Crx_{fast_sma}/{slow_sma}, {pnl}, {count}, {avg}, {median}") 81 | 82 | 83 | # write results to csv 84 | 85 | headers = ["slow_sma", "fast_sma", "pnl", "count", "avg", "median"] 86 | 87 | filename = f"results/MA_Crx_{symbol.replace('/', '')}.csv" 88 | 89 | with open(filename, "w") as f: 90 | import csv 91 | 92 | writer = csv.writer(f) 93 | writer.writerow(headers) 94 | for row in results: 95 | writer.writerow(row) 96 | -------------------------------------------------------------------------------- /ta_scanner/data/constants.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from trading_calendars import get_calendar, TradingCalendar 3 | from typing import Dict 4 | 5 | 6 | class TimezoneNames(Enum): 7 | US_EASTERN = "US/Eastern" 8 | US_CENTRAL = "US/Central" 9 | US_MOUNTAIN = "US/Mountain" 10 | US_PACIFIC = "US/Pacific" 11 | UTC = "UTC" 12 | 13 | 14 | class WhatToShow(Enum): 15 | TRADES = "TRADES" 16 | MIDPOINT = "MIDPOINT" 17 | BID = "BID" 18 | ASK = "ASK" 19 | BID_ASK = "BID_ASK" 20 | ADJUSTED_LAST = "ADJUSTED_LAST" 21 | HISTORICAL_VOLATILITY = "HISTORICAL_VOLATILITY" 22 | OPTION_IMPLIED_VOLATILITY = "OPTION_IMPLIED_VOLATILITY" 23 | REBATE_RATE = "REBATE_RATE" 24 | FEE_RATE = "FEE_RATE" 25 | YIELD_BID = "YIELD_BID" 26 | YIELD_ASK = "YIELD_ASK" 27 | YIELD_BID_ASK = "YIELD_BID_ASK" 28 | YIELD_LAST = "YIELD_LAST" 29 | 30 | 31 | class Exchange(Enum): 32 | SMART = "SMART" 33 | NYSE = "NYSE" 34 | GLOBEX = "GLOBEX" 35 | NYMEX = "NYMEX" 36 | ECBOT = "ECBOT" 37 | CBOE = "CBOE" 38 | ICE = "ICE" 39 | 40 | 41 | class Calendar(Enum): 42 | # https://github.com/quantopian/trading_calendars 43 | DEFAULT = "XNYS" # default to NYSE 44 | NYSE = "XNYS" 45 | CME = "CMES" 46 | CBOE = "XCBF" 47 | ICE = "IEPA" 48 | 49 | @staticmethod 50 | def futures_lookup_hash() -> Dict: 51 | return { 52 | Calendar.CME: [ 53 | # equities 54 | "/ES", 55 | "/MES", 56 | "/MNQ", 57 | "/NQ", 58 | "/MNQ", 59 | # metals 60 | "/GC", 61 | "/MGC", 62 | # energy 63 | "/CL", 64 | "/QM", 65 | # currencies 66 | "/M6A", 67 | "/M6B", 68 | "/M6E", 69 | # interest rates 70 | "/GE", 71 | "/ZN", 72 | "/ZN", 73 | "/ZT", 74 | # grains 75 | "/ZC", 76 | "/YC", 77 | "/ZS", 78 | "/YK", 79 | "/ZW", 80 | "/YW", 81 | ], 82 | Calendar.CBOE: [], 83 | Calendar.ICE: [], 84 | } 85 | 86 | @staticmethod 87 | def select_exchange_by_symbol(symbol: str): 88 | for k, v in Calendar.futures_lookup_hash().items(): 89 | if symbol in v: 90 | return k 91 | logger.warning(f"Did not find a calendar entry for symbol={symbol}") 92 | return Calendar.DEFAULT 93 | 94 | @staticmethod 95 | def init_by_symbol(symbol: str) -> TradingCalendar: 96 | if "/" in symbol: 97 | key = Calendar.select_exchange_by_symbol(symbol) 98 | name = key.value 99 | else: 100 | name = Calendar.NYSE.value 101 | return get_calendar(name) 102 | 103 | 104 | class Currency(Enum): 105 | USD = "USD" 106 | -------------------------------------------------------------------------------- /examples/combine_indcators.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from loguru import logger 3 | import sys 4 | 5 | from ta_scanner.data.data import load_and_cache, db_data_fetch_between, aggregate_bars 6 | from ta_scanner.data.ib import IbDataFetcher 7 | from ta_scanner.indicators import ( 8 | IndicatorSmaCrossover, 9 | IndicatorParams, 10 | CombinedBindary, 11 | ) 12 | from ta_scanner.signals import Signal 13 | from ta_scanner.filters import FilterCumsum, FilterOptions, FilterNames 14 | from ta_scanner.reports import BasicReport 15 | from ta_scanner.models import gen_engine 16 | 17 | # mute the noisy data debug statements 18 | # logger.remove() 19 | # logger.add(sys.stderr, level="INFO") 20 | 21 | symbol = "/MES" 22 | 23 | 24 | def query_data(engine, symbol, sd, ed, groupby_minutes): 25 | df = db_data_fetch_between(engine, symbol, sd, ed) 26 | df.set_index("ts", inplace=True) 27 | df = aggregate_bars(df, groupby_minutes=groupby_minutes) 28 | df["ts"] = df.index 29 | return df 30 | 31 | 32 | engine = gen_engine() 33 | sd, ed = datetime.date(2020, 8, 1), datetime.date(2020, 8, 23) 34 | interval = 1 35 | df = query_data(engine, symbol, sd, ed, interval) 36 | 37 | 38 | # short duration 39 | short_duration_ma_cross = "short_duration_ma_cross" 40 | short_duration_fast_sma = 30 41 | short_duration_slow_sma = 60 42 | 43 | # long duration 44 | multiplier = 3 45 | long_duration_ma_cross = "long_duration_ma_cross" 46 | long_duration_fast_sma = short_duration_fast_sma * multiplier 47 | long_duration_slow_sma = short_duration_slow_sma * multiplier 48 | 49 | # init and apply short duration crosses 50 | short_duration_indicator = IndicatorSmaCrossover( 51 | field_name=short_duration_ma_cross, 52 | params={ 53 | IndicatorParams.fast_sma: short_duration_fast_sma, 54 | IndicatorParams.slow_sma: short_duration_slow_sma, 55 | }, 56 | ) 57 | short_duration_indicator.apply(df) 58 | 59 | # init and apply long duration crosses 60 | long_duration_indicator = IndicatorSmaCrossover( 61 | field_name=long_duration_ma_cross, 62 | params={ 63 | IndicatorParams.fast_sma: long_duration_fast_sma, 64 | IndicatorParams.slow_sma: long_duration_slow_sma, 65 | }, 66 | ) 67 | long_duration_indicator.apply(df) 68 | 69 | # combine indicators 70 | composite_field_name = "composite" 71 | composite_indicator = CombinedBindary( 72 | field_name=composite_field_name, 73 | params={ 74 | IndicatorParams.field_names: [short_duration_ma_cross, long_duration_ma_cross] 75 | }, 76 | ) 77 | composite_indicator.apply(df) 78 | 79 | filter_options = { 80 | FilterOptions.win_points: 20, 81 | FilterOptions.loss_points: 6, 82 | FilterOptions.threshold_intervals: 20, 83 | } 84 | # initialize filter 85 | 86 | result_field_name = f"{composite_field_name}_pnl" 87 | sfilter = FilterCumsum( 88 | field_name=composite_field_name, 89 | result_field_name=result_field_name, 90 | params=filter_options, 91 | ) 92 | 93 | # generate results 94 | results = sfilter.apply(df, -1) 95 | 96 | # analyze results 97 | basic_report = BasicReport() 98 | pnl, count, average, median = basic_report.analyze(df, result_field_name) 99 | 100 | logger.info(f"Final PnL = {pnl}") 101 | -------------------------------------------------------------------------------- /ta_scanner/filters.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from enum import Enum 3 | import pandas as pd 4 | from loguru import logger 5 | from typing import Any, Dict, List, Optional, List 6 | 7 | 8 | class FilterOptions(Enum): 9 | win_points = "win_points" 10 | loss_points = "loss_points" 11 | threshold_intervals = "threshold_intervals" 12 | 13 | 14 | class FilterNames(Enum): 15 | filter_cumsum = "filter_cumsum" 16 | 17 | 18 | class FilterException(Exception): 19 | pass 20 | 21 | 22 | class BaseFitler(metaclass=abc.ABCMeta): 23 | def __init__( 24 | self, field_name: str, result_field_name: str, params: Dict[FilterOptions, Any] 25 | ): 26 | self.field_name = field_name 27 | self.result_field_name = result_field_name 28 | self.params = params 29 | 30 | def ensure_required_filter_options( 31 | self, expected: List[FilterOptions], actual: Dict[FilterOptions, Any] 32 | ): 33 | for fo_key in expected: 34 | if fo_key not in actual: 35 | raise FilterException(f"expected key = {fo_key}") 36 | 37 | @abc.abstractmethod 38 | def apply(self, df, field_name, filter_options): 39 | pass 40 | 41 | 42 | class FilterCumsum(BaseFitler): 43 | required_filter_options = [ 44 | FilterOptions.win_points, 45 | FilterOptions.loss_points, 46 | FilterOptions.threshold_intervals, 47 | ] 48 | 49 | def log_exit(self, action: str, diff, row): 50 | logger.debug(f"Action={action}. Ts={row.ts}. Diff={diff}. Close={row.close}") 51 | 52 | def log_entry(self, action, row): 53 | logger.debug(f"Action={action}. Ts={row.ts}. Close={row.close}") 54 | 55 | def apply(self, df: pd.DataFrame, inverse: int = 1): 56 | self.ensure_required_filter_options(self.required_filter_options, self.params) 57 | 58 | query_signals = f"{self.field_name} != 0" 59 | query_results = df.query(query_signals) 60 | 61 | threshold = self.params[FilterOptions.threshold_intervals] 62 | 63 | for index, rs in query_results.iterrows(): 64 | signal_direction = rs[self.field_name] * inverse 65 | self.log_entry(signal_direction, rs) 66 | 67 | for index_after in range(0, threshold): 68 | # df_index = index + index_after 69 | df_index = df.index.get_loc(index) + index_after 70 | 71 | if df_index >= len(df.index): 72 | rx = df.iloc[df_index - 1] 73 | diff = (rx.close - rs.close) * signal_direction 74 | self.log_exit("MaxTime", diff, rx) 75 | rxi = rx.name 76 | df.loc[rxi, self.result_field_name] = diff 77 | break 78 | 79 | rx = df.iloc[df_index] 80 | rxi = rx.name 81 | diff = (rx.close - rs.close) * signal_direction 82 | 83 | if diff >= self.params[FilterOptions.win_points]: 84 | self.log_exit("Won", diff, df.iloc[df_index]) 85 | df.loc[rxi, self.result_field_name] = diff 86 | break 87 | 88 | if diff <= (self.params[FilterOptions.loss_points] * -1.0): 89 | self.log_exit("Lost", diff, df.iloc[df_index]) 90 | df.loc[rxi, self.result_field_name] = diff 91 | break 92 | 93 | if index_after == threshold - 1: 94 | self.log_exit("MaxTime", diff, df.iloc[df_index]) 95 | df.loc[rxi, self.result_field_name] = diff 96 | -------------------------------------------------------------------------------- /ta_scanner/indicators.py: -------------------------------------------------------------------------------- 1 | import abc 2 | from enum import Enum 3 | import numpy as np 4 | import pandas as pd 5 | from talib import abstract 6 | from typing import Any, Dict, List, Optional 7 | 8 | 9 | class IndicatorParams(Enum): 10 | slow_sma = "slow_sma" 11 | fast_sma = "fast_sma" 12 | slow_ema = "slow_ema" 13 | fast_ema = "fast_ema" 14 | field_names = "field_names" 15 | 16 | 17 | def crossover(series, value=0): 18 | shift = +1 19 | series_shifted = series.shift(shift) 20 | conditions = [ 21 | (series <= value) & (series_shifted >= value), 22 | (series >= value) & (series_shifted <= value), 23 | ] 24 | choices = [-1, +1] 25 | crossover = np.select(conditions, choices, default=0) 26 | return crossover 27 | 28 | 29 | class IndicatorException(Exception): 30 | pass 31 | 32 | 33 | class BaseIndicator(metaclass=abc.ABCMeta): 34 | def __init__(self, field_name: str, params: Dict[IndicatorParams, Any]): 35 | self.field_name = field_name 36 | self.params = params 37 | 38 | def ensure_required_filter_options( 39 | self, expected: List[IndicatorParams], actual: Dict[IndicatorParams, Any] 40 | ): 41 | for expected_key in expected: 42 | if expected_key not in actual: 43 | indicator_name = self.__class__.__name__ 44 | raise IndicatorException( 45 | f"{indicator_name} requires key = {expected_key}" 46 | ) 47 | 48 | @abc.abstractmethod 49 | def apply(self, df, field_name: str) -> None: 50 | pass 51 | 52 | 53 | class IndicatorSmaCrossover(BaseIndicator): 54 | def apply(self, df: pd.DataFrame) -> None: 55 | self.ensure_required_filter_options( 56 | [IndicatorParams.fast_sma, IndicatorParams.slow_sma], self.params 57 | ) 58 | slow_sma = self.params[IndicatorParams.slow_sma] 59 | fast_sma = self.params[IndicatorParams.fast_sma] 60 | 61 | sma = abstract.Function("sma") 62 | df["slow_sma"] = sma(df.close, timeperiod=slow_sma) 63 | df["fast_sma"] = sma(df.close, timeperiod=fast_sma) 64 | df[self.field_name] = crossover(df.fast_sma - df.slow_sma) 65 | return df 66 | 67 | 68 | class IndicatorEmaCrossover(BaseIndicator): 69 | def apply(self, df: pd.DataFrame) -> None: 70 | self.ensure_required_filter_options( 71 | [IndicatorParams.fast_ema, IndicatorParams.slow_ema], self.params 72 | ) 73 | slow_ema = self.params[IndicatorParams.slow_ema] 74 | fast_ema = self.params[IndicatorParams.fast_ema] 75 | 76 | ema = abstract.Function("ema") 77 | df["slow_ema"] = ema(df.close, timeperiod=slow_ema) 78 | df["fast_ema"] = ema(df.close, timeperiod=fast_ema) 79 | df[self.field_name] = crossover(df.fast_ema - df.slow_ema) 80 | return df 81 | 82 | 83 | class CombinedBindary(BaseIndicator): 84 | def apply(self, df: pd.DataFrame) -> None: 85 | self.ensure_required_filter_options([IndicatorParams.field_names], self.params) 86 | field_names = self.params[IndicatorParams.field_names] 87 | 88 | df[self.field_name] = 0 89 | length = len(field_names) 90 | field_name_values = [None for _ in range(length)] 91 | 92 | signals = df.loc[df[field_names].isin([1, -1]).any(1)][field_names] 93 | 94 | for i, row in signals.iterrows(): 95 | for ii, fn in enumerate(field_names): 96 | if row[fn] != 0: 97 | field_name_values[ii] = row[fn] 98 | if abs(sum(filter(None, field_name_values))) == length: 99 | df.loc[i, self.field_name] = field_name_values[0] 100 | -------------------------------------------------------------------------------- /ta_scanner/data/ib.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from loguru import logger 3 | 4 | import datetime 5 | from trading_calendars import get_calendar, TradingCalendar 6 | from typing import Optional, Dict, Any, List, Tuple, Optional 7 | 8 | from ib_insync import IB, Future, ContFuture, Stock, Contract 9 | from ib_insync import util as ib_insync_util 10 | 11 | from ta_scanner.data.base_connector import DataFetcherBase 12 | from ta_scanner.data.constants import ( 13 | TimezoneNames, 14 | WhatToShow, 15 | Exchange, 16 | Calendar, 17 | Currency, 18 | ) 19 | 20 | 21 | class IbDataFetcher(DataFetcherBase): 22 | def __init__(self, client_id: int = 0): 23 | self.ib = None 24 | self.client_id = client_id 25 | 26 | def _init_client(self, host: str = "127.0.0.1", port: int = 4001) -> None: 27 | ib = IB() 28 | ib.connect(host, port, clientId=self.client_id) 29 | self.ib = ib 30 | 31 | def _execute_req_historical( 32 | self, contract, dt, duration, bar_size_setting, what_to_show, use_rth 33 | ) -> pd.DataFrame: 34 | if self.ib is None or not self.ib.isConnected(): 35 | self._init_client() 36 | 37 | dfs = [] 38 | for rth in [True, False]: 39 | bars = self.ib.reqHistoricalData( 40 | contract, 41 | endDateTime=dt, 42 | durationStr=duration, 43 | barSizeSetting=bar_size_setting, 44 | whatToShow=what_to_show, 45 | useRTH=rth, # use_rth, 46 | formatDate=2, # return as UTC time 47 | ) 48 | x = ib_insync_util.df(bars) 49 | if x is None: 50 | continue 51 | x["rth"] = rth 52 | dfs.append(x) 53 | 54 | if dfs == []: 55 | return None 56 | df = pd.concat(dfs).drop_duplicates().reset_index(drop=True) 57 | return df 58 | 59 | def request_stock_instrument( 60 | self, instrument_symbol: str, dt: datetime.datetime, what_to_show: str 61 | ) -> pd.DataFrame: 62 | exchange = Exchange.SMART.value 63 | contract = Stock(instrument_symbol, exchange, Currency.USD.value) 64 | duration = "2 D" 65 | bar_size_setting = "1 min" 66 | use_rth = False 67 | return self._execute_req_historical( 68 | contract, dt, duration, bar_size_setting, what_to_show, use_rth 69 | ) 70 | 71 | def select_exchange_by_symbol(self, symbol): 72 | kvs = { 73 | Exchange.GLOBEX: [ 74 | # fmt: off 75 | # equities 76 | "/ES", "/MES", 77 | "/NQ", "/MNQ", 78 | # currencies 79 | "/M6A", "/M6B", "/M6E", 80 | # interest rates 81 | # '/GE', '/ZN', '/ZN', '/ZT', 82 | # fmt: on 83 | ], 84 | Exchange.ECBOT: ["/ZC", "/YC", "/ZS", "/YK", "/ZW", "/YW"], 85 | Exchange.NYMEX: [ 86 | "/GC", 87 | "/MGC", 88 | "/CL", 89 | "/QM", 90 | ], 91 | } 92 | 93 | for k, v in kvs.items(): 94 | if symbol in v: 95 | return k 96 | raise NotImplementedError 97 | 98 | def request_future_instrument( 99 | self, 100 | symbol: str, 101 | dt: datetime.datetime, 102 | what_to_show: str, 103 | contract_date: Optional[str] = None, 104 | ) -> pd.DataFrame: 105 | exchange_name = self.select_exchange_by_symbol(symbol).value 106 | 107 | if contract_date: 108 | raise NotImplementedError 109 | else: 110 | contract = ContFuture(symbol, exchange_name, currency=Currency.USD.value) 111 | 112 | duration = "1 D" 113 | bar_size_setting = "1 min" 114 | use_rth = False 115 | return self._execute_req_historical( 116 | contract, dt, duration, bar_size_setting, what_to_show, use_rth 117 | ) 118 | 119 | def request_instrument( 120 | self, 121 | symbol: str, 122 | dt, 123 | what_to_show, 124 | contract_date: Optional[str] = None, 125 | ): 126 | if "/" in symbol: 127 | return self.request_future_instrument( 128 | symbol, dt, what_to_show, contract_date 129 | ) 130 | else: 131 | return self.request_stock_instrument(symbol, dt, what_to_show) 132 | -------------------------------------------------------------------------------- /lab/experiment_futures.py: -------------------------------------------------------------------------------- 1 | # todos 2 | # - [ ] all dates and date deltas are in time, not integers 3 | 4 | from loguru import logger 5 | from typing import Dict 6 | import sys 7 | import datetime 8 | from datetime import timedelta 9 | import numpy as np 10 | 11 | from ta_scanner.data.data import load_and_cache, db_data_fetch_between, aggregate_bars 12 | from ta_scanner.data.ib import IbDataFetcher 13 | from ta_scanner.experiments.simple_experiment import SimpleExperiment 14 | 15 | from ta_scanner.indicators import ( 16 | IndicatorSmaCrossover, 17 | IndicatorEmaCrossover, 18 | IndicatorParams, 19 | ) 20 | from ta_scanner.signals import Signal 21 | from ta_scanner.filters import FilterCumsum, FilterOptions, FilterNames 22 | from ta_scanner.reports import BasicReport 23 | from ta_scanner.models import gen_engine 24 | 25 | 26 | ib_data_fetcher = IbDataFetcher() 27 | instrument_symbol = "/NQ" 28 | rth = False 29 | interval = 1 30 | 31 | field_name = "ema_cross" 32 | slow_sma = 25 33 | fast_sma_min = 5 34 | fast_sma_max = 20 35 | 36 | filter_inverse = True 37 | win_pts = 75 38 | loss_pts = 30 39 | trade_interval = 12 40 | 41 | test_total_pnl = 0.0 42 | test_total_count = 0 43 | all_test_results = [] 44 | 45 | engine = gen_engine() 46 | 47 | logger.remove() 48 | logger.add(sys.stderr, level="INFO") 49 | 50 | 51 | def gen_params(sd, ed) -> Dict: 52 | return dict(start_date=sd, end_date=ed, use_rth=rth, groupby_minutes=interval) 53 | 54 | 55 | def run_cross(original_df, fast_sma: int, slow_sma: int): 56 | df = original_df.copy() 57 | 58 | # indicator setup 59 | indicator_params = { 60 | IndicatorParams.fast_ema: fast_sma, 61 | IndicatorParams.slow_ema: slow_sma, 62 | } 63 | indicator = IndicatorEmaCrossover(field_name, indicator_params) 64 | indicator.apply(df) 65 | 66 | # filter setup 67 | filter_params = { 68 | FilterOptions.win_points: win_pts, 69 | FilterOptions.loss_points: loss_pts, 70 | FilterOptions.threshold_intervals: trade_interval, 71 | } 72 | sfilter = FilterCumsum(field_name, filter_params) 73 | 74 | # generate results 75 | if filter_inverse: 76 | results = sfilter.apply(df, inverse=1) 77 | else: 78 | results = sfilter.apply(df) 79 | 80 | # get aggregate pnl 81 | basic_report = BasicReport() 82 | pnl, count, avg, median = basic_report.analyze(df, field_name) 83 | 84 | return pnl, count, avg, median 85 | 86 | 87 | def run_cross_range(df, slow_sma: int, fast_sma_min, fast_sma_max): 88 | results = [] 89 | for fast_sma in range(fast_sma_min, fast_sma_max): 90 | pnl, count, avg, median = run_cross(df, fast_sma, slow_sma) 91 | results.append([fast_sma, pnl, count, avg, median]) 92 | return results 93 | 94 | 95 | def fetch_data(): 96 | sd = datetime.date(2020, 7, 1) 97 | ed = datetime.date(2020, 8, 15) 98 | load_and_cache(instrument_symbol, ib_data_fetcher, **gen_params(sd, ed)) 99 | 100 | 101 | def query_data(engine, symbol, sd, ed, groupby_minutes): 102 | df = db_data_fetch_between(engine, symbol, sd, ed) 103 | df.set_index("ts", inplace=True) 104 | df = aggregate_bars(df, groupby_minutes=groupby_minutes) 105 | df["ts"] = df.index 106 | return df 107 | 108 | 109 | # fetch_data() 110 | 111 | for i in range(0, 33): 112 | initial = datetime.date(2020, 7, 10) + timedelta(days=i) 113 | test_start, test_end = initial, initial 114 | 115 | if initial.weekday() in [5, 6]: 116 | continue 117 | 118 | # fetch training data 119 | train_sd = initial - timedelta(days=5) 120 | train_ed = initial - timedelta(days=1) 121 | df_train = query_data(engine, instrument_symbol, train_sd, train_ed, interval) 122 | 123 | # for training data, let's find results for a range of SMA 124 | results = run_cross_range( 125 | df_train, 126 | slow_sma=slow_sma, 127 | fast_sma_min=fast_sma_min, 128 | fast_sma_max=fast_sma_max, 129 | ) 130 | 131 | fast_sma_pnl = [] 132 | 133 | for resultindex in range(2, len(results) - 3): 134 | fast_sma = results[resultindex][0] 135 | pnl = results[resultindex][1] 136 | result_set = results[resultindex - 2 : resultindex + 3] 137 | total_pnl = sum([x[1] for x in result_set]) 138 | fast_sma_pnl.append([fast_sma, total_pnl, pnl]) 139 | 140 | arr = np.array(fast_sma_pnl, dtype=float) 141 | max_tuple = np.unravel_index(np.argmax(arr, axis=None), arr.shape) 142 | optimal_fast_sma = int(arr[(max_tuple[0], 0)]) 143 | 144 | optimal_fast_sma_pnl = [x[2] for x in fast_sma_pnl if x[0] == optimal_fast_sma][0] 145 | 146 | # logger.info(f"Selected fast_sma={optimal_fast_sma}. PnL={optimal_fast_sma_pnl}") 147 | 148 | test_sd = initial 149 | test_ed = initial + timedelta(days=1) 150 | 151 | df_test = query_data(engine, instrument_symbol, test_sd, test_ed, interval) 152 | test_results = run_cross(df_test, optimal_fast_sma, slow_sma) 153 | 154 | all_test_results.append([initial] + list(test_results)) 155 | 156 | logger.info( 157 | f"Test Results. pnl={test_results[0]}, count={test_results[1]}, avg={test_results[2]}, median={test_results[3]}" 158 | ) 159 | test_total_pnl += test_results[0] 160 | test_total_count += test_results[1] 161 | 162 | logger.info( 163 | f"--- CumulativePnL={test_total_pnl}. Trades Count={test_total_count}. After={initial}" 164 | ) 165 | 166 | 167 | import csv 168 | 169 | with open("simple_results.csv", "w") as csvfile: 170 | spamwriter = csv.writer(csvfile) 171 | for row in all_test_results: 172 | spamwriter.writerow(row) 173 | -------------------------------------------------------------------------------- /ta_scanner/data/data.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | import pandas as pd 3 | import numpy as np 4 | import os 5 | from loguru import logger 6 | from psycopg2 import sql 7 | 8 | import datetime 9 | import pytz 10 | from psycopg2.errors import UniqueViolation 11 | from sqlalchemy.exc import IntegrityError 12 | from trading_calendars import get_calendar, TradingCalendar 13 | from typing import Optional, Dict, Any, List, Tuple 14 | 15 | from ta_scanner.models import gen_engine, init_db, Quote 16 | from ta_scanner.data.base_connector import DataFetcherBase 17 | from ta_scanner.data.constants import ( 18 | TimezoneNames, 19 | WhatToShow, 20 | Exchange, 21 | Calendar, 22 | Currency, 23 | ) 24 | 25 | 26 | def extract_kwarg(kwargs: Dict, key: str, default_value: Any = None) -> Optional[Any]: 27 | if key in kwargs: 28 | return kwargs[key] 29 | else: 30 | return default_value 31 | 32 | 33 | def load_and_cache( 34 | instrument_symbol: str, data_fetcher: DataFetcherBase, **kwargs 35 | ) -> pd.DataFrame: 36 | """Fetch data from IB or postgres 37 | 38 | Args: 39 | instrument_symbol (str): [description] 40 | data_fetcher (DataFetcherBase): [description] 41 | kwargs (Dict): [description] 42 | 43 | Returns: 44 | pd.DataFrame: [description] 45 | """ 46 | engine = gen_engine() 47 | init_db() 48 | 49 | # turn kwargs into variables 50 | start_date = extract_kwarg(kwargs, "start_date", None) 51 | end_date = extract_kwarg(kwargs, "end_date", None) 52 | 53 | use_rth = extract_kwarg(kwargs, "use_rth", False) 54 | contract_date = extract_kwarg(kwargs, "contract_date") 55 | groupby_minutes = extract_kwarg(kwargs, "groupby_minutes", 1) 56 | return_tz = extract_kwarg(kwargs, "return_tz", TimezoneNames.US_EASTERN.value) 57 | 58 | what_to_show = WhatToShow.TRADES.value 59 | 60 | # this is temp - start 61 | dfs = [] 62 | # temp - stop 63 | 64 | calendar = Calendar.init_by_symbol(instrument_symbol) 65 | 66 | for dt in gen_datetime_range(start_date, end_date): 67 | # if market was closed - skip 68 | # if calendar.is_session(dt.date()) == False: 69 | # logger.debug(f"Market closed on {dt.date()} for {instrument_symbol}") 70 | 71 | # if db already has values - skip 72 | # if db_data_exists(engine, instrument_symbol, dt): 73 | # df = db_data_fetch(engine, instrument_symbol, dt) 74 | # else: 75 | if True: 76 | df = data_fetcher.request_instrument(instrument_symbol, dt, what_to_show) 77 | 78 | if df is None: 79 | continue 80 | 81 | df["symbol"] = instrument_symbol 82 | transform_rename_df_columns(df) 83 | # convert time from UTC to US/Eastern 84 | # df["ts"] = df["ts"].dt.tz_convert(TimezoneNames.UTC.value) 85 | # df["ts"] = df["ts"].dt.tz_localize(TimezoneNames.US_EASTERN.value) 86 | # apply_rth(df, calendar) 87 | db_insert_df_conflict_on_do_nothing(engine, df, "quote") 88 | 89 | if use_rth: 90 | df = reduce_to_only_rth(df) 91 | 92 | transform_set_index_ts(df) 93 | 94 | df = aggregate_bars(df, groupby_minutes) 95 | transform_ts_result_tz(df, return_tz) 96 | 97 | logger.debug(f"--- fetched {instrument_symbol} - {dt}") 98 | 99 | # temp - start 100 | dfs.append(df) 101 | # temp - stop 102 | 103 | logger.debug(f"finished {instrument_symbol}") 104 | 105 | df = pd.concat(dfs) 106 | df.sort_values(by=["ts"], inplace=True, ascending=True) 107 | df.reset_index(inplace=True) 108 | return df 109 | 110 | 111 | def gen_datetime_range(start, end) -> List[datetime.datetime]: 112 | result = [] 113 | span = end - start 114 | for i in range(span.days + 1): 115 | d = start + datetime.timedelta(days=i) 116 | result.append(datetime.date(d.year, d.month, d.day)) 117 | return result 118 | 119 | 120 | def reduce_to_only_rth(df) -> pd.DataFrame: 121 | return df[df["rth"] == True] 122 | 123 | 124 | def apply_rth(df: pd.DataFrame, calendar: TradingCalendar) -> None: 125 | calendar_name: str = "XNYS" 126 | calendar = get_calendar(calendar_name) 127 | 128 | def is_open(ts: pd.Timestamp): 129 | return calendar.is_open_on_minute(ts) 130 | 131 | df["rth"] = df.ts.apply(is_open) 132 | 133 | 134 | def aggregate_bars(df: pd.DataFrame, groupby_minutes: int) -> pd.DataFrame: 135 | if groupby_minutes == 1: 136 | return df 137 | 138 | # this method only intended to handle data that's 139 | # aggredating data at intervals less than 1 day 140 | assert groupby_minutes < 1440 141 | 142 | groupby = f"{groupby_minutes}min" 143 | 144 | agg_expression = { 145 | "open": "first", 146 | "high": "max", 147 | "low": "min", 148 | "close": "last", 149 | "volume": "sum", 150 | } 151 | df = df.resample(groupby).agg(agg_expression) 152 | df.dropna(subset=["open", "close", "high", "low"], inplace=True) 153 | return df 154 | 155 | 156 | def transform_set_index_ts(df: pd.DataFrame) -> None: 157 | df.set_index("ts", inplace=True) 158 | 159 | 160 | def transform_rename_df_columns(df) -> None: 161 | df.rename(columns={"date": "ts", "barCount": "bar_count"}, inplace=True) 162 | 163 | 164 | def transform_ts_result_tz(df: pd.DataFrame, return_tz: str) -> None: 165 | return_tz_value = pytz.timezone(return_tz) 166 | df.index = df.index.tz_convert(return_tz_value) 167 | 168 | 169 | def clean_query(query: str) -> str: 170 | return query.replace("\n", "").replace("\t", "") 171 | 172 | 173 | def db_data_exists(engine, instrument_symbol: str, date: datetime.datetime) -> bool: 174 | date_str: str = date.strftime("%Y-%m-%d") 175 | 176 | query = f""" 177 | select count(*) 178 | from {Quote.__tablename__} 179 | where 180 | symbol = '{instrument_symbol}' 181 | and date(ts AT TIME ZONE '{TimezoneNames.US_EASTERN.value}') = date('{date_str}') 182 | """ 183 | with engine.connect() as con: 184 | result = con.execute(clean_query(query)) 185 | counts = [x for x in result] 186 | 187 | return counts != [(0,)] 188 | 189 | 190 | def db_data_fetch( 191 | engine, instrument_symbol: str, date: datetime.datetime 192 | ) -> pd.DataFrame: 193 | date_str: str = date.strftime("%Y-%m-%d") 194 | 195 | query = f""" 196 | select * 197 | from {Quote.__tablename__} 198 | where 199 | symbol = '{instrument_symbol}' 200 | and date(ts AT TIME ZONE '{TimezoneNames.US_EASTERN.value}') = date('{date_str}') 201 | """ 202 | return pd.read_sql(clean_query(query), con=engine) 203 | 204 | 205 | def db_data_fetch_between( 206 | engine, instrument_symbol: str, sd: datetime.datetime, ed: datetime.datetime 207 | ) -> pd.DataFrame: 208 | sd_str: str = sd.strftime("%Y-%m-%d") 209 | ed_str: str = ed.strftime("%Y-%m-%d") 210 | 211 | query = f""" 212 | select * 213 | from {Quote.__tablename__} 214 | where 215 | symbol = '{instrument_symbol}' 216 | and date(ts AT TIME ZONE '{TimezoneNames.US_EASTERN.value}') BETWEEN date('{sd}') AND date('{ed}') 217 | """ 218 | return pd.read_sql(clean_query(query), con=engine) 219 | 220 | 221 | def db_insert_df_conflict_on_do_nothing( 222 | engine, df: pd.DataFrame, table_name: str 223 | ) -> None: 224 | cols = __gen_cols(df) 225 | values = __gen_values(df) 226 | 227 | query_template = "INSERT INTO {table_name} ({cols}) VALUES ({values});" 228 | 229 | query = sql.SQL(query_template).format( 230 | table_name=sql.Identifier(table_name), 231 | cols=sql.SQL(', ').join(map(sql.Identifier, cols)), 232 | values=sql.SQL(', ').join(sql.Placeholder() * len(cols)), 233 | ) 234 | 235 | with engine.connect() as con: 236 | with con.connection.cursor() as cur: 237 | for v in values: 238 | try: 239 | cur.execute(query, v) 240 | con.connection.commit() 241 | except UniqueViolation as e: 242 | cur.execute("rollback") 243 | con.connection.commit() 244 | except Exception as e: 245 | cur.execute("rollback") 246 | con.connection.commit() 247 | 248 | 249 | def __gen_values(df: pd.DataFrame) -> List[Tuple[str]]: 250 | """ 251 | return array of tuples for the df values 252 | """ 253 | return [tuple([str(xx) for xx in x]) for x in df.to_records(index=False)] 254 | 255 | 256 | def __gen_cols(df) -> List[str]: 257 | """ 258 | return column names 259 | """ 260 | return list(df.columns) 261 | -------------------------------------------------------------------------------- /lab/TradingDay.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import pandas as pd\n", 10 | "import numpy as np\n", 11 | "import datetime\n", 12 | "\n", 13 | "import matplotlib.pyplot as plt\n", 14 | "import matplotlib.dates as mdates\n", 15 | "import matplotlib.gridspec as gridspec\n", 16 | "%matplotlib inline\n", 17 | "\n", 18 | "from ta_scanner.data import load_and_cache, IbDataFetcher, db_data_fetch_between\n", 19 | "from ta_scanner.models import gen_engine" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 2, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "import nest_asyncio\n", 29 | "nest_asyncio.apply()\n", 30 | "\n", 31 | "colors = {\n", 32 | " \"pink\": \"#ff1493\"\n", 33 | "}" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 3, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [ 42 | "ib_data_fetcher = IbDataFetcher()" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 4, 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [ 51 | "engine = gen_engine()" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 141, 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "symbol = \"/M6E\"\n", 61 | "\n", 62 | "sd = datetime.date(2020, 7, 30)\n", 63 | "ed = datetime.date(2020, 8, 13)\n", 64 | "\n", 65 | "df = db_data_fetch_between(engine, symbol, sd, ed)\n", 66 | "\n", 67 | "\n", 68 | "# df = load_and_cache(\n", 69 | "# symbol,\n", 70 | "# ib_data_fetcher,\n", 71 | "# start_date=datetime.date(2020, 7, 30),\n", 72 | "# end_date=datetime.date(2020, 8, 8),\n", 73 | "# # use_rth=False,\n", 74 | "# # groupby_minutes=1,\n", 75 | "# )\n", 76 | "# df.drop(['id'], axis=1, inplace=True)" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 142, 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "data": { 86 | "text/html": [ 87 | "
\n", 88 | "\n", 101 | "\n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | " \n", 236 | " \n", 237 | " \n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | "
idtssymbolopenclosehighlowaveragevolumebar_countrth
04812020-07-30 04:00:00+00:00/M6E1.181.181.181.181.182112False
14822020-07-30 04:01:00+00:00/M6E1.181.181.181.181.1864False
24832020-07-30 04:02:00+00:00/M6E1.181.181.181.181.1833False
34842020-07-30 04:03:00+00:00/M6E1.181.181.181.181.1887False
44852020-07-30 04:04:00+00:00/M6E1.181.181.181.181.1844False
....................................
8275120562020-08-07 03:55:00+00:00/M6E1.191.191.191.191.1976False
8276120572020-08-07 03:56:00+00:00/M6E1.191.191.191.191.1962False
8277120582020-08-07 03:57:00+00:00/M6E1.191.191.191.191.1986False
8278120592020-08-07 03:58:00+00:00/M6E1.191.191.191.191.19178False
8279120602020-08-07 03:59:00+00:00/M6E1.191.191.191.191.1952False
\n", 275 | "

8280 rows × 11 columns

\n", 276 | "
" 277 | ], 278 | "text/plain": [ 279 | " id ts symbol open close high low \\\n", 280 | "0 481 2020-07-30 04:00:00+00:00 /M6E 1.18 1.18 1.18 1.18 \n", 281 | "1 482 2020-07-30 04:01:00+00:00 /M6E 1.18 1.18 1.18 1.18 \n", 282 | "2 483 2020-07-30 04:02:00+00:00 /M6E 1.18 1.18 1.18 1.18 \n", 283 | "3 484 2020-07-30 04:03:00+00:00 /M6E 1.18 1.18 1.18 1.18 \n", 284 | "4 485 2020-07-30 04:04:00+00:00 /M6E 1.18 1.18 1.18 1.18 \n", 285 | "... ... ... ... ... ... ... ... \n", 286 | "8275 12056 2020-08-07 03:55:00+00:00 /M6E 1.19 1.19 1.19 1.19 \n", 287 | "8276 12057 2020-08-07 03:56:00+00:00 /M6E 1.19 1.19 1.19 1.19 \n", 288 | "8277 12058 2020-08-07 03:57:00+00:00 /M6E 1.19 1.19 1.19 1.19 \n", 289 | "8278 12059 2020-08-07 03:58:00+00:00 /M6E 1.19 1.19 1.19 1.19 \n", 290 | "8279 12060 2020-08-07 03:59:00+00:00 /M6E 1.19 1.19 1.19 1.19 \n", 291 | "\n", 292 | " average volume bar_count rth \n", 293 | "0 1.18 21 12 False \n", 294 | "1 1.18 6 4 False \n", 295 | "2 1.18 3 3 False \n", 296 | "3 1.18 8 7 False \n", 297 | "4 1.18 4 4 False \n", 298 | "... ... ... ... ... \n", 299 | "8275 1.19 7 6 False \n", 300 | "8276 1.19 6 2 False \n", 301 | "8277 1.19 8 6 False \n", 302 | "8278 1.19 17 8 False \n", 303 | "8279 1.19 5 2 False \n", 304 | "\n", 305 | "[8280 rows x 11 columns]" 306 | ] 307 | }, 308 | "execution_count": 142, 309 | "metadata": {}, 310 | "output_type": "execute_result" 311 | } 312 | ], 313 | "source": [ 314 | "df" 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": 143, 320 | "metadata": {}, 321 | "outputs": [], 322 | "source": [ 323 | "df['date'] = df['ts'].dt.date" 324 | ] 325 | }, 326 | { 327 | "cell_type": "code", 328 | "execution_count": 144, 329 | "metadata": {}, 330 | "outputs": [ 331 | { 332 | "data": { 333 | "image/png": "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Z7n7OZKelPT/xtxnAACAkTKMGYDt6h/lGYDtnichDTMAW58DQK96nYmWNcwABAAAQFoNLAB093tVC/k6le929wcltftztYKkOWZWkDRX0vP1de5y9+DT7f2STom31wAAAKMlzgCwWCw2LWv8uVMZAWD/CAABZAEBYG27250n4wwAO52HCQABAADQSeo++bn7DjO7XtI2SYcl3eXud7V56X+QdHOneszsSklXStLy5cs1OTk5gN4CyTp48CC/20BEjCOMuh07dkiSnn766Y6/y8eOHZMkPfnkkx1fs3PnTh09erSp/ODBg1PPjxw50lT24osvSpIOHTo01DG0d+9eTUxM9N1m0G9J2rhxow4dOhRTz3p36NChafs8zOHDh7Vnz55Y9veTTz459XzXrl0cBxPGuSjdzjtwQJL0CO9RW88++6wk6Z577tG8efMS6UOSY+jxxx+XJG3ZskULFiyYWr579+7YzpF79+7VrFmzmuratm2bJGnTpk064YQTZqzj4YcfllQ79x0+fDhyn5A9nIuAaBhDQHSMo/ilLgA0s8WSLpd0mqQDkm4xs4+4+7cbXvPHksqSvtOpHne/UdKNkrRmzRpft27dILsNJGJyclL8bgPRMI4w6p544glJ0tlnn93xdzn4q/+VK1d2fM3ixYv18ssvN5U3BoCLFi1qKjtQvyBeKBSGOoZe9apXacmSJX23uWLFiqnna9eu1dlnnx1Tz3q3dOlSlcvlrrfFzELfw14E75+k/8ve/UdJetd1on9/ZrpCSEKYkIEJEDCohMhxMcIIhNXLSGQJoITjXZH4K7quQc/RGDxRcL1r1j9w0cUDuq5ycpALHtlI+CVcdpEgdwdulEAmIUoiv0EgJmGEkA1DSFKT/t4/uho6Q1d11VRX11M9r9c5z+nq77eepz5V3Z+qnnrP96k85jGP8Tw4Z16LOm7Xyolt/IzW95GPfCRJcs455+TUU0+dSw3z7KHjjjsuSfLEJz7xfjW86U1vyjXXXLMpdZ144onf8jr86U9/Okly5plnjnUbt9++8nv81Kc+NU94whOmrontx2sRTEcPwfT00eab5WcAHq0fSvLZ1tq/tNb6Sd6a5Gmrk1V1YZIfTvKTrbU2pxoBADphnFN67dy5837XHXYcnwG4tZwCFNgOnALUZwACANBNXQwAP5/kqVV1QlVVknOTfDRJquq8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\n", 334 | "text/plain": [ 335 | "
" 336 | ] 337 | }, 338 | "metadata": { 339 | "needs_background": "light" 340 | }, 341 | "output_type": "display_data" 342 | } 343 | ], 344 | "source": [ 345 | "fig = plt.figure(figsize=(25, 15))\n", 346 | "\n", 347 | "num_days = 4\n", 348 | "\n", 349 | "dates = []\n", 350 | "for k, v in df.groupby('date'):\n", 351 | " v.set_index('ts', inplace=True)\n", 352 | "\n", 353 | " if len(v.between_time('6:30', '9:00')) > 10:\n", 354 | " dates.append(k)\n", 355 | "\n", 356 | "gs = gridspec.GridSpec(num_days, 1, height_ratios=([1] * num_days))\n", 357 | "\n", 358 | "for i, date in enumerate(reversed(dates[-num_days:])):\n", 359 | " ax = plt.subplot(gs[i])\n", 360 | " idf = df.query(\"date == @date\").copy()\n", 361 | " idf.set_index('ts', inplace=True)\n", 362 | " idf = idf.between_time('4:30', '12:00').copy()\n", 363 | " \n", 364 | " idf.sort_index(inplace=True)\n", 365 | "\n", 366 | " ax.plot(idf.close, color='black')\n", 367 | " ax.grid(True)\n", 368 | " ax.set_ylabel(f\"{date}, {symbol}\")\n", 369 | " \n", 370 | " for index, row in idf[(idf.index.hour == 8) & (idf.index.minute == 30)].iterrows():\n", 371 | " ax.axvline(index, color=colors[\"pink\"], linestyle='solid')\n", 372 | " ax.axhline(row.close, color=colors[\"pink\"], linestyle='solid', lw=0.7)\n", 373 | " \n", 374 | "fig.tight_layout()" 375 | ] 376 | }, 377 | { 378 | "cell_type": "code", 379 | "execution_count": 145, 380 | "metadata": {}, 381 | "outputs": [ 382 | { 383 | "data": { 384 | "text/plain": [ 385 | "" 386 | ] 387 | }, 388 | "execution_count": 145, 389 | "metadata": {}, 390 | "output_type": "execute_result" 391 | }, 392 | { 393 | "data": { 394 | "image/png": "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\n", 395 | "text/plain": [ 396 | "
" 397 | ] 398 | }, 399 | "metadata": { 400 | "needs_background": "light" 401 | }, 402 | "output_type": "display_data" 403 | } 404 | ], 405 | "source": [ 406 | "# idf.query(\"6 < index.hour\").query(\"index.hour < 10\")\n", 407 | "idf.between_time('7:15', '10:45').close.plot()" 408 | ] 409 | }, 410 | { 411 | "cell_type": "code", 412 | "execution_count": 146, 413 | "metadata": {}, 414 | "outputs": [ 415 | { 416 | "name": "stdout", 417 | "output_type": "stream", 418 | "text": [ 419 | "2020-07-30. 2020-07-30T04:00:00.000000000 - 2020-07-30T23:59:00.000000000. 1140\n", 420 | "2020-07-31. 2020-07-31T00:00:00.000000000 - 2020-07-31T18:59:00.000000000. 1140\n", 421 | "2020-08-03. 2020-08-03T00:00:00.000000000 - 2020-08-03T23:59:00.000000000. 1380\n", 422 | "2020-08-04. 2020-08-04T11:50:00.000000000 - 2020-08-04T11:49:00.000000000. 1380\n", 423 | "2020-08-05. 2020-08-05T04:08:00.000000000 - 2020-08-05T23:59:00.000000000. 1380\n", 424 | "2020-08-06. 2020-08-06T00:00:00.000000000 - 2020-08-06T23:59:00.000000000. 1380\n", 425 | "2020-08-07. 2020-08-07T00:00:00.000000000 - 2020-08-07T03:59:00.000000000. 240\n" 426 | ] 427 | } 428 | ], 429 | "source": [ 430 | "grouped = df.groupby('date').sum().reset_index()\n", 431 | "grouped.sort_values('date', ascending=False)\n", 432 | "\n", 433 | "for k, v in df.groupby('date'):\n", 434 | " if k.weekday() in [5, 6]:\n", 435 | " continue\n", 436 | " \n", 437 | " msg = f\"{k}. {v.ts.values[0]} - {v.ts.values[-1]}. {len(v.index)}\"\n", 438 | " print(msg)" 439 | ] 440 | } 441 | ], 442 | "metadata": { 443 | "kernelspec": { 444 | "display_name": "Python 3", 445 | "language": "python", 446 | "name": "python3" 447 | }, 448 | "language_info": { 449 | "codemirror_mode": { 450 | "name": "ipython", 451 | "version": 3 452 | }, 453 | "file_extension": ".py", 454 | "mimetype": "text/x-python", 455 | "name": "python", 456 | "nbconvert_exporter": "python", 457 | "pygments_lexer": "ipython3", 458 | "version": "3.7.7" 459 | } 460 | }, 461 | "nbformat": 4, 462 | "nbformat_minor": 4 463 | } 464 | --------------------------------------------------------------------------------