├── tests
├── __init__.py
├── test_auth.py
├── test_log.py
├── util.py
├── test_demo.py
└── test_strategy.py
├── MANIFEST.in
├── docs
├── img
│ └── cira.jpeg
├── news
│ └── v3_realse.md
└── docs.md
├── cira
├── asset_option.py
├── config.py
├── alpaca_utils.py
├── log.py
├── util.py
├── __init__.py
├── auth.py
├── asset_stock.py
├── portfolio.py
├── exchange.py
├── assset_cryptocurrency.py
├── strategy.py
└── asset.py
├── requirements.txt
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── build-test.yml
│ └── python-publish.yml
├── setup.py
├── examples
└── dev.ipynb
├── LICENSE.txt
├── .gitignore
└── README.md
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE.txt
2 |
--------------------------------------------------------------------------------
/docs/img/cira.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AxelGard/cira/HEAD/docs/img/cira.jpeg
--------------------------------------------------------------------------------
/tests/test_auth.py:
--------------------------------------------------------------------------------
1 | import cira
2 |
3 |
4 | def test_checking_keys():
5 | """To check if keys are correct we need walid keys"""
6 | assert not cira.auth.check_keys()
7 |
--------------------------------------------------------------------------------
/cira/asset_option.py:
--------------------------------------------------------------------------------
1 | import warnings
2 |
3 |
4 | class OptionContract:
5 | def __init__(self) -> None:
6 | warnings.warn("Options have not been implemented, comming soon")
7 |
--------------------------------------------------------------------------------
/cira/config.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | """
4 | This files keeps the varibale that is used
5 | for configuration cross cira.
6 | """
7 |
8 | # logging
9 | IS_LOGGING = False
10 | LOG_FILE = "./cira-log.csv"
11 | LOGGING_LEVEL = logging.WARNING
12 |
13 | # debugging
14 | DEBUG = False
15 |
16 | # paper trading
17 | PAPER_TRADING = True
18 |
19 | # data that will not very often change is cached
20 | USE_CASHING = True
21 |
--------------------------------------------------------------------------------
/cira/alpaca_utils.py:
--------------------------------------------------------------------------------
1 | import auth
2 | from alpaca.trading.client import TradingClient
3 | from .config import PAPER_TRADING
4 |
5 |
6 | def get_trading_client() -> TradingClient:
7 | """get the alpaca-sdk python trading class
8 | obj initalized with set config"""
9 | apca_id, apca_key = auth.get_api_keys()
10 | assert apca_id != "" and apca_key != "", "The keys for alpaca are not set "
11 | return TradingClient(apca_id, apca_key, paper=PAPER_TRADING)
12 |
--------------------------------------------------------------------------------
/cira/log.py:
--------------------------------------------------------------------------------
1 | import csv
2 | import time
3 | import logging
4 | from . import config
5 |
6 | """
7 | This functions is logging trades
8 | """
9 |
10 |
11 | def log(action: str, sym: str, qty: int) -> None:
12 | """writes log data to file"""
13 | time_ = time.strftime("%Y-%m-%d %H:%M", time.gmtime())
14 | log_data = [action, sym, qty, time_]
15 | with open(config.LOG_FILE, "a") as file:
16 | writer = csv.writer(file)
17 | writer.writerow(log_data)
18 |
--------------------------------------------------------------------------------
/cira/util.py:
--------------------------------------------------------------------------------
1 | def bars_to_dict(bars):
2 | result = []
3 | for bar in bars:
4 | result.append(
5 | {
6 | "open": float(bar.open),
7 | "high": float(bar.high),
8 | "low": float(bar.low),
9 | "close": float(bar.close),
10 | "time": bar.timestamp.strftime("%Y-%m-%d, %H:%M:%S"),
11 | "time_zone": str(bar.timestamp.tzinfo),
12 | "volume": float(bar.volume),
13 | }
14 | )
15 | return result
16 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | alpaca-py>=0.21.0
2 | black>=23.7.0
3 | ipykernel>=6.25.0
4 | ipython>=8.12.2
5 | jupyter-client>=8.3.0
6 | jupyter-core>=5.3.1
7 | matplotlib>=3.7.3
8 | matplotlib-inline>=0.1.6
9 | mypy-extensions>=1.0.0
10 | nest-asyncio>=1.5.6
11 | numpy>=1.24.4
12 | packaging>=23.1
13 | pandas>=1.3.5
14 | parso>=0.8.3
15 | Pillow>=10.0.0
16 | platformdirs>=3.9.1
17 | pytest>=6.2.5
18 | python-dateutil>=2.8.2
19 | requests>=2.31.0
20 | requests-toolbelt>=0.9.1
21 | schedule>=1.2.0
22 | scikit-learn>=1.3.0
23 | scipy>=1.10.1
24 | tqdm>=4.48.2
25 | twine>=3.2.0
26 | typing-extensions>=4.7.1
27 |
--------------------------------------------------------------------------------
/cira/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Cira
3 |
4 | A simpler libray for alpaca-trade-api from Alpaca Markets.
5 | """
6 |
7 | from . import auth
8 | from . import config
9 | from . import util
10 | from . import log
11 | from . import strategy
12 |
13 | # Assets
14 | from .asset import Asset
15 | from .assset_cryptocurrency import Cryptocurrency
16 | from .asset_stock import Stock
17 | from .asset_option import OptionContract
18 |
19 | from .portfolio import Portfolio, Position
20 | from .exchange import Exchange, DemoExchange
21 |
22 | import alpaca
23 |
24 | __version__ = "3.3.0"
25 | __author__ = "Axel Gard"
26 | __credits__ = "alpaca.markets"
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Screenshots**
20 | If applicable, add screenshots to help explain your problem.
21 |
22 | **Desktop (please complete the following information):**
23 | - OS: [e.g. Linux]
24 | - Version [e.g. 22]
25 |
26 | **Additional context**
27 | Add any other context about the problem here.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/tests/test_log.py:
--------------------------------------------------------------------------------
1 | import cira
2 | import csv
3 | import os
4 |
5 |
6 | def test_logging():
7 | """simple test for checking logging format"""
8 |
9 | cira.config.LOG_FILE = "./cira-test-log.csv"
10 | cira.config.IS_LOGGING = True
11 | cira.log.log("BUY", "SYM", 1)
12 | cira.log.log("SELL", "SYM", 2)
13 | with open(cira.config.LOG_FILE) as f:
14 | reader = csv.reader(f, delimiter=",")
15 | rows = [r for r in reader]
16 |
17 | assert len(rows) == 2
18 | assert len(rows[0]) == 4 and len(rows[1]) == 4
19 | assert rows[0][0] == "BUY" and rows[1][0] == "SELL"
20 | assert rows[0][1] == "SYM" and rows[1][1] == "SYM"
21 | assert rows[0][2] == "1" and rows[1][2] == "2"
22 |
23 | os.system(f"rm {cira.config.LOG_FILE}")
24 |
--------------------------------------------------------------------------------
/.github/workflows/build-test.yml:
--------------------------------------------------------------------------------
1 | name: Build and Test [Python 3.8]
2 |
3 |
4 | on:
5 | push:
6 | branches: [ master ]
7 | pull_request:
8 | branches: [ master, develop ]
9 |
10 | jobs:
11 | build:
12 |
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | python-version: [3.8]
17 | os: [ubuntu-latest]
18 |
19 | steps:
20 | - uses: actions/checkout@v2
21 | - name: Set up Python ${{ matrix.python-version }}
22 | uses: actions/setup-python@v1
23 | with:
24 | python-version: ${{ matrix.python-version }}
25 | - name: Install dependencies
26 | run: |
27 | python -m pip install --upgrade pip
28 | pip install pytest
29 | pip install -r requirements.txt
30 | pip install -e .
31 | - name: Test with pytest
32 | run: |
33 | export APCA_ID='${{ secrets.APCA_ID }}'
34 | export APCA_KEY='${{ secrets.APCA_KEY }}'
35 | pytest
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | with open("README.md", "r") as fh:
4 | long_description = fh.read()
5 |
6 | setup(
7 | name="cira",
8 | version="3.3.0",
9 | description="A simpler library for the alapaca trade api",
10 | url="https://github.com/AxelGard/cira",
11 | author="Axel Gard",
12 | author_email="axel.gard@tutanota.com",
13 | license="MIT",
14 | packages=["cira"],
15 | long_description=long_description,
16 | long_description_content_type="text/markdown",
17 | install_requires=[
18 | "alpaca-py==0.21.0",
19 | "schedule==1.2.0",
20 | "matplotlib",
21 | "pandas",
22 | "numpy",
23 | ],
24 | extras_requires={"dev": ["pytest"]},
25 | classifiers=[
26 | "Development Status :: 5 - Production/Stable",
27 | "Topic :: Office/Business :: Financial",
28 | "Programming Language :: Python :: 3.8",
29 | "License :: OSI Approved :: MIT License",
30 | ],
31 | )
32 |
--------------------------------------------------------------------------------
/examples/dev.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "code",
5 | "execution_count": 1,
6 | "metadata": {},
7 | "outputs": [],
8 | "source": [
9 | "import cira \n",
10 | "\n",
11 | "cira.auth.KEY_FILE = \"../../alpc_key.json\"\n",
12 | "assert cira.auth.check_keys(), \"the set keys dose not work\""
13 | ]
14 | },
15 | {
16 | "cell_type": "code",
17 | "execution_count": null,
18 | "metadata": {},
19 | "outputs": [],
20 | "source": []
21 | }
22 | ],
23 | "metadata": {
24 | "kernelspec": {
25 | "display_name": "env",
26 | "language": "python",
27 | "name": "python3"
28 | },
29 | "language_info": {
30 | "codemirror_mode": {
31 | "name": "ipython",
32 | "version": 3
33 | },
34 | "file_extension": ".py",
35 | "mimetype": "text/x-python",
36 | "name": "python",
37 | "nbconvert_exporter": "python",
38 | "pygments_lexer": "ipython3",
39 | "version": "3.10.12"
40 | }
41 | },
42 | "nbformat": 4,
43 | "nbformat_minor": 2
44 | }
45 |
--------------------------------------------------------------------------------
/.github/workflows/python-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflows will upload a Python Package using Twine when a release is created
2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3 |
4 | name: Upload Python Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | deploy:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Set up Python
17 | uses: actions/setup-python@v2
18 | with:
19 | python-version: '3.11.4'
20 | - name: Install dependencies
21 | run: |
22 | python -m pip install --upgrade pip
23 | pip install setuptools wheel twine
24 | - name: Build and publish
25 | env:
26 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
27 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
28 | run: |
29 | python setup.py sdist bdist_wheel
30 | twine upload --skip-existing --verbose dist/*
31 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Axel Gard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/cira/auth.py:
--------------------------------------------------------------------------------
1 | import json
2 | import warnings
3 | import os
4 |
5 | from alpaca.data import StockHistoricalDataClient, StockLatestQuoteRequest
6 |
7 | from .portfolio import Portfolio
8 |
9 |
10 | """
11 | This function let's you interact
12 | with the Alpaca trade API
13 | """
14 |
15 | KEY_FILE = "" # user key file path
16 | APCA_API_KEY_ID = ""
17 | APCA_API_SECRET_KEY = ""
18 |
19 |
20 | def get_api_keys():
21 | global KEY_FILE
22 | global APCA_API_KEY_ID
23 | global APCA_API_SECRET_KEY
24 |
25 | if "APCA_ID" in os.environ:
26 | APCA_ID = os.environ["APCA_ID"]
27 | APCA_KEY = os.environ["APCA_KEY"]
28 | elif KEY_FILE:
29 | auth_header = authentication_header()
30 | APCA_ID = str(auth_header["APCA-API-KEY-ID"])
31 | APCA_KEY = str(auth_header["APCA-API-SECRET-KEY"])
32 | else:
33 | APCA_ID = APCA_API_KEY_ID
34 | APCA_KEY = APCA_API_SECRET_KEY
35 |
36 | if not APCA_ID or not APCA_KEY:
37 | url = "https://github.com/AxelGard/cira/wiki/Storing-the-Alpaca-API-key"
38 | raise ValueError("Alpaca market keys were not given, " + url)
39 | return APCA_ID, APCA_KEY
40 |
41 |
42 | def check_keys() -> bool:
43 | try:
44 | APCA_ID, APCA_KEY = get_api_keys()
45 | stock_client = StockHistoricalDataClient(APCA_ID, APCA_KEY)
46 | perms = StockLatestQuoteRequest(symbol_or_symbols="SPY")
47 | ask = float(stock_client.get_stock_latest_quote(perms)["SPY"].ask_price)
48 |
49 | return True
50 | except:
51 | return False
52 |
53 |
54 | def authentication_header():
55 | """get's key and returns key in json format"""
56 | global KEY_FILE
57 | with open(KEY_FILE, "r") as file:
58 | header = json.load(file)
59 | return header
60 |
61 |
62 | def api(version="v2"):
63 | """returns object for api"""
64 | warnings.warn("This function has been deepracted by Alpaca Markets")
65 | return None
66 |
--------------------------------------------------------------------------------
/cira/asset_stock.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 |
3 |
4 | # Alpaca
5 | import alpaca
6 | from alpaca.trading.requests import GetAssetsRequest
7 | from alpaca.trading.enums import AssetClass, OrderType, AssetStatus
8 | from alpaca.data.models import Bar
9 | from alpaca.trading.enums import OrderSide, TimeInForce
10 | from alpaca.data.timeframe import TimeFrame
11 | from alpaca.trading.requests import LimitOrderRequest, StopLimitOrderRequest
12 | from alpaca.trading.client import TradingClient
13 |
14 |
15 | # stock
16 | from alpaca.data import StockHistoricalDataClient
17 | from alpaca.data.requests import StockLatestQuoteRequest
18 | from alpaca.data.requests import StockBarsRequest
19 | from alpaca.trading.requests import MarketOrderRequest
20 | from alpaca.data.live import StockDataStream
21 |
22 |
23 | # cira
24 | from .asset import Asset
25 | from . import auth
26 | from . import config
27 | from . import util
28 | from . import log
29 |
30 |
31 | class Stock(Asset):
32 | def __init__(self, symbol: str) -> None:
33 | """Exchange for trading stocks"""
34 | APCA_ID, APCA_SECRET = auth.get_api_keys()
35 | self.symbol = symbol
36 | self.live_client = StockDataStream(APCA_ID, APCA_SECRET)
37 | self.history = StockHistoricalDataClient(APCA_ID, APCA_SECRET)
38 | self.trade = TradingClient(APCA_ID, APCA_SECRET, paper=config.PAPER_TRADING)
39 | self.latest_quote_request = StockLatestQuoteRequest
40 | self.bars_request = StockBarsRequest
41 |
42 | def price(self) -> float:
43 | """gets the asking price of the symbol"""
44 | perms = self.latest_quote_request(symbol_or_symbols=self.symbol)
45 | return float(self.history.get_stock_latest_quote(perms)[self.symbol].ask_price)
46 |
47 | @classmethod
48 | def get_all_assets(self):
49 | APCA_ID, APCA_SECRET = auth.get_api_keys()
50 | trade = TradingClient(APCA_ID, APCA_SECRET, paper=config.PAPER_TRADING)
51 | search_params = GetAssetsRequest(
52 | asset_class=AssetClass.US_EQUITY, status=AssetStatus.ACTIVE
53 | )
54 | return [a.symbol for a in trade.get_all_assets(search_params)]
55 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #vscode
2 | .vscode/
3 |
4 | *.csv
5 |
6 | # test
7 | tests/test_key.json
8 | test_key.json
9 |
10 | temp.py
11 |
12 | #note
13 | noteToSelf.txt
14 |
15 | cira/env/
16 |
17 | # Byte-compiled / optimized / DLL files
18 | __pycache__/
19 | *.py[cod]
20 | *$py.class
21 |
22 | # C extensions
23 | *.so
24 |
25 | # Distribution / packaging
26 | .Python
27 | build/
28 | develop-eggs/
29 | dist/
30 | downloads/
31 | eggs/
32 | .eggs/
33 | lib/
34 | lib64/
35 | parts/
36 | sdist/
37 | var/
38 | wheels/
39 | pip-wheel-metadata/
40 | share/python-wheels/
41 | *.egg-info/
42 | .installed.cfg
43 | *.egg
44 | MANIFEST
45 |
46 | # PyInstaller
47 | # Usually these files are written by a python script from a template
48 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
49 | *.manifest
50 | *.spec
51 |
52 | # Installer logs
53 | pip-log.txt
54 | pip-delete-this-directory.txt
55 |
56 | # Unit test / coverage reports
57 | htmlcov/
58 | .tox/
59 | .nox/
60 | .coverage
61 | .coverage.*
62 | .cache
63 | nosetests.xml
64 | coverage.xml
65 | *.cover
66 | *.py,cover
67 | .hypothesis/
68 | .pytest_cache/
69 |
70 | # Translations
71 | *.mo
72 | *.pot
73 |
74 | # Django stuff:
75 | *.log
76 | local_settings.py
77 | db.sqlite3
78 | db.sqlite3-journal
79 |
80 | # Flask stuff:
81 | instance/
82 | .webassets-cache
83 |
84 | # Scrapy stuff:
85 | .scrapy
86 |
87 | # Sphinx documentation
88 | docs/_build/
89 |
90 | # PyBuilder
91 | target/
92 |
93 | # Jupyter Notebook
94 | .ipynb_checkpoints
95 |
96 | # IPython
97 | profile_default/
98 | ipython_config.py
99 |
100 | # pyenv
101 | .python-version
102 |
103 | # pipenv
104 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
105 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
106 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
107 | # install all needed dependencies.
108 | #Pipfile.lock
109 |
110 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
111 | __pypackages__/
112 |
113 | # Celery stuff
114 | celerybeat-schedule
115 | celerybeat.pid
116 |
117 | # SageMath parsed files
118 | *.sage.py
119 |
120 | # Environments
121 | .env
122 | .venv
123 | env/
124 | venv/
125 | ENV/
126 | env.bak/
127 | venv.bak/
128 |
129 | # Spyder project settings
130 | .spyderproject
131 | .spyproject
132 |
133 | # Rope project settings
134 | .ropeproject
135 |
136 | # mkdocs documentation
137 | /site
138 |
139 | # mypy
140 | .mypy_cache/
141 | .dmypy.json
142 | dmypy.json
143 |
144 | # Pyre type checker
145 | .pyre/
146 |
--------------------------------------------------------------------------------
/tests/util.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | from pandas import Timestamp
3 |
4 |
5 | stock_data = pd.DataFrame(
6 | {
7 | "symbol": {
8 | Timestamp("2016-01-04 05:00:00+0000", tz="UTC"): "MSFT",
9 | Timestamp("2016-01-05 05:00:00+0000", tz="UTC"): "MSFT",
10 | Timestamp("2016-01-06 05:00:00+0000", tz="UTC"): "MSFT",
11 | Timestamp("2016-01-07 05:00:00+0000", tz="UTC"): "MSFT",
12 | Timestamp("2016-01-08 05:00:00+0000", tz="UTC"): "MSFT",
13 | },
14 | "open": {
15 | Timestamp("2016-01-04 05:00:00+0000", tz="UTC"): 48.18,
16 | Timestamp("2016-01-05 05:00:00+0000", tz="UTC"): 48.72,
17 | Timestamp("2016-01-06 05:00:00+0000", tz="UTC"): 48.18,
18 | Timestamp("2016-01-07 05:00:00+0000", tz="UTC"): 46.75,
19 | Timestamp("2016-01-08 05:00:00+0000", tz="UTC"): 46.45,
20 | },
21 | "high": {
22 | Timestamp("2016-01-04 05:00:00+0000", tz="UTC"): 48.61,
23 | Timestamp("2016-01-05 05:00:00+0000", tz="UTC"): 49.13,
24 | Timestamp("2016-01-06 05:00:00+0000", tz="UTC"): 48.25,
25 | Timestamp("2016-01-07 05:00:00+0000", tz="UTC"): 47.44,
26 | Timestamp("2016-01-08 05:00:00+0000", tz="UTC"): 47.26,
27 | },
28 | "low": {
29 | Timestamp("2016-01-04 05:00:00+0000", tz="UTC"): 47.36,
30 | Timestamp("2016-01-05 05:00:00+0000", tz="UTC"): 48.38,
31 | Timestamp("2016-01-06 05:00:00+0000", tz="UTC"): 47.58,
32 | Timestamp("2016-01-07 05:00:00+0000", tz="UTC"): 46.19,
33 | Timestamp("2016-01-08 05:00:00+0000", tz="UTC"): 46.26,
34 | },
35 | "close": {
36 | Timestamp("2016-01-04 05:00:00+0000", tz="UTC"): 48.61,
37 | Timestamp("2016-01-05 05:00:00+0000", tz="UTC"): 48.83,
38 | Timestamp("2016-01-06 05:00:00+0000", tz="UTC"): 47.94,
39 | Timestamp("2016-01-07 05:00:00+0000", tz="UTC"): 46.28,
40 | Timestamp("2016-01-08 05:00:00+0000", tz="UTC"): 46.42,
41 | },
42 | "volume": {
43 | Timestamp("2016-01-04 05:00:00+0000", tz="UTC"): 59379610.0,
44 | Timestamp("2016-01-05 05:00:00+0000", tz="UTC"): 36457804.0,
45 | Timestamp("2016-01-06 05:00:00+0000", tz="UTC"): 41899187.0,
46 | Timestamp("2016-01-07 05:00:00+0000", tz="UTC"): 61897908.0,
47 | Timestamp("2016-01-08 05:00:00+0000", tz="UTC"): 52260304.0,
48 | },
49 | "trade_count": {
50 | Timestamp("2016-01-04 05:00:00+0000", tz="UTC"): 272783.0,
51 | Timestamp("2016-01-05 05:00:00+0000", tz="UTC"): 180639.0,
52 | Timestamp("2016-01-06 05:00:00+0000", tz="UTC"): 225858.0,
53 | Timestamp("2016-01-07 05:00:00+0000", tz="UTC"): 303273.0,
54 | Timestamp("2016-01-08 05:00:00+0000", tz="UTC"): 261924.0,
55 | },
56 | "vwap": {
57 | Timestamp("2016-01-04 05:00:00+0000", tz="UTC"): 48.05,
58 | Timestamp("2016-01-05 05:00:00+0000", tz="UTC"): 48.83,
59 | Timestamp("2016-01-06 05:00:00+0000", tz="UTC"): 47.99,
60 | Timestamp("2016-01-07 05:00:00+0000", tz="UTC"): 46.64,
61 | Timestamp("2016-01-08 05:00:00+0000", tz="UTC"): 46.74,
62 | },
63 | }
64 | )
65 |
--------------------------------------------------------------------------------
/tests/test_demo.py:
--------------------------------------------------------------------------------
1 | import cira
2 | from datetime import datetime
3 | from pandas import Timestamp
4 | import numpy as np
5 | import pandas as pd
6 |
7 | def test_demo():
8 | #cira.auth.KEY_FILE = "" # NO KEY
9 | #assert not cira.auth.check_keys()
10 |
11 | #SYMBOL = "BTC/USD"
12 | #ast = cira.Cryptocurrency(SYMBOL)
13 | #assert ast.price() > 0.0 # send requst to alpaca but small
14 |
15 | data = pd.DataFrame({'symbol': {Timestamp('2023-07-01 05:00:00+0000', tz='UTC'): 'BTC/USD', Timestamp('2023-07-02 05:00:00+0000', tz='UTC'): 'BTC/USD', Timestamp('2023-07-03 05:00:00+0000', tz='UTC'): 'BTC/USD', Timestamp('2023-07-04 05:00:00+0000', tz='UTC'): 'BTC/USD', Timestamp('2023-07-05 05:00:00+0000', tz='UTC'): 'BTC/USD'}, 'open': {Timestamp('2023-07-01 05:00:00+0000', tz='UTC'): 30385.895956, Timestamp('2023-07-02 05:00:00+0000', tz='UTC'): 30528.37, Timestamp('2023-07-03 05:00:00+0000', tz='UTC'): 30697.0715105075, Timestamp('2023-07-04 05:00:00+0000', tz='UTC'): 31063.25735, Timestamp('2023-07-05 05:00:00+0000', tz='UTC'): 30841.5055}, 'high': {Timestamp('2023-07-01 05:00:00+0000', tz='UTC'): 30710.060945, Timestamp('2023-07-02 05:00:00+0000', tz='UTC'): 30813.870461679, Timestamp('2023-07-03 05:00:00+0000', tz='UTC'): 31390.039548221, Timestamp('2023-07-04 05:00:00+0000', tz='UTC'): 31123.8403, Timestamp('2023-07-05 05:00:00+0000', tz='UTC'): 30883.247817}, 'low': {Timestamp('2023-07-01 05:00:00+0000', tz='UTC'): 30374.97, Timestamp('2023-07-02 05:00:00+0000', tz='UTC'): 30231.36435, Timestamp('2023-07-03 05:00:00+0000', tz='UTC'): 30485.7, Timestamp('2023-07-04 05:00:00+0000', tz='UTC'): 30629.9067, Timestamp('2023-07-05 05:00:00+0000', tz='UTC'): 30204.959}, 'close': {Timestamp('2023-07-01 05:00:00+0000', tz='UTC'): 30529.743525, Timestamp('2023-07-02 05:00:00+0000', tz='UTC'): 30701.6205105075, Timestamp('2023-07-03 05:00:00+0000', tz='UTC'): 31056.9905094905, Timestamp('2023-07-04 05:00:00+0000', tz='UTC'): 30842.05240964, Timestamp('2023-07-05 05:00:00+0000', tz='UTC'): 30457.98348348}, 'volume': {Timestamp('2023-07-01 05:00:00+0000', tz='UTC'): 0.544617957, Timestamp('2023-07-02 05:00:00+0000', tz='UTC'): 1.010968445, Timestamp('2023-07-03 05:00:00+0000', tz='UTC'): 1.339711015, Timestamp('2023-07-04 05:00:00+0000', tz='UTC'): 0.421326377, Timestamp('2023-07-05 05:00:00+0000', tz='UTC'): 1.431555108}, 'trade_count': {Timestamp('2023-07-01 05:00:00+0000', tz='UTC'): 100.0, Timestamp('2023-07-02 05:00:00+0000', tz='UTC'): 104.0, Timestamp('2023-07-03 05:00:00+0000', tz='UTC'): 197.0, Timestamp('2023-07-04 05:00:00+0000', tz='UTC'): 79.0, Timestamp('2023-07-05 05:00:00+0000', tz='UTC'): 364.0}, 'vwap': {Timestamp('2023-07-01 05:00:00+0000', tz='UTC'): 30565.780444418, Timestamp('2023-07-02 05:00:00+0000', tz='UTC'): 30540.5968780593, Timestamp('2023-07-03 05:00:00+0000', tz='UTC'): 30950.9446485936, Timestamp('2023-07-04 05:00:00+0000', tz='UTC'): 30957.1352892724, Timestamp('2023-07-05 05:00:00+0000', tz='UTC'): 30443.6923107624}})
16 | #data = ast.historical_data_df(datetime(2023, 7, 1), datetime(2023, 7, 6)) # to not request data for each , but should be the same result
17 | assert data.shape == (5, 8)
18 | assert data.keys().to_list() == ['symbol', 'open', 'high', 'low', 'close', 'volume', 'trade_count', 'vwap']
19 |
20 | strat = cira.strategy.Randomness()
21 | bt = cira.strategy.back_test_against_buy_and_hold(strat, data, data["open"].to_frame(), 100_000, True)
22 | assert np.allclose(bt[cira.strategy.ByAndHold().name].head(3).to_numpy(), np.array([99635.369248528, 100062.791380528, 100568.8959120505]))
23 |
--------------------------------------------------------------------------------
/tests/test_strategy.py:
--------------------------------------------------------------------------------
1 | import cira
2 | from . import util
3 | import os
4 | import numpy as np
5 | import pandas as pd
6 |
7 |
8 | def test_iterate():
9 | feature_data = util.stock_data
10 | strat = cira.strategy.DollarCostAveraging(amount=1)
11 | prices = feature_data["close"].to_frame()
12 | change_in_portfolio = strat.iterate(feature_data, prices.iloc[-1], 10_000)
13 | assert change_in_portfolio.tolist() == [1]
14 |
15 |
16 | def test_storing_strategy():
17 | CHECK = "this should be in the strategy"
18 | FILE = "./my_strat.pkl"
19 |
20 | strat = cira.strategy.Strategy("my_strat")
21 | strat.test_name = CHECK
22 | strat.save(FILE)
23 | new_strat = cira.strategy.Strategy.load(FILE)
24 | assert CHECK == new_strat.test_name
25 |
26 | os.system(f"rm {FILE}")
27 |
28 |
29 | def test_backtest():
30 | feature_data = util.stock_data
31 | strat = cira.strategy.DollarCostAveraging(amount=1)
32 | prices = feature_data["close"].to_frame()
33 | prices["close"] = [10, 10, 5, 20, 10]
34 |
35 | resutlt = cira.strategy.back_test(strat, feature_data, prices, 20, use_fees=False)
36 |
37 | res = resutlt[strat.name].values.astype(int).tolist()
38 | assert res == [20, 20, 10, 40, 20]
39 |
40 |
41 | def test_backtest_float():
42 | feature_data = util.stock_data
43 | strat = cira.strategy.DollarCostAveraging(amount=0.5)
44 | prices = feature_data["close"].to_frame()
45 | prices["close"] = [10, 10, 5, 20, 10]
46 |
47 | resutlt = cira.strategy.back_test(strat, feature_data, prices, 10, use_fees=False)
48 |
49 | res = resutlt[strat.name].values.astype(int).tolist()
50 | assert res == [10, 10, 5, 20, 10]
51 |
52 |
53 | def test_backtest_fees():
54 | feature_data = util.stock_data
55 | strat = cira.strategy.DollarCostAveraging(amount=1)
56 | prices = feature_data["close"].to_frame()
57 | prices["close"] = [10, 10, 10, 10, 10]
58 |
59 | cira.strategy.FEE_RATE = 0.1
60 | resutlt = cira.strategy.back_test(strat, feature_data, prices, 100, use_fees=True)
61 |
62 | res = resutlt[strat.name].values.astype(int).tolist()
63 | assert res == [99, 98, 97, 96, 95]
64 |
65 |
66 | def test_backtest_multi_asset_uniform_prices():
67 | feature_data = util.stock_data
68 | strat = cira.strategy.DollarCostAveraging(amount=1)
69 |
70 | prices = pd.DataFrame()
71 | prices["ast_1"] = [10, 10, 5, 20, 10]
72 | prices["ast_2"] = [10, 10, 5, 20, 10]
73 | prices["ast_3"] = [10, 10, 5, 20, 10]
74 | prices["ast_4"] = [10, 10, 5, 20, 10]
75 |
76 | resutlt = cira.strategy.back_test(strat, feature_data, prices, 40, use_fees=False)
77 |
78 | res = resutlt[strat.name].values.astype(int).tolist()
79 | assert res == [40, 40, 20, 80, 40]
80 |
81 |
82 | def test_backtest_multi_asset():
83 | feature_data = util.stock_data
84 | strat = cira.strategy.DollarCostAveraging(amount=1)
85 |
86 | prices = pd.DataFrame()
87 | prices["ast_1"] = [10, 10, 1, 0, 0]
88 | prices["ast_2"] = [10, 11, 5, 0, 0]
89 | prices["ast_3"] = [10, 10, 1, 0, 0]
90 | prices["ast_4"] = [10, 14, 7, 99, 0]
91 |
92 | resutlt = cira.strategy.back_test(strat, feature_data, prices, 40, use_fees=False)
93 |
94 | res = resutlt[strat.name].values.astype(int).tolist()
95 | assert res == [40, 45, 14, 99, 0]
96 |
97 |
98 | def test_backtest_not_enugh_cash():
99 | feature_data = util.stock_data
100 | strat = cira.strategy.DollarCostAveraging(amount=1)
101 | prices = feature_data["close"].to_frame()
102 | prices["close"] = [10, 11, 12, 13, 14]
103 |
104 | resutlt = cira.strategy.back_test(strat, feature_data, prices, 9, use_fees=True)
105 |
106 | res = resutlt[strat.name].values.astype(int).tolist()
107 | assert res == [9, 9, 9, 9, 9]
108 |
109 |
110 | def test_backtest_sell_no_allocation():
111 | feature_data = util.stock_data
112 | strat = cira.strategy.DollarCostAveraging(amount=-1)
113 | prices = feature_data["close"].to_frame()
114 | prices["close"] = [10, 11, 12, 13, 14]
115 |
116 | resutlt = cira.strategy.back_test(strat, feature_data, prices, 100, use_fees=True)
117 |
118 | res = resutlt[strat.name].values.astype(int).tolist()
119 | assert res == [100, 100, 100, 100, 100]
--------------------------------------------------------------------------------
/docs/news/v3_realse.md:
--------------------------------------------------------------------------------
1 |
2 | # cira v3.0.0 is out!
3 |
4 | After a long time of almost no updates v3 is finaly out.
5 | V3 brings some much needed features to the cira library.
6 |
7 | Sadly there are some breaking changes.
8 | But they should be easly fixed.
9 |
10 | ## cira v2 vs v3
11 |
12 | most code from cira v2 will still work the only change that might be needed is that some things that was prevusaly a class `@property` such as price is
13 | now a accessed through a function call to make it more clear when the api is called. This also makes the docutmention easyer to read.
14 |
15 | So in cira **v2**
16 |
17 | ```python
18 | import cira
19 | stock = cira.Stock("TSLA")
20 | print(stock.price)
21 | ```
22 | Now in cira **v3**
23 | ```python
24 | import cira
25 | stock = cira.Stock("TSLA")
26 | print(stock.price())
27 | ```
28 |
29 | ### Alpaca file is now in auth
30 |
31 | there is now also a function for checking that you key is working.
32 |
33 | In **v2**
34 | ```python
35 | import cira
36 | cira.alpaca.KEY_FILE = "../mypath/key.json"
37 | stock = cira.Stock("TSLA")
38 | ```
39 |
40 | but now in **v3**:
41 |
42 | ```python
43 | import cira
44 | cira.auth.KEY_FILE = "../mypath/key.json"
45 | assert cira.auth.check_keys(), "the set keys dose not work"
46 | ```
47 |
48 |
49 | ## Cira Strategies
50 |
51 | A new module for cira v3 is strategies.
52 | The cira strategies lets you backtest the models and set them into production in a simple way.
53 |
54 | the strategies have some sub modules.
55 |
56 | * [strategy class](../../cira/strategy/strategy.py), for you model
57 | * [backtest](../../cira/strategy/backtest.py), for checking how your model preformce on historical data.
58 | * [scheduler](../../cira/strategy/scheduler.py), for scheduling of execution.
59 |
60 |
61 | An **full example** of how to use the strategy is [example/linear](../../examples/linear.ipynb).
62 |
63 |
64 | ```python
65 | from cira.strategy import Strategy
66 |
67 | class MyStrat(Strategy):
68 | def __init__(self) -> None:
69 | super().__init__(name="MyStrat")
70 |
71 | def iterate(self, feature_data: DataFrame, prices: DataFrame, portfolio: np.ndarray, cash:float) -> np.ndarray:
72 | # this mehod will be called for each row of data in the backtest
73 | # the function should return the change of your portfolio.
74 | # -1 means sell one stock, 0 means hold, 1 means buy one stock
75 | return np.array([ portfolio_change_as_int ])
76 | ```
77 |
78 | cira comes with two strategies:
79 |
80 | * Randomness, which is will just return random values for each iteration
81 | * BuyAndHold, which will buy as much as possible in the frist iteration then hold.
82 |
83 | more might be added later on.
84 |
85 | ### backtest
86 |
87 | with cira v3 there is a new backtest function.
88 | there are some in v3.0.0 three types of backtests:
89 |
90 | * backtest
91 | * backtest against buy and hold strategi
92 | * multi strategy backtest
93 |
94 | a example of how to use them is:
95 |
96 | ```python
97 | import cira
98 | from cira.strategy.strategy import Randomness
99 | from cira.strategy.backtest import back_test
100 | from datetime import datetime
101 | import pandas as pd
102 |
103 | cira.auth.KEY_FILE = "../../alpc_key.json"
104 | assert cira.auth.check_keys(), "the set keys dose not work"
105 |
106 | stock = cira.Stock("AAPL")
107 | df = stock.historical_data_df(datetime(2022, 1, 1), datetime(2024, 1, 1))
108 | prices = pd.DataFrame()
109 | prices["AAPL"] = df["close"]
110 |
111 | strat = Randomness(-10,10, seed=23323)
112 | bt = back_test(strat, df.copy(), prices.copy(), 10_000, True)
113 | bt.plot()
114 | ```
115 |
116 | If you want more full example of how to use the backtest checkout
117 | [multiassets](../../examples/multi_assets.ipynb) and
118 | [linear](../../examples/linear.ipynb).
119 |
120 |
121 | ## cryptocurncies
122 |
123 | Alpaca.py have support for cryptob but in cira v3.0.0 the crypto support is very minmal.
124 |
125 | More suppor is comming...
126 |
127 | ## Assets class / Stock class
128 |
129 | There are new methods in the assets classes such as:
130 |
131 | ```python
132 | import cira
133 | from datetime import datetime
134 |
135 | stock = cira.Stock("TSLA")
136 | df = stock.historical_data_df(datetime(2019, 1, 1), datetime(2024, 1, 1))
137 | df # df will be a pandas data frame, were he index is a timestamp
138 | ```
--------------------------------------------------------------------------------
/cira/portfolio.py:
--------------------------------------------------------------------------------
1 | from typing import List, Dict
2 | import warnings
3 | from alpaca.trading.client import TradingClient
4 | from . import auth
5 | from . import config
6 | from .asset_stock import Stock
7 |
8 |
9 | class Position:
10 | def __init__(self, symbol) -> None:
11 | APCA_ID, APCA_SECRET = auth.get_api_keys()
12 | self.client = TradingClient(APCA_ID, APCA_SECRET, paper=config.PAPER_TRADING)
13 | self.symbol = symbol
14 |
15 | def quantity(self) -> int:
16 | """returns the number of the assets that is owned"""
17 | qty: int = 0
18 | try:
19 | qty = int(self.client.get_open_position(self.symbol).qty)
20 | except:
21 | qty = 0
22 | return qty
23 |
24 | def market_value(self) -> float:
25 | """Returns market value of symbol in portfolio"""
26 | return float(self.client.get_open_position(self.symbol).market_value)
27 |
28 | def to_dict(self) -> dict:
29 | """Returns a dict of the position"""
30 | return {
31 | "symbol": self.symbol,
32 | "market_value": self.market_value(),
33 | "quantity": self.quantity(),
34 | }
35 |
36 | def __str__(self) -> str:
37 | return f"({self.symbol}, {self.quantity()})"
38 |
39 | def __repr__(self) -> str:
40 | return f"({self.symbol}, {self.quantity()})"
41 |
42 |
43 | class Portfolio:
44 | def __init__(self) -> None:
45 | APCA_ID, APCA_SECRET = auth.get_api_keys()
46 | self.trading = TradingClient(APCA_ID, APCA_SECRET, paper=config.PAPER_TRADING)
47 | self.account = self.trading.get_account()
48 | self.positions: List[Position] = []
49 | self.account.portfolio_value
50 |
51 | def total_value(self) -> float:
52 | return float(self.account.portfolio_value)
53 |
54 | def is_blocked(self) -> bool:
55 | return self.account.account_blocked
56 |
57 | def buying_power(self) -> float:
58 | """gets the amount of cash currently available"""
59 | return float(self.account.buying_power)
60 |
61 | def cash(self) -> float:
62 | """gets the amount of cash currently available"""
63 | return float(self.account.cash)
64 |
65 | def equity(self) -> float:
66 | """returns the amount of equity that users has"""
67 | return float(self.account.equity)
68 |
69 | def equity_yesterday(self) -> float:
70 | """returns the amount of equity that was
71 | available at market close yesterday"""
72 | return float(self.account.last_equity)
73 |
74 | def equity_change(self):
75 | """returns the change in equity from yesterday to now"""
76 | return self.equity() - self.equity_yesterday()
77 |
78 | def all_positions(self) -> List[Position]:
79 | """Returns all positions of portfolio"""
80 | positions = self.trading.get_all_positions()
81 | self.positions = []
82 | for p in positions:
83 | self.positions.append(Position(p.symbol))
84 | return self.positions
85 |
86 | def close_all_positions(self) -> None:
87 | """WARNING: This closes all your open positions"""
88 | warnings.warn("Warning: will close all open positions ")
89 | self.trading.close_all_positions(cancel_orders=True)
90 |
91 | def position_in(self, symbol: str) -> Position:
92 | return Position(symbol)
93 |
94 | def get_allocation(self, symbol: str) -> int:
95 | return Position(symbol).quantity()
96 |
97 | def cancel_all_orders(self) -> None:
98 | self.trading.cancel_orders()
99 |
100 | def sell_list(self, symbols: List[str]) -> None:
101 | """takes a list of Stocks and sells all stocks in that list"""
102 | for symbol in symbols:
103 | q = self.position_in(symbol).quantity()
104 | if q == 0:
105 | continue
106 | stk = Stock(symbol=symbol)
107 | stk.sell(q)
108 |
109 | def owned_stock_qty(self, symbol: str) -> int: # maby shuld be in stock.Stock
110 | """returns quantity of owned of a stock Stock (obj)"""
111 | assert isinstance(symbol, str), "symbol needs to be string"
112 | return Position(symbol).quantity()
113 |
114 | def owned_stocks_qty(self) -> Dict[str, int]:
115 | positions = self.trading.get_all_positions()
116 | result = {}
117 | for p in positions:
118 | result[p.symbol] = Position(p.symbol).quantity()
119 | return result
120 |
121 | def owned_stocks(self) -> List[Stock]:
122 | """returns a list of owned stocks"""
123 | return [Stock(p.symbol) for p in self.all_positions()]
124 |
125 | def __repr__(self):
126 | return f"portfolio({self.equity()})"
127 |
128 | def __str__(self):
129 | return f"{self.all_positions()}"
130 |
--------------------------------------------------------------------------------
/cira/exchange.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 |
3 | import alpaca.trading
4 | import alpaca.trading.enums
5 | import alpaca.trading.requests
6 | from . import asset
7 | from . import auth
8 | from . import config
9 | from alpaca.trading.client import TradingClient
10 | from alpaca.trading.requests import GetAssetsRequest
11 | from alpaca.data import CryptoHistoricalDataClient, StockHistoricalDataClient
12 | from alpaca.trading.enums import AssetClass
13 | from alpaca.trading.models import Clock
14 | from .asset_stock import Stock
15 | import alpaca
16 | import warnings
17 | import datetime
18 |
19 |
20 | class Exchange:
21 | def __init__(self) -> None:
22 | APCA_ID, APCA_SECRET = auth.get_api_keys()
23 | self.alpc_client = TradingClient(APCA_ID, APCA_SECRET)
24 | self.alpc_historical = StockHistoricalDataClient(APCA_ID, APCA_SECRET)
25 | self.stock_cache: List[Stock] = []
26 |
27 | def is_open(self) -> bool:
28 | """Checks if the exchange is open and able to trade"""
29 | return bool(self.alpc_client.get_clock().is_open)
30 |
31 | def to_assets(self, symbols: List[str]) -> List[asset.Asset]:
32 | """Takes a list of symbols and returns
33 | them in a list of cira Assets objects"""
34 | return [self.to_asset(s) for s in symbols]
35 |
36 | def to_asset(self, symbol: str) -> asset.Asset:
37 | """Takes a symbols and returns
38 | it as a cira Assets objects"""
39 | return Stock(symbol)
40 |
41 | def get_all_stocks(
42 | self, is_tradeable: bool = True, force_reload: bool = False
43 | ) -> List[asset.Asset]:
44 | """Returns a list of all stocks as cira asset,
45 | objects will be cached, can be turn off in config."""
46 | if config.USE_CASHING and self.stock_cache != [] and not force_reload:
47 | return self.stock_cache
48 | search_params = GetAssetsRequest(asset_class=AssetClass.US_EQUITY)
49 | alpc_assets = self.alpc_client.get_all_assets(search_params)
50 | self.stock_cache = [
51 | Stock(a.symbol) for a in alpc_assets if a.tradable == is_tradeable
52 | ]
53 | return self.stock_cache
54 |
55 | def calendar(
56 | self, start: datetime.date = None, end: datetime.date = None
57 | ) -> List[alpaca.trading.Calendar]:
58 | _calendar: List[alpaca.trading.Calendar] = None
59 | if start and end:
60 | req = alpaca.trading.requests.GetCalendarRequest(start=start, end=end)
61 | _calendar = TradingClient.get_calendar(filters=req)
62 | else:
63 | _calendar = TradingClient.get_calendar()
64 | return _calendar
65 |
66 | def assets_raw(self):
67 | """(legacy, should not be used)
68 | returns a list of all avilabel stocks in exchanges list"""
69 | warnings.warn(
70 | f"Warning: function is deprecated ({self.assets_raw}), will return {None}"
71 | )
72 | return None
73 |
74 | def symbols_stocks(self) -> List[str]:
75 | """returns a list of all symbols (stocks)"""
76 | _req = alpaca.trading.GetAssetsRequest(
77 | status=alpaca.trading.enums.AssetStatus.ACTIVE,
78 | asset_class=alpaca.trading.enums.AssetClass.US_EQUITY,
79 | )
80 | all_asts = self.alpc_client.get_all_assets(_req)
81 | return [ast.symbol for ast in all_asts]
82 |
83 | def symbols_crypto(self) -> List[str]:
84 | """returns a list of all symbols (crypto)"""
85 | _req = alpaca.trading.GetAssetsRequest(
86 | status=alpaca.trading.enums.AssetStatus.ACTIVE,
87 | asset_class=alpaca.trading.enums.AssetClass.CRYPTO,
88 | )
89 | all_asts = self.alpc_client.get_all_assets(_req)
90 | return [ast.symbol for ast in all_asts]
91 |
92 | def symbols_options(self) -> List[str]:
93 | """returns a list of all symbols (crypto)"""
94 | _req = alpaca.trading.GetAssetsRequest(
95 | status=alpaca.trading.enums.AssetStatus.ACTIVE,
96 | asset_class=alpaca.trading.enums.AssetClass.US_OPTION,
97 | )
98 | all_asts = self.alpc_client.get_all_assets(_req)
99 | return [ast.symbol for ast in all_asts]
100 |
101 | def symbols(self) -> List[str]:
102 | """returns a list of all symbols"""
103 | _req = alpaca.trading.GetAssetsRequest(
104 | status=alpaca.trading.enums.AssetStatus.ACTIVE
105 | )
106 | all_asts = self.alpc_client.get_all_assets(_req)
107 | return [ast.symbol for ast in all_asts]
108 |
109 |
110 | class DemoExchange(Exchange):
111 | def __init__(self) -> None:
112 | """uses crypto client, so no need for keys, has limited usage"""
113 | warnings.warn("Warning: demo exchange has limited usage ")
114 | self.alpc_historical = CryptoHistoricalDataClient()
115 |
--------------------------------------------------------------------------------
/cira/assset_cryptocurrency.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 | from datetime import datetime
3 | import logging
4 | import warnings
5 |
6 | # Alpaca
7 | import alpaca
8 | from alpaca.trading.requests import GetAssetsRequest
9 | from alpaca.trading.enums import AssetClass, OrderType, AssetStatus
10 | from alpaca.data.models import Bar
11 | from alpaca.trading.enums import OrderSide, TimeInForce
12 | from alpaca.data.timeframe import TimeFrame
13 | from alpaca.trading.requests import LimitOrderRequest, StopLimitOrderRequest
14 | from alpaca.trading.client import TradingClient
15 |
16 | # stock
17 | from alpaca.data import StockHistoricalDataClient
18 | from alpaca.data.requests import StockLatestQuoteRequest
19 | from alpaca.data.requests import StockBarsRequest
20 | from alpaca.trading.requests import MarketOrderRequest
21 | from alpaca.data.live import StockDataStream
22 |
23 | # crypto
24 | from alpaca.data import CryptoHistoricalDataClient
25 | from alpaca.data.live.crypto import CryptoDataStream
26 | from alpaca.data.requests import CryptoLatestQuoteRequest
27 | from alpaca.data.requests import CryptoBarsRequest
28 |
29 | import pandas as pd
30 |
31 | from . import auth
32 | from . import config
33 | from . import util
34 | from . import log
35 |
36 | from .asset import Asset
37 |
38 |
39 | class Cryptocurrency(Asset):
40 | def __init__(self, symbol: str) -> None:
41 | """Exchange for trading cryptocurrencies"""
42 | try:
43 | APCA_ID, APCA_SECRET = auth.get_api_keys()
44 | except ValueError:
45 | APCA_ID, APCA_SECRET = "", ""
46 | self.symbol = symbol
47 | self.live_client = CryptoDataStream(APCA_ID, APCA_SECRET)
48 | self.history: CryptoHistoricalDataClient = CryptoHistoricalDataClient(
49 | None, None
50 | )
51 | if APCA_ID != "" and APCA_SECRET != "":
52 | self.history = CryptoHistoricalDataClient(APCA_ID, APCA_SECRET)
53 | self.trade = TradingClient(APCA_ID, APCA_SECRET, paper=config.PAPER_TRADING)
54 | self.latest_quote_request = CryptoLatestQuoteRequest
55 | self.bars_request = CryptoBarsRequest
56 |
57 | def price(self) -> float:
58 | """gets the asking price of the symbol"""
59 | perms = self.latest_quote_request(symbol_or_symbols=self.symbol)
60 | return float(self.history.get_crypto_latest_quote(perms)[self.symbol].ask_price)
61 |
62 | def buy(self, qty: float) -> None:
63 | """Buy the asset,
64 | qty is the number of the asset that you buy"""
65 | market_order = MarketOrderRequest(
66 | symbol=self.symbol,
67 | qty=qty,
68 | side=OrderSide.BUY,
69 | type=OrderType.MARKET,
70 | time_in_force=TimeInForce.GTC,
71 | )
72 | if config.IS_LOGGING:
73 | log.log("BUY", self.symbol, qty)
74 | self.trade.submit_order(market_order)
75 |
76 | def buy_within(self, qty: float, buy_at: float, sell_at: float) -> None:
77 | req = StopLimitOrderRequest(
78 | symbol=self.symbol,
79 | qty=qty,
80 | side=OrderSide.BUY,
81 | time_in_force=TimeInForce.GTC,
82 | limit_price=buy_at,
83 | stop_price=sell_at,
84 | )
85 | self.trade.submit_order(req)
86 |
87 | def buy_at(self, qty: int, price: float) -> None:
88 | """Buy the asset at a given price,
89 | qty is the number of the asset that you buy"""
90 | limit_order_data = LimitOrderRequest(
91 | symbol=self.symbol,
92 | limit_price=price,
93 | qty=qty,
94 | side=OrderSide.BUY,
95 | time_in_force=TimeInForce.GTC,
96 | )
97 | if config.IS_LOGGING:
98 | log.log("BUY", self.symbol, qty)
99 | self.trade.submit_order(order_data=limit_order_data)
100 |
101 | def sell(self, qty: float) -> None:
102 | """Sell the asset,
103 | qty is the number of the asset that you sell"""
104 | market_order = MarketOrderRequest(
105 | symbol=self.symbol,
106 | qty=qty,
107 | side=OrderSide.SELL,
108 | type=OrderType.MARKET,
109 | time_in_force=TimeInForce.GTC,
110 | )
111 | logging.info(f"sell:{self.symbol}, qty:{qty}")
112 | if config.IS_LOGGING:
113 | log.log("SELL", self.symbol, qty)
114 | self.trade.submit_order(market_order)
115 |
116 | def sell_at(self, qty: int, price: float) -> None:
117 | """Sell the asset at a given price,
118 | qty is the number of the asset that you sell"""
119 | limit_order_data = LimitOrderRequest(
120 | symbol=self.symbol,
121 | limit_price=price,
122 | qty=qty,
123 | side=OrderSide.SELL,
124 | time_in_force=TimeInForce.GTC,
125 | )
126 | if config.IS_LOGGING:
127 | log.log("SELL", self.symbol, qty)
128 | self.trade.submit_order(order_data=limit_order_data)
129 |
130 | def _get_bars(self, start_date: datetime, end_date: datetime):
131 | """returns aplc bars from the given dates"""
132 | params = self.bars_request(
133 | symbol_or_symbols=self.symbol,
134 | timeframe=TimeFrame.Day,
135 | start=start_date,
136 | end=end_date,
137 | adjustment="all",
138 | )
139 | return self.history.get_crypto_bars(params)
140 |
141 | @classmethod
142 | def get_all_assets(self):
143 | APCA_ID, APCA_SECRET = auth.get_api_keys()
144 | trade = TradingClient(APCA_ID, APCA_SECRET, paper=config.PAPER_TRADING)
145 | search_params = GetAssetsRequest(
146 | asset_class=AssetClass.CRYPTO, status=AssetStatus.ACTIVE
147 | )
148 | return [a.symbol for a in trade.get_all_assets(search_params)]
149 |
--------------------------------------------------------------------------------
/cira/strategy.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 | import pickle
3 | import pandas as pd
4 | import numpy as np
5 | import random
6 | import schedule
7 | import time
8 |
9 |
10 | class Strategy:
11 | def __init__(self, name) -> None:
12 | self.name = name
13 |
14 | def iterate(
15 | self,
16 | feature_data: pd.DataFrame,
17 | prices: pd.DataFrame,
18 | portfolio: np.ndarray,
19 | cash=float,
20 | ) -> np.ndarray:
21 | """
22 | Takes in feature data, then returns allocation prediction.
23 | """
24 | raise NotImplementedError
25 |
26 | def save(self, file_path):
27 | """
28 | Save strategy to pickle file
29 | usage:
30 | strategy.fit(train_data)
31 | strategy.save('./model.pkl')
32 | """
33 | with open(file_path, "wb") as file:
34 | pickle.dump(self, file)
35 |
36 | @classmethod
37 | def load(cls, file_path):
38 | """
39 | Load in strategy from pickle file
40 | usage:
41 | strategy = Strategy.load('./model.pkl')
42 | predictions = strategy.predict(test_data)
43 | """
44 | with open(file_path, "rb") as file:
45 | return pickle.load(file)
46 |
47 |
48 | class Randomness(Strategy):
49 | def __init__(
50 | self,
51 | lower: float = -1,
52 | upper: float = 1,
53 | seed=0,
54 | use_float: bool = False,
55 | ) -> None:
56 | super().__init__(name="Randomness")
57 | random.seed(seed)
58 | self.a = lower
59 | self.b = upper
60 | self.allocation = []
61 | self.use_float = use_float
62 |
63 | def iterate(
64 | self,
65 | feature_data: pd.DataFrame,
66 | prices: pd.DataFrame,
67 | portfolio: np.ndarray,
68 | cash=float,
69 | ) -> np.ndarray:
70 | al = np.array(
71 | [
72 | random.uniform(float(self.a), float(self.b))
73 | for _ in range(len(prices.keys()))
74 | ]
75 | )
76 | if not self.use_float:
77 | al = al.astype(int)
78 | self.allocation.append(al)
79 | return al
80 |
81 |
82 | class DollarCostAveraging(Strategy):
83 | def __init__(self, amount: float = 1) -> None:
84 | super().__init__(name="DollarCostAveraging")
85 | self.amount = amount
86 | self.allocation = []
87 |
88 | def iterate(
89 | self,
90 | feature_data: pd.DataFrame,
91 | prices: pd.DataFrame,
92 | portfolio: np.ndarray,
93 | cash=float,
94 | ) -> np.ndarray:
95 | al = np.array([self.amount for _ in range(len(prices.keys()))])
96 | self.allocation.append(al)
97 | return al
98 |
99 |
100 | class ByAndHold(Strategy):
101 | def __init__(self) -> None:
102 | super().__init__(name="BuyAndHold")
103 | self.is_first = True
104 | self.allocation = []
105 |
106 | def iterate(
107 | self,
108 | feature_data: pd.DataFrame,
109 | prices: pd.DataFrame,
110 | portfolio: np.ndarray,
111 | cash=float,
112 | ) -> np.ndarray:
113 | if self.is_first:
114 | self.is_first = False
115 | amount = cash / len(prices.keys())
116 | amount *= 0.96
117 | al = (amount // prices.values).astype(np.int64)[0]
118 | self.allocation.append(al)
119 | return al
120 | al = np.array([0] * len(prices.keys()))
121 | self.allocation.append(al)
122 | return al
123 |
124 |
125 | FEE_RATE = 0.004 # this is what alpaca takes
126 |
127 | fees = lambda prices, allocation: FEE_RATE * np.matmul(prices.T, allocation)
128 |
129 |
130 | def back_test(
131 | strat: Strategy,
132 | feature_data: pd.DataFrame,
133 | asset_prices: pd.DataFrame,
134 | capital=100_000.0,
135 | use_fees: bool = True,
136 | ) -> pd.DataFrame:
137 | """
138 | DISCLAIMER:
139 | The results of this backtest are based on historical data and do not guarantee future performance.
140 | The financial markets are inherently uncertain, and various factors can influence actual trading results.
141 | This backtest is provided for educational and informational purposes only.
142 | Users should exercise caution and conduct additional research before applying any trading strategy in live markets.
143 | """
144 | portfolio_history = {
145 | "value": [],
146 | "timestamp": [],
147 | }
148 | assert len(feature_data) == len(asset_prices)
149 | total_value = capital
150 | nr_of_asset = np.zeros([len(asset_prices.keys())], float)
151 | i = 0
152 | for t, cur_price in asset_prices.iterrows():
153 | # if len(asset_prices) == i + 1:
154 | # break
155 | if total_value > 0:
156 | f_data = feature_data.iloc[: i + 1]
157 | p_data = asset_prices.iloc[: i + 1]
158 | allocation = strat.iterate(f_data, p_data, nr_of_asset.copy(), capital)
159 | assert len(allocation) == len(
160 | nr_of_asset
161 | ), "tried to allocating more assets then is aviabel"
162 | for a, _ in enumerate(allocation):
163 | if capital <= 0.0 and allocation[a] < 0.0:
164 | allocation[a] = 0
165 | if nr_of_asset[a] + allocation[a] < 0.0:
166 | allocation[a] = -nr_of_asset[a]
167 | asking = float(
168 | np.matmul(cur_price.values.T, allocation)
169 | + use_fees * fees(cur_price.values, allocation)
170 | ) # - capital)
171 | if asking <= capital:
172 | capital -= asking
173 | nr_of_asset += allocation
174 | total_value = np.matmul(cur_price.values.T, nr_of_asset) + capital
175 | else:
176 | total_value = np.matmul(cur_price.values.T, nr_of_asset) + capital
177 |
178 | portfolio_history["timestamp"].append(t)
179 | portfolio_history["value"].append(total_value)
180 | i += 1
181 |
182 | df = pd.DataFrame(portfolio_history)
183 | df = df.set_index("timestamp")
184 | df.index = pd.to_datetime(df.index.get_level_values("timestamp"))
185 | df.rename(columns={"value": strat.name}, inplace=True)
186 | return df
187 |
188 |
189 | def multi_strategy_backtest(
190 | strats: List[Strategy],
191 | feature_data: pd.DataFrame,
192 | asset_prices: pd.DataFrame,
193 | capital=100_000.0,
194 | use_fees: bool = True,
195 | ):
196 | result = pd.DataFrame()
197 | result.index = asset_prices.index
198 | for s in strats:
199 | s_result = back_test(
200 | s,
201 | feature_data=feature_data,
202 | asset_prices=asset_prices,
203 | capital=capital,
204 | use_fees=use_fees,
205 | )
206 | result[s.name] = s_result[s.name]
207 | return result
208 |
209 |
210 | def back_test_against_buy_and_hold(
211 | strat: Strategy,
212 | feature_data: pd.DataFrame,
213 | asset_prices: pd.DataFrame,
214 | capital=100_000.0,
215 | use_fees: bool = True,
216 | ):
217 | buy_and_hold = ByAndHold()
218 | return multi_strategy_backtest(
219 | strats=[strat, buy_and_hold],
220 | feature_data=feature_data,
221 | asset_prices=asset_prices,
222 | capital=capital,
223 | use_fees=use_fees,
224 | )
225 |
226 |
227 | class Scheduler:
228 | def __init__(self) -> None:
229 | pass
230 |
231 | def add_daily_job(self, func_name) -> None:
232 | schedule.every(1).days.do(func_name)
233 |
234 | def add_daily_job_at(self, func_name, time_HM: str = "12:00") -> None:
235 | schedule.every().day.at(time_HM).do(func_name)
236 |
237 | def add_hour_job(self, func_name) -> None:
238 | schedule.every(1).hour.do(func_name)
239 |
240 | def add_minute_job(self, func_name) -> None:
241 | schedule.every(1).minute.do(func_name)
242 |
243 | def add_daily_job_at_time_EDT(self, func_name, time_HM: str = "12:00") -> None:
244 | schedule.every().day.at(time_HM, "America/New_York").do(func_name)
245 |
246 | def get_all_jobs(self):
247 | return schedule.jobs
248 |
249 | def clear_all_jobs(self) -> None:
250 | schedule.clear()
251 |
252 | def run(self):
253 | """runs the scheduler for ever"""
254 | while True:
255 | schedule.run_pending()
256 | time.sleep(1)
257 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cira
2 |
3 | Cira algorithmic trading made easy. A [Façade library](https://refactoring.guru/design-patterns/facade) for simpler interaction with alpaca-trade-API from Alpaca Markets.
4 |
5 | Cira is available on [pip](https://pypi.org/project/cira/). **Please give it a star if you like it!**
6 |
7 |
8 |
9 | 
10 | 
11 | [](https://twitter.com/Axel_Gard)
12 |
13 | 
14 | 
15 | 
16 | 
17 |
18 | The name **cira** is a miss spelling of the word for a [baby alpaca cria](https://en.wikipedia.org/wiki/Cria) and because this is a simple and small lib I thought it would be a perfect fit.
19 |
20 | [Axel Gard](https://github.com/AxelGard) is main developer for cira.
21 |
22 | ## Getting Started
23 |
24 | If you are new to cira checkout the [tutorial](https://github.com/AxelGard/cira/wiki/Tutorial).
25 | Or checkout an [example](https://github.com/AxelGard/cira/blob/master/examples/linear.ipynb).
26 |
27 | ### Installation
28 | You can install it using [pip](https://pypi.org/project/cira/).
29 | ```bash
30 | pip install cira
31 | ```
32 |
33 | ### Usage
34 | Since the Alpaca trade API need a API key, you need to generate your own key at [alpaca markets website](https://app.alpaca.markets/signup). If you want to play around with it you can try paper trading (recommended for beginners). I recommend keep it in a **JSON file** which cira needs the **path** to.
35 | You can also set the variables directly or use an environment variable, see the **[wiki](https://github.com/AxelGard/cira/wiki/Storing-the-Alpaca-API-key)** for diffrent the ways. However, it is **recommended** that you store it in a file just make sure not to upload that file on any public repositories.
36 |
37 | You can set the Alpaca keys directly
38 |
39 | ```python
40 | import cira
41 |
42 | cira.auth.APCA_API_KEY_ID = "my key"
43 | cira.auth.APCA_API_SECRET_KEY = "my secret key"
44 |
45 | stock = cira.Stock("TSLA")
46 | stock.buy(1) # buy 1 TSLA stock on alpaca
47 | stock.sell(1) # sell 1 TSLA stock on alpaca
48 | ```
49 |
50 | For interactons with alpaca you can:
51 | ```python
52 | portfolio = cira.Portfolio() # methods for your portfolio
53 | exchange = cira.Exchange() # methods for exchange
54 | stock = cira.Stock("TSLA") # a class for one stock
55 | crypto = cira.Cryptocurrency("BTC/USD") # method for one cryptocurrency
56 | ```
57 |
58 | ### DEMO, no keys needed
59 |
60 | Crypto market data can be accessed [without any alpaca keys](https://alpaca.markets/sdks/python/market_data.html#api-keys).
61 | So there for you can try cira out with out needing to get alpaca keys.
62 | To put you model in production where you buy and sell you will need alpaca keys.
63 |
64 | Needs `cira>=3.2.2`.
65 |
66 | ```python
67 | import cira
68 | from datetime import datetime
69 | import matplotlib.pyplot as plt
70 |
71 | assert not cira.auth.check_keys() # No keys are needed
72 |
73 | SYMBOL = "BTC/USD"
74 | ast = cira.Cryptocurrency(SYMBOL)
75 |
76 | print(f"The current asking price for {SYMBOL} is {ast.price()}")
77 |
78 |
79 | # alpaca only have BTC data from 2021 and forward
80 | data = ast.historical_data_df(datetime(2021, 1, 1), datetime.now().date())
81 | print(data.head())
82 |
83 | # All of strategies and backtesting works with out keys as well.
84 | strat = cira.strategy.Randomness()
85 | cira.strategy.back_test_against_buy_and_hold(strat, data, data["open"].to_frame(), 100_000).plot()
86 | plt.savefig('./result.png')
87 | ```
88 |
89 | you can find more examples on the **[examples repo](https://github.com/AxelGard/cira-examples)** and the **[wiki/tutorial](https://github.com/AxelGard/cira/wiki/Tutorial)** for even more information.
90 |
91 | ### Cira Stratergies
92 |
93 | Cira have also support for strategies.
94 | An **full example** of how to use the strategy is [example/linear](../../examples/linear.ipynb).
95 |
96 | With strategies you can run a cira backtests.
97 |
98 | ```python
99 | from cira.strategy import Strategy
100 | import numpy as np
101 | import pandas as pd
102 |
103 | class MyStrat(Strategy):
104 | def __init__(self) -> None:
105 | super().__init__(name="MyStrat")
106 |
107 | def iterate(self, feature_data: pd.DataFrame, prices: pd.DataFrame, portfolio: np.ndarray, cash:float) -> np.ndarray:
108 | # this mehod will be called for each row of data in the backtest
109 | # the function should return the change of your portfolio.
110 | # -1 means sell one stock, 0 means hold, 1 means buy one stock
111 | return np.array([ portfolio_change_as_int_or_float ])
112 | ```
113 |
114 | #### Backtest
115 |
116 | If your model is put into a strategy you can run a backtest on you own data.
117 | This is a backtest using some of the included strategy in cira.
118 | You can run a backtest aginst multiple strategies using the same data, this requires however that all features for all models are in the given data to the backtest.
119 | You should of course add your own strategy, but as an example.
120 |
121 | ```python
122 | import cira
123 | from datetime import datetime
124 | import matplotlib.pyplot as plt
125 |
126 | assert not cira.auth.check_keys() # back testing against crypto do not need keys
127 |
128 | SYMBOL = "ETH/USD"
129 | ast = cira.Cryptocurrency(SYMBOL)
130 |
131 | data = ast.historical_data_df(datetime(2021, 1, 1), datetime.now().date())
132 |
133 | strats = [
134 | cira.strategy.ByAndHold(),
135 | cira.strategy.DollarCostAveraging(0.8),
136 | cira.strategy.Randomness(-100, 100, seed=None, use_float=True),
137 | # add your own strategy and compare your model against other models
138 | ]
139 | cira.strategy.multi_strategy_backtest(strats, data, data["open"].to_frame(), 100_000).plot()
140 | plt.savefig("./result.png")
141 | ```
142 |
143 | If you want more full example of how to use the backtest checkout
144 | [multiassets](https://github.com/AxelGard/cira/blob/master/examples/multi_assets.ipynb) and
145 | [linear](https://github.com/AxelGard/cira/blob/master/examples/linear.ipynb).
146 |
147 |
148 |
149 | ## Things to checkout
150 |
151 | * [News](https://github.com/AxelGard/cira/discussions/categories/news)
152 | * [Wiki](https://github.com/AxelGard/cira/wiki/)
153 | * [Tutorial](https://github.com/AxelGard/cira/wiki/Tutorial)
154 | * [Storing the Alpaca API key](https://github.com/AxelGard/cira/wiki/Storing-the-Alpaca-API-key)
155 | * [Examples of how to use cira](https://github.com/AxelGard/cira-examples)
156 | * [Discussions](https://github.com/AxelGard/cira/discussions)
157 |
158 | ### [Wiki](https://github.com/AxelGard/cira/wiki) and docs
159 |
160 | To see what more you can do check out the [wiki](https://github.com/AxelGard/cira/wiki).
161 |
162 | ### Want the old version?
163 |
164 | For backwards compatibility I made sure to fork cira in to [cira-classic](https://github.com/AxelGard/cira-classic) and cira-classic is also available on [pypi with pip](https://pypi.org/project/cira-classic/).
165 |
166 | **If you find bug plz let me know with a issue.** If you know how to solve the problem then you can of course make a pull request and I will take a look at it.
167 |
168 | ### Have a question?
169 |
170 | If you have a question about cira, want to share what you built with cira or want to talk to others using cira,
171 | you can checkout the [discussions page](https://github.com/AxelGard/cira/discussions) or make issue if that is more fitting.
172 |
173 | ### History of cira
174 |
175 | I was interested in using the Alpaca trade API for building a quantitative paper trader.
176 | The project is available [here](https://github.com/AxelGard/paper-trader).
177 | However after working on this for alomst a year (off and on) I realized that I had alomst build a small library for using the Alpaca API.
178 | So I thought that I would make this into a real library so that you can get started with quantitative paper trading as well.
179 |
180 | ## Development
181 | If you want to help develop cira you are more then welcome to do so.
182 | Feel free to make a pull request or issue.
183 | To install cira with all the dev req.
184 | ```bash
185 | git clone git@github.com:AxelGard/cira.git
186 | cd cira/
187 | git checkout develop
188 | ```
189 | and know you need to
190 | ```bash
191 | python3 -m venv env
192 | source env/bin/activate
193 | pip install -e .
194 | pip install -r requirements.txt
195 | ```
196 | Run tests using pytest. Ensure that you are in the cira dir.
197 | But you will need a new key. This key should not only be used for testing or if you don't mind if all of the assets in the portfolio being sold.
198 | ```bash
199 | pytest -rP
200 | ```
201 |
202 | ### Coding style
203 | I'm trying to follow the [pep8](https://pep8.org/) standard notation.
204 | I try to make the library to be so intuitive as possible for easy of use.
205 |
206 | I enforce [black formater](https://github.com/psf/black) when you commit code, by [pre-commit githooks](https://git-scm.com/docs/githooks#_pre_commit) to keep it some what well formated.
207 |
208 | ## License
209 |
210 | This project is licensed under the MIT License - see the [LICENSE](https://github.com/AxelGard/cira/blob/master/LICENSE.txt) file for details.
211 |
212 |
213 | ## Acknowledgments
214 |
215 | * [Alpaca API](https://alpaca.markets/)
216 | * [paper-trader](https://github.com/AxelGard/paper-trader)
217 | * [cira-classic](https://github.com/AxelGard/cira-classic)
218 |
--------------------------------------------------------------------------------
/cira/asset.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 | from datetime import datetime
3 | import logging
4 | import warnings
5 |
6 | # Alpaca
7 | import alpaca
8 | import alpaca.trading
9 | import alpaca.trading.models
10 | from alpaca.trading.requests import GetAssetsRequest
11 | from alpaca.trading.enums import AssetClass, OrderType, AssetStatus
12 | from alpaca.data.models import Bar
13 | from alpaca.trading.enums import OrderSide, TimeInForce
14 | from alpaca.data.timeframe import TimeFrame
15 | from alpaca.trading.requests import LimitOrderRequest, StopLimitOrderRequest
16 | from alpaca.trading.client import TradingClient
17 |
18 | # stock
19 | from alpaca.data import StockHistoricalDataClient
20 | from alpaca.data.requests import StockLatestQuoteRequest
21 | from alpaca.data.requests import StockBarsRequest
22 | from alpaca.trading.requests import MarketOrderRequest
23 | from alpaca.data.live import StockDataStream
24 |
25 | # crypto
26 | from alpaca.data import CryptoHistoricalDataClient
27 | from alpaca.data.live.crypto import CryptoDataStream
28 | from alpaca.data.requests import CryptoLatestQuoteRequest
29 | from alpaca.data.requests import CryptoBarsRequest
30 |
31 | import pandas as pd
32 |
33 | from . import auth
34 | from . import config
35 | from . import util
36 | from . import log
37 |
38 |
39 | class Asset:
40 | def __init__(self, symbol: str) -> None:
41 | """Interface class"""
42 | self.symbol = symbol
43 | self.live_client: StockDataStream = None
44 | self.history: StockHistoricalDataClient = None
45 | self.trade: TradingClient = None
46 | self.latest_quote_request: StockLatestQuoteRequest = None
47 | self.bars_request: StockBarsRequest = None
48 |
49 | def price(self) -> float:
50 | raise NotImplementedError
51 |
52 | @classmethod
53 | def get_all_assets(self):
54 | raise NotImplementedError
55 |
56 | def live_data(self, async_function_to_resolve_to, run: bool = True) -> None:
57 | self.live_client.subscribe_quotes(async_function_to_resolve_to, self.symbol)
58 | if run:
59 | self.live_client.run()
60 |
61 | def _get_bars(self, start_date: datetime, end_date: datetime):
62 | """returns aplc bars from the given dates"""
63 | params = self.bars_request(
64 | symbol_or_symbols=self.symbol,
65 | timeframe=TimeFrame.Day,
66 | start=start_date,
67 | end=end_date,
68 | adjustment="all",
69 | )
70 | return self.history.get_stock_bars(params)
71 |
72 | def historical_data_df(
73 | self, start_date: datetime, end_date: datetime
74 | ) -> pd.DataFrame:
75 | """takes two dates, and returns a data frame with bars from the given dates"""
76 | data = self._get_bars(start_date, end_date).df
77 | data = data.reset_index(level="symbol")
78 | data["timestamp"] = pd.to_datetime(data.index.get_level_values("timestamp"))
79 | data.set_index("timestamp", inplace=True)
80 | return data
81 |
82 | def historical_data(self, start_date: datetime, end_date: datetime) -> List[dict]:
83 | """takes two dates, and returns a list of dicts with bars from the given dates"""
84 | return self._get_bars(start_date, end_date).dict()[self.symbol]
85 |
86 | def buy(self, qty: float) -> None:
87 | """Buy the asset,
88 | qty is the number of the asset that you buy"""
89 | market_order = MarketOrderRequest(
90 | symbol=self.symbol,
91 | qty=qty,
92 | side=OrderSide.BUY,
93 | time_in_force=TimeInForce.DAY,
94 | )
95 | if config.IS_LOGGING:
96 | log.log("BUY", self.symbol, qty)
97 | self.trade.submit_order(market_order)
98 |
99 | def sell(self, qty: float) -> None:
100 | """Sell the asset,
101 | qty is the number of the asset that you sell"""
102 | market_order = MarketOrderRequest(
103 | symbol=self.symbol,
104 | qty=qty,
105 | side=OrderSide.SELL,
106 | time_in_force=TimeInForce.DAY,
107 | )
108 | logging.info(f"sell:{self.symbol}, qty:{qty}")
109 | if config.IS_LOGGING:
110 | log.log("SELL", self.symbol, qty)
111 | self.trade.submit_order(market_order)
112 |
113 | def buy_at(self, qty: int, price: float) -> None:
114 | """Buy the asset at a given price,
115 | qty is the number of the asset that you buy"""
116 | limit_order_data = LimitOrderRequest(
117 | symbol=self.symbol,
118 | limit_price=price,
119 | qty=qty,
120 | side=OrderSide.BUY,
121 | time_in_force=TimeInForce.FOK,
122 | )
123 | if config.IS_LOGGING:
124 | log.log("BUY", self.symbol, qty)
125 | self.trade.submit_order(order_data=limit_order_data)
126 |
127 | def sell_at(self, qty: int, price: float) -> None:
128 | """Sell the asset at a given price,
129 | qty is the number of the asset that you sell"""
130 | limit_order_data = LimitOrderRequest(
131 | symbol=self.symbol,
132 | limit_price=price,
133 | qty=qty,
134 | side=OrderSide.SELL,
135 | time_in_force=TimeInForce.FOK,
136 | )
137 | if config.IS_LOGGING:
138 | log.log("SELL", self.symbol, qty)
139 | self.trade.submit_order(order_data=limit_order_data)
140 |
141 | def cancel_orders(self):
142 | return self.trade.cancel_orders()
143 |
144 | def save_historical_data(
145 | self, file_path, start_date: datetime, end_date: datetime
146 | ) -> None:
147 | data = self.historical_data_df(start_date=start_date, end_date=end_date)
148 | data.to_csv(file_path)
149 |
150 | @classmethod
151 | def load_historical_data(cls, file_path) -> pd.DataFrame:
152 | """
153 | Load in model from pickle file
154 | usage:
155 | model = Strategy.load('./model.pkl')
156 | predictions = model.predict(X_test)
157 | """
158 | data = pd.read_csv(file_path)
159 | data["timestamp"] = pd.to_datetime(data["timestamp"])
160 | data.set_index("timestamp", inplace=True)
161 | return data
162 |
163 | def value(self) -> float: # prev: value_of_stock
164 | """takes a string sym. Gets and returns the asset value at close"""
165 |
166 | warnings.warn(f"Warning: function is deprecated ({self.value})")
167 |
168 | nr_days = 1
169 | bars = self.barset(nr_days)
170 | if bars is None:
171 | self._value = 0.0
172 | else:
173 | self._value = bars[0].c # get stock at close
174 | return self._value
175 |
176 | def order(self, qty: int, beh: str) -> float:
177 | """submit order and is a template for order"""
178 |
179 | warnings.warn(
180 | f"Warning: function is deprecated ({self.order}), will return {None}"
181 | )
182 |
183 | return None
184 |
185 | def is_sortable(self) -> bool:
186 | """checks if asset can be shorted"""
187 | return self.trade.get_asset(self.symbol).shortable
188 |
189 | def can_borrow(self) -> bool:
190 | """check whether the name is currently
191 | available to short at Alpaca"""
192 | return self.trade.get_asset(self.symbol).easy_to_borrow
193 |
194 | def barset(self, limit: int):
195 | """returns barset for asset for time period lim"""
196 | return alpaca.api().get_bars(self.symbol, TimeFrame.Minute, limit=int(limit))
197 |
198 | def is_tradable(self) -> bool:
199 | """return if the asset can be traded"""
200 | return self.trade.get_asset(self.symbol).tradable
201 |
202 | def position(self):
203 | """returns position of asset,
204 | if symbols is not in all your positions then it will return None"""
205 |
206 | def reformat_position(position):
207 | """reformat position to be float values"""
208 | for key, value in position.items():
209 | try:
210 | if isinstance(value, str):
211 | if "." in value:
212 | position[key] = float(value)
213 | else:
214 | position[key] = int(value)
215 | except ValueError:
216 | continue
217 | return position
218 |
219 | all_pos = self.trade.get_all_positions()
220 | for pos in all_pos:
221 | if pos.symbol == self.symbol:
222 | return reformat_position(dict(pos))
223 | return None
224 |
225 | def today_plpc(self) -> float:
226 | """asset today's profit/loss percent"""
227 | return float(self.position().unrealized_intraday_plpc)
228 |
229 | def plpc(self) -> float:
230 | """asset sym (str) Unrealized profit/loss percentage"""
231 | return float(self.position().unrealized_plpc)
232 |
233 | # Operators
234 |
235 | def __eq__(self, other):
236 | if isinstance(other, (int, float)):
237 | return self.price() == other
238 | return self.price() == other.price()
239 |
240 | def __ne__(self, other):
241 | if isinstance(other, (int, float)):
242 | return self.price() != other
243 | return self.price() != other.price()
244 |
245 | def __lt__(self, other):
246 | if isinstance(other, (int, float)):
247 | return self.price() < other
248 | return self.price() < other.price()
249 |
250 | def __le__(self, other):
251 | if isinstance(other, (int, float)):
252 | return self.price() <= other
253 | return self.price() <= other.price()
254 |
255 | def __gt__(self, other):
256 | if isinstance(other, (int, float)):
257 | return self.price() > other
258 | return self.price() > other.price()
259 |
260 | def __ge__(self, other):
261 | if isinstance(other, (int, float)):
262 | return self.price() >= other
263 | return self.price() >= other.price()
264 |
265 | # Arithmetic Operators
266 |
267 | def __add__(self, other):
268 | if isinstance(other, (int, float)):
269 | return self.price() + other
270 | return self.price() + other.price()
271 |
272 | def __radd__(self, other):
273 | return self.price() + other
274 |
275 | def __sub__(self, other):
276 | if isinstance(other, (int, float)):
277 | return self.price() - other
278 | return self.price() - other.price()
279 |
280 | def __rsub__(self, other):
281 | return self.price() - other
282 |
283 | def __mul__(self, other):
284 | if isinstance(other, (int, float)):
285 | return self.price() * other
286 | return self.price() * other.price()
287 |
288 | def __rmul__(self, other):
289 | return self.price() * other
290 |
291 | def __truediv__(self, other):
292 | if isinstance(other, (int, float)):
293 | return self.price() / other
294 | return self.price() / other.price()
295 |
296 | def __rdiv__(self, other):
297 | return self.price() / other
298 |
299 | def __floordiv__(self, other):
300 | if isinstance(other, (int, float)):
301 | return self.price() // other
302 | return self.price() // other.price()
303 |
304 | def __rfloordiv__(self, other):
305 | return self.price() // other
306 |
307 | # Type Conversion
308 |
309 | def __abs__(self):
310 | # dose not rely makes sense should not be able to
311 | # be neg but might be good to have
312 | return abs(self.price)
313 |
314 | def __int__(self):
315 | return int(self.price)
316 |
317 | def __float__(self):
318 | return float(self.price)
319 |
320 | def __round__(self, nDigits):
321 | return round(self.price, nDigits)
322 |
323 | def __str__(self) -> str:
324 | return self.symbol
325 |
326 | def __repr__(self) -> str:
327 | return self.symbol
328 |
329 | def __eq__(self, other):
330 | if not isinstance(other, Asset):
331 | raise ValueError
332 | return self.symbol == other.symbol
333 |
334 | def __ne__(self, other):
335 | return not self.__eq__(other)
336 |
--------------------------------------------------------------------------------
/docs/docs.md:
--------------------------------------------------------------------------------
1 | # cira/
2 | ## cira/alpaca_utils.py
3 | **def get_trading_client() -> TradingClient** \
4 | `get the alpaca-sdk python trading class` \
5 | `obj initalized with set config`
6 |
7 |
8 | ## cira/portfolio.py
9 | ### class Position()
10 | `None`
11 |
12 | > **def \_\_init\_\_(self, symbol) -> None** \
13 | > `None`
14 | >
15 | > **def quantity(self) -> int** \
16 | > `returns the number of the assets that is owned`
17 | >
18 | > **def market_value(self) -> float** \
19 | > `Returns market value of symbol in portfolio`
20 | >
21 | > **def to_dict(self) -> dict** \
22 | > `Returns a dict of the position`
23 | >
24 | > **def \_\_str\_\_(self) -> str** \
25 | > `None`
26 | >
27 | > **def \_\_repr\_\_(self) -> str** \
28 | > `None`
29 | >
30 | ### class Portfolio()
31 | `None`
32 |
33 | > **def \_\_init\_\_(self) -> None** \
34 | > `None`
35 | >
36 | > **def total_value(self) -> float** \
37 | > `None`
38 | >
39 | > **def is_blocked(self) -> bool** \
40 | > `None`
41 | >
42 | > **def buying_power(self) -> float** \
43 | > `gets the amount of cash currently available`
44 | >
45 | > **def cash(self) -> float** \
46 | > `gets the amount of cash currently available`
47 | >
48 | > **def equity(self) -> float** \
49 | > `returns the amount of equity that users has`
50 | >
51 | > **def equity_yesterday(self) -> float** \
52 | > `returns the amount of equity that was` \
53 | `available at market close yesterday`
54 | >
55 | > **def equity_change(self)** \
56 | > `returns the change in equity from yesterday to now`
57 | >
58 | > **def all_positions(self) -> List[Position]** \
59 | > `Returns all positions of portfolio`
60 | >
61 | > **def close_all_positions(self) -> None** \
62 | > `WARNING: This closes all your open positions`
63 | >
64 | > **def position_in(self, symbol: str) -> Position** \
65 | > `None`
66 | >
67 | > **def get_allocation(self, symbol: str) -> int** \
68 | > `None`
69 | >
70 | > **def cancel_all_orders(self) -> None** \
71 | > `None`
72 | >
73 | > **def sell_list(self, symbols: List[str]) -> None** \
74 | > `takes a list of Stocks and sells all stocks in that list`
75 | >
76 | > **def owned_stock_qty(self, symbol: str) -> int** \
77 | > `returns quantity of owned of a stock Stock (obj)`
78 | >
79 | > **def owned_stocks_qty(self) -> Dict[(str, int)]** \
80 | > `None`
81 | >
82 | > **def owned_stocks(self) -> List[Stock]** \
83 | > `returns a list of owned stocks`
84 | >
85 | > **def \_\_repr\_\_(self)** \
86 | > `None`
87 | >
88 | > **def \_\_str\_\_(self)** \
89 | > `None`
90 | >
91 |
92 | ## cira/asset.py
93 | ### class Asset()
94 | `None`
95 |
96 | > **def \_\_init\_\_(self, symbol: str) -> None** \
97 | > `Interface class`
98 | >
99 | > **def historical_data_df(self, start_date: datetime, end_date: datetime) -> pd.DataFrame** \
100 | > `None`
101 | >
102 | > **def price(self) -> float** \
103 | > `None`
104 | >
105 | > **def \_\_str\_\_(self) -> str** \
106 | > `None`
107 | >
108 | > **def \_\_repr\_\_(self) -> str** \
109 | > `None`
110 | >
111 | > **def \_\_eq\_\_(self, other)** \
112 | > `None`
113 | >
114 | > **def \_\_ne\_\_(self, other)** \
115 | > `None`
116 | >
117 | ### class Stock(Asset)
118 | `None`
119 |
120 | > **def \_\_init\_\_(self, symbol: str) -> None** \
121 | > `Exchange for trading stocks`
122 | >
123 | > **def price(self) -> float** \
124 | > `gets the asking price of the symbol`
125 | >
126 | > **def live_data(self, async_function_to_resolve_to, run: bool=True) -> None** \
127 | > `None`
128 | >
129 | > **def _get_bars(self, start_date: datetime, end_date: datetime)** \
130 | > `returns aplc bars from the given dates`
131 | >
132 | > **def historical_data_df(self, start_date: datetime, end_date: datetime) -> pd.DataFrame** \
133 | > `takes two dates, and returns a data frame with bars from the given dates`
134 | >
135 | > **def historical_data(self, start_date: datetime, end_date: datetime) -> List[dict]** \
136 | > `takes two dates, and returns a list of dicts with bars from the given dates`
137 | >
138 | > **def buy(self, qty: float) -> None** \
139 | > `Buy the asset,` \
140 | `qty is the number of the asset that you buy`
141 | >
142 | > **def sell(self, qty: float) -> None** \
143 | > `Sell the asset,` \
144 | `qty is the number of the asset that you sell`
145 | >
146 | > **def buy_at(self, qty: int, price: float) -> None** \
147 | > `Buy the asset at a given price,` \
148 | `qty is the number of the asset that you buy`
149 | >
150 | > **def sell_at(self, qty: int, price: float) -> None** \
151 | > `Sell the asset at a given price,` \
152 | `qty is the number of the asset that you sell`
153 | >
154 | > **def save_historical_data(self, file_path, start_date: datetime, end_date: datetime) -> None** \
155 | > `None`
156 | >
157 | > **@classmethod \
158 | def load_historical_data(cls, file_path) -> pd.DataFrame** \
159 | > `Load in model from pickle file` \
160 | `usage:` \
161 | ` model = Strategy.load('./model.pkl')` \
162 | ` predictions = model.predict(X_test)`
163 | >
164 | > **def value(self) -> float** \
165 | > `takes a string sym. Gets and returns the stock value at close`
166 | >
167 | > **def order(self, qty: int, beh: str) -> float** \
168 | > `submit order and is a template for order`
169 | >
170 | > **def is_sortable(self) -> bool** \
171 | > `checks if stock can be shorted`
172 | >
173 | > **def can_borrow(self) -> bool** \
174 | > `check whether the name is currently` \
175 | `available to short at Alpaca`
176 | >
177 | > **def barset(self, limit: int)** \
178 | > `returns barset for stock for time period lim`
179 | >
180 | > **def is_tradable(self) -> bool** \
181 | > `return if the stock can be traded`
182 | >
183 | > **def position(self)** \
184 | > `returns position of stock`
185 | >
186 | > **def today_plpc(self) -> float** \
187 | > `stock today's profit/loss percent`
188 | >
189 | > **def plpc(self) -> float** \
190 | > `stock sym (str) Unrealized profit/loss percentage`
191 | >
192 | > **def \_\_eq\_\_(self, other)** \
193 | > `None`
194 | >
195 | > **def \_\_ne\_\_(self, other)** \
196 | > `None`
197 | >
198 | > **def \_\_lt\_\_(self, other)** \
199 | > `None`
200 | >
201 | > **def \_\_le\_\_(self, other)** \
202 | > `None`
203 | >
204 | > **def \_\_gt\_\_(self, other)** \
205 | > `None`
206 | >
207 | > **def \_\_ge\_\_(self, other)** \
208 | > `None`
209 | >
210 | > **def \_\_add\_\_(self, other)** \
211 | > `None`
212 | >
213 | > **def \_\_radd\_\_(self, other)** \
214 | > `None`
215 | >
216 | > **def \_\_sub\_\_(self, other)** \
217 | > `None`
218 | >
219 | > **def \_\_rsub\_\_(self, other)** \
220 | > `None`
221 | >
222 | > **def \_\_mul\_\_(self, other)** \
223 | > `None`
224 | >
225 | > **def \_\_rmul\_\_(self, other)** \
226 | > `None`
227 | >
228 | > **def \_\_truediv\_\_(self, other)** \
229 | > `None`
230 | >
231 | > **def \_\_rdiv\_\_(self, other)** \
232 | > `None`
233 | >
234 | > **def \_\_floordiv\_\_(self, other)** \
235 | > `None`
236 | >
237 | > **def \_\_rfloordiv\_\_(self, other)** \
238 | > `None`
239 | >
240 | > **def \_\_abs\_\_(self)** \
241 | > `None`
242 | >
243 | > **def \_\_int\_\_(self)** \
244 | > `None`
245 | >
246 | > **def \_\_float\_\_(self)** \
247 | > `None`
248 | >
249 | > **def \_\_round\_\_(self, nDigits)** \
250 | > `None`
251 | >
252 | ### class Cryptocurrency(Asset)
253 | `None`
254 |
255 | > **def \_\_init\_\_(self, symbol: str) -> None** \
256 | > `Exchange for trading cryptocurrencies`
257 | >
258 | > **def _get_bars(self, start_date: datetime, end_date: datetime)** \
259 | > `None`
260 | >
261 | > **def historical_data_df(self, start_date: datetime, end_date: datetime) -> pd.DataFrame** \
262 | > `None`
263 | >
264 | > **def price(self) -> float** \
265 | > `gets the asking price of the symbol`
266 | >
267 |
268 | ## cira/util.py
269 | **def reformat_position(position)** \
270 | `reformat position to be float values`
271 |
272 | **def bars_to_dict(bars)** \
273 | `None`
274 |
275 | **def date_to_days_back(date: str)** \
276 | `None`
277 |
278 |
279 | ## cira/__init__.py
280 |
281 | ## cira/config.py
282 |
283 | ## cira/log.py
284 | **def format_log_action(act: str, sym: str, qty: int) -> list** \
285 | `formats info for logging`
286 |
287 | **def log(log_data: list) -> None** \
288 | `writes log data to file`
289 |
290 | **def set_logging()** \
291 | `None`
292 |
293 |
294 | ## cira/auth.py
295 | **def get_api_keys()** \
296 | `None`
297 |
298 | **def check_keys() -> bool** \
299 | `None`
300 |
301 | **def authentication_header()** \
302 | `get's key and returns key in json format`
303 |
304 | **def api(version='v2')** \
305 | `returns object for api`
306 |
307 |
308 | ## cira/exchange.py
309 | ### class Exchange()
310 | `None`
311 |
312 | > **def \_\_init\_\_(self) -> None** \
313 | > `None`
314 | >
315 | > **def is_open(self) -> bool** \
316 | > `Checks if the exchange is open and able to trade`
317 | >
318 | > **def to_assets(self, symbols: List[str]) -> List[asset.Asset]** \
319 | > `Takes a list of symbols and returns` \
320 | `them in a list of cira Assets objects`
321 | >
322 | > **def to_asset(self, symbol: str) -> asset.Asset** \
323 | > `Takes a symbols and returns` \
324 | `it as a cira Assets objects`
325 | >
326 | > **def get_all_stocks(self, is_tradeable: bool=True, force_reload: bool=False) -> List[asset.Stock]** \
327 | > `Returns a list of all stocks as cira asset,` \
328 | `objects will be cached, can be turn off in config.`
329 | >
330 | > **def calendar(self, start='2018-12-01', end='2018-12-01')** \
331 | > `None`
332 | >
333 | > **def assets_raw(self)** \
334 | > `(legacy, should not be used)` \
335 | `returns a list of all avilabel stocks in exchanges list`
336 | >
337 | > **def symbols(self)** \
338 | > `returns a list of all symbols`
339 | >
340 | ### class DemoExchange(Exchange)
341 | `None`
342 |
343 | > **def \_\_init\_\_(self) -> None** \
344 | > `uses crypto client, so no need for keys, has limited usage`
345 | >
346 |
347 | ## cira/strategy/strategy.py
348 | ### class Strategy()
349 | `None`
350 |
351 | > **def \_\_init\_\_(self, name) -> None** \
352 | > `None`
353 | >
354 | > **def iterate(self, feature_data: pd.DataFrame, prices: pd.DataFrame, portfolio: np.ndarray, cash=float) -> np.ndarray** \
355 | > `Takes in feature data, then returns allocation prediction.`
356 | >
357 | > **def save(self, file_path)** \
358 | > `Save strategy to pickle file` \
359 | `usage:` \
360 | ` strategy.fit(train_data)` \
361 | ` strategy.save('./model.pkl')`
362 | >
363 | > **@classmethod \
364 | def load(cls, file_path)** \
365 | > `Load in strategy from pickle file` \
366 | `usage:` \
367 | ` strategy = Strategy.load('./model.pkl')` \
368 | ` predictions = strategy.predict(test_data)`
369 | >
370 | ### class Randomness(Strategy)
371 | `None`
372 |
373 | > **def \_\_init\_\_(self, lower: int=(- 1), upper: int=1, seed=0) -> None** \
374 | > `None`
375 | >
376 | > **def iterate(self, feature_data: pd.DataFrame, prices: pd.DataFrame, portfolio: np.ndarray, cash=float) -> np.ndarray** \
377 | > `None`
378 | >
379 | ### class ByAndHold(Strategy)
380 | `None`
381 |
382 | > **def \_\_init\_\_(self) -> None** \
383 | > `None`
384 | >
385 | > **def iterate(self, feature_data: pd.DataFrame, prices: pd.DataFrame, portfolio: np.ndarray, cash=float) -> np.ndarray** \
386 | > `None`
387 | >
388 |
389 | ## cira/strategy/backtest.py
390 | **def back_test(strat: Strategy, feature_data: pd.DataFrame, asset_prices: pd.DataFrame, capital=100000.0, use_fees: bool=True) -> pd.DataFrame** \
391 | `DISCLAIMER: ` \
392 | `The results of this backtest are based on historical data and do not guarantee future performance. ` \
393 | `The financial markets are inherently uncertain, and various factors can influence actual trading results. ` \
394 | `This backtest is provided for educational and informational purposes only. ` \
395 | `Users should exercise caution and conduct additional research before applying any trading strategy in live markets. `
396 |
397 | **def multi_strategy_backtest(strats: List[Strategy], feature_data: pd.DataFrame, asset_prices: pd.DataFrame, capital=100000.0, use_fees: bool=True)** \
398 | `None`
399 |
400 | **def back_test_against_buy_and_hold(strat: Strategy, feature_data: pd.DataFrame, asset_prices: pd.DataFrame, capital=100000.0, use_fees: bool=True)** \
401 | `None`
402 |
403 |
404 | ## cira/strategy/__init__.py
405 |
406 | ## cira/strategy/scheduler.py
407 | ### class Scheduler()
408 | `None`
409 |
410 | > **def \_\_init\_\_(self) -> None** \
411 | > `None`
412 | >
413 | > **def add_daily_job(self, func_name) -> None** \
414 | > `None`
415 | >
416 | > **def add_daily_job_at(self, func_name, time_HM: str='12:00') -> None** \
417 | > `None`
418 | >
419 | > **def add_hour_job(self, func_name) -> None** \
420 | > `None`
421 | >
422 | > **def add_minute_job(self, func_name) -> None** \
423 | > `None`
424 | >
425 | > **def add_daily_job_at_time_EDT(self, func_name, time_HM: str='12:00') -> None** \
426 | > `None`
427 | >
428 | > **def get_all_jobs(self)** \
429 | > `None`
430 | >
431 | > **def clear_all_jobs(self) -> None** \
432 | > `None`
433 | >
434 | > **def run(self)** \
435 | > `runs the scheduler for ever`
436 | >
437 |
438 |
--------------------------------------------------------------------------------