├── 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 | " id | \n",
106 | " ts | \n",
107 | " symbol | \n",
108 | " open | \n",
109 | " close | \n",
110 | " high | \n",
111 | " low | \n",
112 | " average | \n",
113 | " volume | \n",
114 | " bar_count | \n",
115 | " rth | \n",
116 | "
\n",
117 | " \n",
118 | " \n",
119 | " \n",
120 | " | 0 | \n",
121 | " 481 | \n",
122 | " 2020-07-30 04:00:00+00:00 | \n",
123 | " /M6E | \n",
124 | " 1.18 | \n",
125 | " 1.18 | \n",
126 | " 1.18 | \n",
127 | " 1.18 | \n",
128 | " 1.18 | \n",
129 | " 21 | \n",
130 | " 12 | \n",
131 | " False | \n",
132 | "
\n",
133 | " \n",
134 | " | 1 | \n",
135 | " 482 | \n",
136 | " 2020-07-30 04:01:00+00:00 | \n",
137 | " /M6E | \n",
138 | " 1.18 | \n",
139 | " 1.18 | \n",
140 | " 1.18 | \n",
141 | " 1.18 | \n",
142 | " 1.18 | \n",
143 | " 6 | \n",
144 | " 4 | \n",
145 | " False | \n",
146 | "
\n",
147 | " \n",
148 | " | 2 | \n",
149 | " 483 | \n",
150 | " 2020-07-30 04:02:00+00:00 | \n",
151 | " /M6E | \n",
152 | " 1.18 | \n",
153 | " 1.18 | \n",
154 | " 1.18 | \n",
155 | " 1.18 | \n",
156 | " 1.18 | \n",
157 | " 3 | \n",
158 | " 3 | \n",
159 | " False | \n",
160 | "
\n",
161 | " \n",
162 | " | 3 | \n",
163 | " 484 | \n",
164 | " 2020-07-30 04:03:00+00:00 | \n",
165 | " /M6E | \n",
166 | " 1.18 | \n",
167 | " 1.18 | \n",
168 | " 1.18 | \n",
169 | " 1.18 | \n",
170 | " 1.18 | \n",
171 | " 8 | \n",
172 | " 7 | \n",
173 | " False | \n",
174 | "
\n",
175 | " \n",
176 | " | 4 | \n",
177 | " 485 | \n",
178 | " 2020-07-30 04:04:00+00:00 | \n",
179 | " /M6E | \n",
180 | " 1.18 | \n",
181 | " 1.18 | \n",
182 | " 1.18 | \n",
183 | " 1.18 | \n",
184 | " 1.18 | \n",
185 | " 4 | \n",
186 | " 4 | \n",
187 | " False | \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 | " | 8275 | \n",
205 | " 12056 | \n",
206 | " 2020-08-07 03:55:00+00:00 | \n",
207 | " /M6E | \n",
208 | " 1.19 | \n",
209 | " 1.19 | \n",
210 | " 1.19 | \n",
211 | " 1.19 | \n",
212 | " 1.19 | \n",
213 | " 7 | \n",
214 | " 6 | \n",
215 | " False | \n",
216 | "
\n",
217 | " \n",
218 | " | 8276 | \n",
219 | " 12057 | \n",
220 | " 2020-08-07 03:56:00+00:00 | \n",
221 | " /M6E | \n",
222 | " 1.19 | \n",
223 | " 1.19 | \n",
224 | " 1.19 | \n",
225 | " 1.19 | \n",
226 | " 1.19 | \n",
227 | " 6 | \n",
228 | " 2 | \n",
229 | " False | \n",
230 | "
\n",
231 | " \n",
232 | " | 8277 | \n",
233 | " 12058 | \n",
234 | " 2020-08-07 03:57:00+00:00 | \n",
235 | " /M6E | \n",
236 | " 1.19 | \n",
237 | " 1.19 | \n",
238 | " 1.19 | \n",
239 | " 1.19 | \n",
240 | " 1.19 | \n",
241 | " 8 | \n",
242 | " 6 | \n",
243 | " False | \n",
244 | "
\n",
245 | " \n",
246 | " | 8278 | \n",
247 | " 12059 | \n",
248 | " 2020-08-07 03:58:00+00:00 | \n",
249 | " /M6E | \n",
250 | " 1.19 | \n",
251 | " 1.19 | \n",
252 | " 1.19 | \n",
253 | " 1.19 | \n",
254 | " 1.19 | \n",
255 | " 17 | \n",
256 | " 8 | \n",
257 | " False | \n",
258 | "
\n",
259 | " \n",
260 | " | 8279 | \n",
261 | " 12060 | \n",
262 | " 2020-08-07 03:59:00+00:00 | \n",
263 | " /M6E | \n",
264 | " 1.19 | \n",
265 | " 1.19 | \n",
266 | " 1.19 | \n",
267 | " 1.19 | \n",
268 | " 1.19 | \n",
269 | " 5 | \n",
270 | " 2 | \n",
271 | " False | \n",
272 | "
\n",
273 | " \n",
274 | "
\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": "iVBORw0KGgoAAAANSUhEUgAAAYAAAAEGCAYAAABsLkJ6AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4yLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+WH4yJAAAdvElEQVR4nO3df5Ak5X3f8fdn9o5gLGMk7gBZgA7LEhhTzllcKMVODBEmBXIKbCdKcVVyqEjlw3ISAgmJUCkl4ko5IdLZKZf1B4UjjJ2kLgaX8A+VU4IQK8QJwiwEbFTSgXCMdAJuD2F8KITjbuebP7p7p6e3Z2em59f1M59X1dbuPD3TPPuw199+nqef76OIwMzMlk9n0RUwM7PFcAAwM1tSDgBmZkvKAcDMbEk5AJiZLalti67AOHbs2BG7du1adDXMzFrl8ccffzkidlbLWxUAdu3axerq6qKrYWbWKpKeryv3EJCZ2ZJyADAzW1IOAGZmS8oBwMxsSTkAmJktqaEBQNLdktYkPT3g+EWSHpF0TNKtlWO3SPqypKclHZB0al7+NkkPSno2//7W6fw6ZmY2qlF6APcAV29x/BXgJmB/uVDSO/LyPRFxCbACXJ8fvg14KCLeDTyUvzYzszkaug4gIh6WtGuL42vAmqQfH3D+75B0HDgNeCEvvw64Iv/514EvAh8bVpfDR9/glx44OOxtlnvLqdv4+z9yAdtXsji/dvQNnjr0F1x18dkb73nyG6/y375yeKzzSuKn3vsO3nnmd061vmY2XzNbCBYR35S0H/g68P+AByLigfzw2RHxYv6+FyWdNeg8kvYB+wBOOef7+JU/+NqsqpyUYpuHPbvexnvPz0bY7l39Br/04DN87Rc+QKcjAH7loWd56KtrSOOd+43j63z8A98/7Wqb2RzNLADk4/rXARcArwL3SfpQRPzHcc4TEXcBdwHs2bMnVv9NXUfDqv7w2Zf50GcfZb3b2/DnzfWgG9CNoEN2xT/eDX7o/DO4/+d+ZORzX3L7FzjR9UZCZm03y6eAfgz4PxFxJCKOA58Dfjg/dljS2wHy72szrMdSym/w6ZYu1MXub+Vrd0TQGef2H5CyIGJm7TbLAPB14H2STpMk4ErgK/mx3wVuyH++AfidGdZjKSm/qJcv9t2NABB9ZZ3xrv90JHz9N2u/oUNAkg6QTdjukHQIuB3YDhARd0o6B1gFTge6km4GLo6IRyX9FvAEcAL43+RDOcAdwL2SPkIWKD441d/KNi7q0XexL8p67+t2e8FinHO7B2DWfqM8BbR3yPGXgHMHHLudLGBUy79F1iOwGSkmeWfVA3AAMGs/rwRO1MYcQJTnAOrLxp8DEJ4DNms/B4BEFcM666WLffFEULfbe996g0ngjvonl82snRwAElVc1KMy3FP+Xvw85vXfQ0BmiXAASFTvMdBeWd0QULfBEFA2CTxpDc1s0RwAEtXZeAy0rgfQe180mASWewBmSXAASJQ2JoF7ZcVFuzosNHYPoIPXAZglwAEgUfVzAP3foek6APcAzFLgAJCoTs1K4JjqOoCJq2hmC+YAkKi6dQDFhPDk6wC8EtgsBQ4AidIWk8BRmRfojPlXkOUCcgAwazsHgEStdIo5gF5Zt/Yx0PEngVekvsdLzaydHAASVZ8Kou4xUA8BmS0rB4BE1U0CTzcZ3MRVNLMFcwBIlOomgTfSQU+4EriD5wDMEuAAkKitcwHRV+Z1AGbLyQEgUfXrAMjLqo+Bjndup4M2S4MDQKJq1wHkP5c3il/vNkwH7R6AWes5ACRqqz2Bp7MOYNIamtmiOQAkqpcOuny3n5dVJoGb7Am87jEgs9ZzAEhUXTro+nUATgdttqwcABI13jqA8XsAvv6btZ8DQKKU/5+tSwc98ToA9wDMkuAAkKhRdwTznsBmy8sBIFG9x0B7ZRvrALrRV9YsF9CkNTSzRXMASNQ4PYAmuYCcCsKs/RwAEtVLBdErG7gn8JgRYKXjlcBmKXAASFTdOoDaPYEbTQJ7JbBZChwAEjXqnsDN1wFMXEUzWzAHgERtlQ66WtZsHYAjgFnbOQAkShJSfTro6ryA00GbLScHgIRVd+6q9gAiolE6aO8IZpaGoQFA0t2S1iQ9PeD4RZIekXRM0q2l8gslPVn6Oirp5vzYbklfystXJV02vV/JCtXJ2qI3UCRyKy7i3hPYbDmN0gO4B7h6i+OvADcB+8uFEXEwInZHxG7gUuB14P788KeAn8+PfTJ/bVNWnaytrgMoXjdbBzCNGprZIg0NABHxMNlFftDxtYh4DDi+xWmuBJ6LiOeLjwGn5z9/N/DCaNW1cVQna7t5Oujq00BN0kG7B2DWftvm9N+5HjhQen0z8AVJ+8mC0A8P+qCkfcA+gPPPP3+WdUxOR+rL21/tAUTDIaDqec2snWY+CSzpFOBa4L5S8UeBWyLiPOAW4LODPh8Rd0XEnojYs3PnztlWNjGbJ4Gj9nuTdQDuAJi13zyeAroGeCIiDpfKbgA+l/98H+BJ4BmoTtZWx/6bTgJ7CMgsDfMIAHvpH/6BbMz/8vzn9wPPzqEeS6eatK26DqA3BzD+eR0AzNpv6ByApAPAFcAOSYeA24HtABFxp6RzgFWySd1u/qjnxRFxVNJpwFXAjZXT/gzwy5K2AW+Qj/HbdHVUTQWRfd8IBN3ifWP2ADpOB22WgqEBICL2Djn+EnDugGOvA2fWlP8h2aOhNkPVO/VpPQYqp4M2S4JXAies0xltEnhl3HTQXglslgQHgIQNXweQvfY6ALPl5ACQsOoQUPXCHxtDQOOmglDfPgNm1k4OAAkblgyu9xjo+Od1B8Cs/RwAErZ5HcCgSWAPAZktIweAhFXv1KtDP43XAXhPYLMkOAAkbGg66IbrAJwO2iwNDgAJG5wLqP91Z8y/As8BmKXBASBhg3IBVYeAPAdgtpwcABI2KBdQtSfgPYHNlpMDQMI27QdQ2Qqytw5gvPMWO405HYRZuzkAJEyVZHDTTAcNeB7ArOUcABI2ajroJgvByp83s3ZyAEhYNW3zxoW/2z8X0CQXUPb5iatoZgvkAJCwYemgm+4JLPcAzJLgAJCwUfcEXhnzr6BIH+3rv1m7OQAkbFM66E3rALLXzYeAHAHM2swBIGHlIaC6QNB8IZiHgMxS4ACQsI60ke+nbihoknUA1XOaWfs4ACSsnAqiLiXE5OsAHAHM2swBIGHlpG21O4N1G6aDdg/ALAkOAAnL1gH0L/6CXjro9QmSwYHnAMzazgEgYeVJ4PqsoL33jcPrAMzS4ACQMJXWAWy1HqBpKghf/83azQEgYeV1ALVzAF4HYLbUHAASVl4JHN1e+eZ1AOOft3weM2snB4CEdbR5whfq1gGMvycw9J4iMrN2cgBImIZMAjfdFN4rgc3S4ACQsGwOIPu5fg6g4TqATnHOiatoZgvkAJCw/lxAvfLJdwRzD8AsBUMDgKS7Ja1JenrA8YskPSLpmKRbS+UXSnqy9HVU0s2l4/9I0kFJX5b0qen8OlbW6QxbB1Ckg24WAJwKwqzdto3wnnuAzwC/MeD4K8BNwE+UCyPiILAbQNIK8E3g/vz13wCuA34wIo5JOqtJ5W1r/akgeuWbewDjn7d6TjNrn6E9gIh4mOwiP+j4WkQ8Bhzf4jRXAs9FxPP5648Cd0TEseIco1fZRtUpJ4PrlucAsu+TbwnpCGDWZvOaA7geOFB6/R7gr0t6VNJ/l/RXBn1Q0j5Jq5JWjxw5MvOKpqRvHcAUVwJvpILoDnmjmZ3UZh4AJJ0CXAvcVyreBrwVeB/wz4B7NeA2NCLuiog9EbFn586ds65uUoalg26aC8g9ALM0zKMHcA3wREQcLpUdAj4XmT8CusCOOdRlqQxKB725B9B0EngKlTSzhZlHANhL//APwG8D7weQ9B7gFODlOdRlqfTNAZSHgIrVwU33A9hYB+AIYNZmQ58CknQAuALYIekQcDuwHSAi7pR0DrAKnA5080c9L46Io5JOA64Cbqyc9m7g7vzR0jeBG8LPFE7d4D2B+9cGdMacBHA6aLM0DA0AEbF3yPGXgHMHHHsdOLOm/E3gQyPW0RoanA66+O5kcGbLzCuBEzZqOmjvCWy2nBwAEtbp6wHUbQrvPYHNlpkDQMLKk8B16wAmTgftHoBZqzkAJEzSxhM/9T2A7LuTwZktJweAhJWHgNa7dXMAE04CeyWwWas5ACRs4DoA7wlsZjgAJG2lM2AdQH7n3jgddMdDQGYpcABI2OB1ANMZAvL136zdHAASNngdAHlZ8T4PAZktIweAhNWtA9hWs0uY1wGYLScHgITVrQPonxco3ud1AGbLyAEgYcrTQUdEpQeQHS/WCDRPB+0AYNZmDgAJK0/Wdmt6AOtOBme21BwAElaerN3oAax0vA7AzAAHgKT1ntfvf+a/vA5g3Lt/KO8HMJVqmtmCOAAkrDxZW1z0t1eeAhp3/B+cDtosFQ4ACeufA+gNAZXXATQLAF4JbJYCB4CE9c8BZD9X1wE0uP47GZxZIhwAEla+U++bAyitA2jSA/A6ALM0OAAkrDxZW34MNErrAJpMAheTy77+m7WbA0DCNoaAurHxzP+2FfU9BjrJJPC6I4BZqzkAJGylUzcE1OnLD9Rp0AVY8SSwWRIcABLWPwS0ORmc1wGYLTcHgISVn9cvntjpmwOYcAjI6wDM2s0BIGGdIT2A7DHQCdYBuAtg1moOAAkrrwOoSwed9QCanNdDQGYpcABImEqTtXXpoKNhKgjlfzWeBDZrNweAhNWng+5sDN2sN10H4D2BzZLgAJCw2nTQlSGgZnMAvfOaWXs5ACSsPFa/sQ5gpTIE1OAvwHMAZmlwAEhYXzroAcngnAvIbHkNDQCS7pa0JunpAccvkvSIpGOSbi2VXyjpydLXUUk3Vz57q6SQtGPyX8Wqynv39oaAppcO2usAzNptlB7APcDVWxx/BbgJ2F8ujIiDEbE7InYDlwKvA/cXxyWdB1wFfH3MOtuIOjXJ4LavTDEdtK//Zq02NABExMNkF/lBx9ci4jHg+BanuRJ4LiKeL5X9O+CfA76MzEj/OoDppYP2JLBZGuY1B3A9cKB4Iela4JsR8dSwD0raJ2lV0uqRI0dmWcfkqLRxS906gK5zAZkttZkHAEmnANcC9+WvTwM+AXxylM9HxF0RsSci9uzcuXN2FU1Q3Y5gK53ORm+g6SRwcW7PAZi12zx6ANcAT0TE4fz1u4ALgKck/RlwLvCEpHPmUJelUk4Hvd4t7wdAXt5sCKg497q7AGattm0O/429lIZ/IuJPgLOK13kQ2BMRL8+hLkuldh1ANR10w1sASR4CMmu5oQFA0gHgCmCHpEPA7cB2gIi4M79zXwVOB7r5o54XR8TRfLjnKuDGGdXftjBoHUBE8Who8x6Ah4DM2m9oAIiIvUOOv0Q2jFN37HXgzCGf3zWsDtZM3TqAldJ+vk3TQRfn9lNAZu3mlcAJq1sHsNKXIbRZOuji3B4CMms3B4CElTeFL7Z/7HT65wWaDgFJXgdg1nYOAAmr7gnckfrmBZqmg4asB+Drv1m7OQAkrG9P4HzCt9M3BDTJHIB7AGZt5wCQsPJwT5H3p7c4rPmWkOBJYLMUOAAkrLoncLUHMNkcgCeBzdrOASBhfXsC5+P9RVl0J1sJ7HUAZu3nAJCw6p7AWQ8gO9abA2h+7m53ShU1s4VwAEhY9WLf6agvP9CkPQDPAZi1mwNAwqq5gMpDQOWyJjwHYNZ+DgAJq+YCKg8BFekhGvcAOp4DMGs7B4CE9fL+9J7570sP0e09Kjr2uf0YqFnrOQAkrLjYr3d7u39tmheYYBJ43dd/s1ZzAEhY38W+S54KorwOoPkksHMBmbWfA0DCVEn7kPUA+tNBN38KSJ4DMGs5B4CEVdcByOsAzKzEASBh/akgsu0f+x8N9RCQ2TJzAEhY/4Yw/emg17vB+oSTwF4HYNZuDgAJq18H0P9oqNcBmC0vB4CEVS/2EpvWAXhPYLPl5QCQsLrx/k3zAk4FYba0HAASVrfoq//RUCeDM1tmDgAJq9sTuJcLKC9r+BfgPYHN2s8BIGHVPYH7cwH1ypqe2z0As3ZzAEjYxsW+m433r3Qo7QcwjXTQDgBmbeYAkLD+dQD0rQPoTvoYqPAksFnLOQAkrBjf76V9qK4DaD4JvNJxLiCztnMASNjmPYHrVwc3Pbd7AGbt5gCQsI39ACLodvufAsrmBZhoDmDdEcCs1RwAErZ5vL/m0dCGEaAjp4Iwa7uhAUDS3ZLWJD094PhFkh6RdEzSraXyCyU9Wfo6Kunm/NinJX1V0h9Lul/SGdP7laxQzf0vbd4TeKJ00L7+m7XaKD2Ae4Crtzj+CnATsL9cGBEHI2J3ROwGLgVeB+7PDz8IXBIRPwg8A3x8zHrbCMrDPRtzAJ3NTwY1PbcfAzVrt6EBICIeJrvIDzq+FhGPAce3OM2VwHMR8Xz+mQci4kR+7EvAuaNX2UbVnwuofw6gNy/Q7NzOBWTWfvOaA7geODDg2IeB/zLog5L2SVqVtHrkyJGZVC5VdemgN28T6TkAs2U18wAg6RTgWuC+mmOfAE4A/2nQ5yPirojYExF7du7cObuKJkj5wq+6dNAxcSoIrwQ2a7ttc/hvXAM8ERGHy4WSbgD+FnBl+FZyZorJ2t6GMFn5erc4Ptl5zay95hEA9lIZ/pF0NfAx4PKIeH0OdVhaxWRtkfdnY21AvqO79wQ2W15DA4CkA8AVwA5Jh4Dbge0AEXGnpHOAVeB0oJs/6nlxRByVdBpwFXBj5bSfAf4S8GA+BPGliPjZ6fxKVqaNHkD/nsAn8tv3SXoAvv6btdvQABARe4ccf4kBT/Hkd/dn1pR/36gVtMkUk7XF9o+9HkB29XY6aLPl5ZXAiSsma7uVdNAn1mPj+CTnNbP2cgBIXDFZW90TeH3CISBJ5NMIZtZSDgCJK4ZqenMAeQ8gDwArDSPASsfrAMzazgEgcZ2OSrmANj8FNNk6gKlV08wWwAEgccVYfXUIaNKngLwlpFn7OQAkrn8IiE1PAXlLSLPl5QCQuGzjliz527TXAbgHYNZuDgCJ8zoAMxvEASBxvTmA/iGg4+uTpoIQXY8BmbWaA0DiBiWD6y0Ea35edwDM2s0BIHEqTwJ32LQOwDuCmS0vB4DEFXfqRe7/3krgYh1Aw/N2vA7ArO0cABI3KB30pD0Ap4M2az8HgMT15gCKPYEr6wAa/gV4DsCs/RwAEtebA8j3BM7/j3sOwMwcABKX3alv3hN4fX3SdQBeCGbWdg4AievkaZsjYEViZdMcQLPzFjuNmVl7OQAkLntap3gMtJcKongKaKVhD6D4nFNCm7WXA0DiiqRt1SGgE1NIBQFOCGfWZg4AievNAUx3R7BO/kHPA5i1lwNA4ma5DgAcAMzazAEgcVJ/LiBVewATrAMAvBbArMUcABJX9ADWu4HyPYGl6c0BrHsSwKy1HAAS15E2jfd3JE5MmA66+JyHgMzaywEgcR1p03h/R5Ong9ZGAJi8jma2GA4AiZM2P/EjiRPdSXsA2XevAzBrLweAxJV7ACr1AHpbQjY/L7gHYNZmDgCJ63R6q357Q0Cbh4XGPq8fAzVrPQeAxGUTvpsngdcnXgfgSWCztnMASJxqLvblx0An2RMYvA7ArM0cABJXN95f7gFMngvIEcCsrYYGAEl3S1qT9PSA4xdJekTSMUm3lsovlPRk6euopJvzY2+T9KCkZ/Pvb53er2RlAx8D3ZgXaH5e8CSwWZuN0gO4B7h6i+OvADcB+8uFEXEwInZHxG7gUuB14P788G3AQxHxbuCh/LXNQPlufyW/2q90tLEhzErDCLCRDM4RwKy1tg17Q0Q8LGnXFsfXgDVJP77Faa4EnouI5/PX1wFX5D//OvBF4GPDq2vj6gheOvoG0BsCksTh145lPzPZENBPf/ZRtq94JNGsjYYGgCm5HjhQen12RLwIEBEvSjpr0Acl7QP2AZx//vkzrWSKrr/sPLatiO0rHS5/z04Afvbyd/H4869w+qnbeffZb2l03r/6rjP5yR96B8dOrE+zumY2A/91QLlGWcmZ9wA+HxGXbPGefwl8OyL2V8pPAV4AfiAiDudlr0bEGaX3/HlEDJ0H2LNnT6yurg6tr5mZ9Uh6PCL2VMvn0Xe/BniiuPjnDkt6e16xtwNrc6iHmZmVzCMA7KV/+Afgd4Eb8p9vAH5nDvUwM7OSoXMAkg6QTdjukHQIuB3YDhARd0o6B1gFTge6+aOeF0fEUUmnAVcBN1ZOewdwr6SPAF8HPjil38fMzEY0ylNAe4ccfwk4d8Cx14Eza8q/RfZkkJmZLYif3zMzW1IOAGZmS8oBwMxsSTkAmJktqZEWgp0sJL0GHJzhf+K7gb+Y4flnbQfw8qIrMYE2t7/bfrHc/lu7MCK+q1o4r1QQ03KwbjXbtEi6KyL2zer8syZpdZbtM2ttbn+3/WK5/YeevzaFgoeA+v3eoiuw5Nz+i+O2X6yFtL8DQElE+B/BArn9F8dtv1iLav+2BYC7Fl2Bk5zbZ3Hc9ovl9t9abfu0ahLYzMymp209gJFJulrSQUlfk3RbXvabpS0q/0zSkzWfO1XSH0l6StKXJf186Zi3shzRgPbfLelLefuvSrqs5nNu/wkNaPu/nG/d+ieSfk/S6TWfc9tPQd02uqO030LaPyKS+wJWgOeA7wVOAZ4iS1BXfs8vAp+s+ayAt+Q/bwceBd6Xv/4UcFv+823Av13073oyfg1qf+AB4Jr8PR8Avuj2n1vbPwZcnr/nw8C/ctvP7P/BjwLvBZ4ulQ1tv0W0f6o9gMuAr0XEn0bEm8B/JtuGEgBJAv4um9NUE5lv5y+351/FONl1ZFtYkn//idlUv/kd9KDP5uXzuosb1P5BljUWsueeX6h+8CRv/6F30YM+m5fPo/0Htf2FwMP5ex4E/nb1gydR2ze6g87ft+i/fSLiYbK90suGtt9C2n/R0XJGEfjvAP++9Pqngc9UIvRq6fX3AL9fer0CPAl8m1KkBV6t/Hf+fEb1n+QOemDvhzndxQ1qf+D7ydJ/fwP4JvDOlrX/KHfRC23/Ldr+fwHX5WX/BHjtZGz7/NxN76AX/rdfqsuuSv1r22/R7Z9qD6Bup/PybHffJjUR8UJEfKD0ej0idpOlub5M0sCtMGek8R30Fp+F+d3FDWr/jwK3RMR5wC3AZ6FV7T/0LnqLz8J82n9Q238Y+AeSHge+C3gTTsq2JxreQbP4th/bots/1QBwCDiv9Ppc8oulpG3ATwG/OewkEfEq8EXg6rxoXltZvoPsLrlwKC+7Gfi0pG8A+4GP53X5Hkm/P+SzAGdHxIsA+fezZlT/Qe1/A/C5vOw+sn+wA52E7f80cG1e9kHy3/Eka//ato+Ir0bE34yIS8lufp7b6iQLbPtBatvuJGv7rYzVfvNq/1QDwGPAuyVdoGxT+uvJtqEE+DHgqxFxqO6DknZKOiP/+TuK9+eH57WV5SR30MN6P/MwqP1fAC7P3/N+4NnqB0/y9h/lLnrR7V/b9pKKC2YH+BfAndUPniRtP5aTrO23MrT9FtH+SQaAiDgB/EPgC8BXgHsj4sv54eupTP5W7iLeDvyBpD8m+8f0YER8Pj92B3CVpGfJtrq8Y0a/wiR30AN7P8zpLm6L9v8Z4BclPQX8a2BfXpdWtP+Id9ELbf8t2n6vpGfILigvAL+W1+Nka/tBRmm7hf/t5+c/ADwCXCjpkLKtb2vbb+HtP8uJEH81nkDaBvwpcAG9yawfIPsHfUX+niuBx0f9bH7s0/RPhH1q0b/ryfi1RfuflR/vAL8BfNjtP7P/B7von0Qd2nZu+wbtvOgK+GvA/5jsKZ9nyO4yP5GX/TXg8fwP+1Hg0ry8+iTBps/m5WcCD5ENvTwEvG3Rv+fJ+jWg/f9xXvYM2R1YsZLe7T/dtj8AvAgcJ7ur/8igtnPbT/blVBBmZksqyTkAMzMbzgHAzGxJOQCYmS0pBwAzsyXlAGBmtqQcAMwakHSGpJ9bdD3MJuEAYNbMGYADgLXatkVXwKyl7gDepWxXucfIMoWeTvZv6qMR8T8WWTmzUXghmFkDknYBn4+ISyT9U+DUiPgFSSvAaRHx2kIraDYC9wDMJvcYcLek7cBvR8SmvabNTkaeAzCbUGQbmPwo2S5n/0HS31twlcxG4gBg1sxrZHsCIOmdwFpE/CrZHg3vXWTFzEblISCzBiLiW5L+Z75x+XcC/1fScbK9XN0DsFbwJLCZ2ZLyEJCZ2ZJyADAzW1IOAGZmS8oBwMxsSTkAmJktKQcAM7Ml5QBgZrak/j+E7X5T5La+rwAAAABJRU5ErkJggg==\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 |
--------------------------------------------------------------------------------