├── 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 | drawing 8 | 9 | ![GitHub stars](https://img.shields.io/github/stars/AxelGard/Cira?style=social) 10 | ![GitHub forks](https://img.shields.io/github/forks/AxelGard/cira?style=social) 11 | [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/Axel_Gard) 12 | 13 | ![GitHub](https://img.shields.io/github/license/AxelGard/cira?style=plastic) 14 | ![PyPI](https://img.shields.io/pypi/v/cira) 15 | ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cira) 16 | ![PyPI - Downloads](https://img.shields.io/pypi/dm/cira) 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 | --------------------------------------------------------------------------------