├── .github ├── dependabot.yml ├── stale.yml └── workflows │ ├── codeql-analysis.yml │ ├── pre-commit.yml │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── coverage.svg ├── poetry.lock ├── pyproject.toml ├── stake ├── __init__.py ├── asx │ ├── __init__.py │ ├── common.py │ ├── equity.py │ ├── funding.py │ ├── market.py │ ├── order.py │ ├── product.py │ ├── trade.py │ └── transaction.py ├── client.py ├── common.py ├── constant.py ├── equity.py ├── funding.py ├── fx.py ├── market.py ├── order.py ├── product.py ├── ratings.py ├── trade.py ├── transaction.py ├── user.py └── watchlist.py └── tests ├── __init__.py ├── cassettes ├── test_equity │ ├── test_list_equities[exchange0].yaml │ └── test_list_equities[exchange1].yaml ├── test_funding │ ├── test_cash_available[exchange0].yaml │ ├── test_cash_available[exchange1].yaml │ ├── test_funds_in_flight[exchange0].yaml │ ├── test_funds_in_flight[exchange1].yaml │ ├── test_list_fundings[exchange0-request_0].yaml │ └── test_list_fundings[exchange1-request_1].yaml ├── test_fx │ └── test_fx_conversion.yaml ├── test_market │ ├── test_check_market_status[exchange0].yaml │ └── test_check_market_status[exchange1].yaml ├── test_order │ ├── test_brokerage[exchange0].yaml │ ├── test_brokerage[exchange1].yaml │ ├── test_cancel_order[exchange0].yaml │ ├── test_cancel_order[exchange1].yaml │ ├── test_list_orders[exchange0].yaml │ └── test_list_orders[exchange1].yaml ├── test_product │ ├── test_find_products_by_name[exchange0].yaml │ ├── test_find_products_by_name[exchange1].yaml │ ├── test_get_product[exchange0-symbols0].yaml │ └── test_get_product[exchange1-symbols1].yaml ├── test_ratings │ ├── test_list_ratings.yaml │ └── test_list_ratings_unknown.yaml ├── test_trade │ ├── test_limit_buy.yaml │ ├── test_limit_sell.yaml │ ├── test_sell[exchange0-request_0].yaml │ ├── test_sell[exchange1-request_1].yaml │ ├── test_stop_buy.yaml │ ├── test_successful_trade[exchange0-request_0].yaml │ ├── test_successful_trade[exchange1-request_1].yaml │ ├── test_successful_trade[exchange2-request_2].yaml │ └── test_successful_trade[exchange3-request_3].yaml ├── test_transaction │ ├── test_list_transactions[exchange0-request_0].yaml │ └── test_list_transactions[exchange1-request_1].yaml └── test_watchlist │ ├── test_add_to_watchlist.yaml │ ├── test_create_watchlist[exchange0-symbols0].yaml │ ├── test_create_watchlist[exchange1-symbols1].yaml │ ├── test_list_watchlist.yaml │ └── test_remove_from_watchlist.yaml ├── conftest.py ├── test_client.py ├── test_equity.py ├── test_funding.py ├── test_fx.py ├── test_integration.py ├── test_market.py ├── test_order.py ├── test_product.py ├── test_ratings.py ├── test_trade.py ├── test_transaction.py └── test_watchlist.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | registries: 3 | python-index-pypi-python-org-simple: 4 | type: python-index 5 | url: https://pypi.python.org/simple/ 6 | username: "${{secrets.PYTHON_INDEX_PYPI_PYTHON_ORG_SIMPLE_USERNAME}}" 7 | password: "${{secrets.PYTHON_INDEX_PYPI_PYTHON_ORG_SIMPLE_PASSWORD}}" 8 | 9 | updates: 10 | - package-ecosystem: pip 11 | directory: "/" 12 | schedule: 13 | interval: daily 14 | open-pull-requests-limit: 10 15 | ignore: 16 | - dependency-name: pytest-asyncio 17 | versions: 18 | - 0.15.0 19 | - dependency-name: faker 20 | versions: 21 | - 5.8.0 22 | - 6.0.0 23 | - 6.1.1 24 | - 6.3.0 25 | - 6.4.1 26 | - 6.5.0 27 | - 6.5.2 28 | - 6.6.0 29 | - 6.6.1 30 | - 6.6.2 31 | - 6.6.3 32 | - 7.0.1 33 | - 8.0.0 34 | - 8.1.0 35 | - dependency-name: python-dotenv 36 | versions: 37 | - 0.16.0 38 | - 0.17.0 39 | - dependency-name: pytest 40 | versions: 41 | - 6.2.2 42 | - 6.2.3 43 | - dependency-name: pre-commit 44 | versions: 45 | - 2.10.0 46 | - 2.10.1 47 | - 2.11.0 48 | - 2.11.1 49 | - dependency-name: aioresponses 50 | versions: 51 | - 0.7.2 52 | - dependency-name: aiohttp 53 | versions: 54 | - 3.7.4 55 | - 3.7.4.post0 56 | - dependency-name: pydantic 57 | versions: 58 | - "1.8" 59 | - 1.8.1 60 | registries: 61 | - python-index-pypi-python-org-simple 62 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 10 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 2 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [master] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [master] 20 | schedule: 21 | - cron: "42 12 * * 1" 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: ["python"] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.github/workflows/pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: pre-commit 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [master] 7 | 8 | jobs: 9 | pre-commit: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-python@v3 17 | - uses: pre-commit/action@v3.0.0 18 | with: 19 | token: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | release: 9 | strategy: 10 | fail-fast: true 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Set output 15 | id: vars 16 | run: echo ::set-output name=tag::${GITHUB_REF#refs/*/} 17 | - uses: actions/setup-python@v2 18 | with: 19 | python-version: 3.7 20 | - name: Run image 21 | uses: abatilo/actions-poetry@v2.0.0 22 | with: 23 | poetry-version: 1.3.2 24 | - name: Publish python package 25 | run: poetry version ${{ steps.vars.outputs.tag }} && poetry config pypi-token.pypi ${{ secrets.PYPI_API_KEY }} && poetry build && poetry publish 26 | - name: Auto commit pyproject.toml 27 | uses: EndBug/add-and-commit@v4.4.0 28 | with: 29 | add: pyproject.toml 30 | branch: master 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [master] 7 | 8 | jobs: 9 | ci: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] 14 | poetry-version: [1.3.2] 15 | os: [ubuntu-latest, macos-latest, windows-latest] 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-python@v2 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Run image 23 | uses: abatilo/actions-poetry@v2.0.0 24 | with: 25 | poetry-version: ${{ matrix.poetry-version }} 26 | - name: Run Tests 27 | env: # Or as an environment variable 28 | STAKE_TOKEN: ${{ secrets.STAKE_TOKEN }} 29 | run: poetry install && poetry run pytest --cov=stake --cov-report=xml && poetry run coverage-badge -f -o coverage.svg 30 | - name: Upload coverage 31 | uses: actions/upload-artifact@v2 32 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == 3.7 }} 33 | with: 34 | name: coverage 35 | path: coverage.svg 36 | - name: Auto commit coverage badge 37 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == 3.7 }} 38 | uses: EndBug/add-and-commit@v4.4.0 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | - name: Run codacy-coverage-reporter 42 | uses: codacy/codacy-coverage-reporter-action@master 43 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == 3.7 }} 44 | with: 45 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 46 | coverage-reports: coverage.xml 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | 140 | .vscode 141 | .history 142 | 143 | .env 144 | 145 | STAKE.*.json 146 | .idea 147 | DS_Store 148 | .DS_Store 149 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: "https://github.com/pre-commit/pre-commit-hooks" 3 | rev: v4.3.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - id: check-added-large-files 9 | - id: requirements-txt-fixer 10 | - repo: "https://github.com/psf/black" 11 | rev: 22.6.0 12 | hooks: 13 | - id: black 14 | - repo: "https://github.com/pre-commit/mirrors-mypy" 15 | rev: v0.961 16 | hooks: 17 | - id: mypy 18 | - repo: "https://github.com/PyCQA/isort" 19 | rev: 5.12.0 20 | hooks: 21 | - id: isort 22 | - repo: "https://github.com/asottile/seed-isort-config" 23 | rev: v2.2.0 24 | hooks: 25 | - id: seed-isort-config 26 | - repo: "https://github.com/myint/docformatter" 27 | rev: v1.4 28 | hooks: 29 | - id: docformatter 30 | args: 31 | - "--in-place" 32 | - repo: https://github.com/charliermarsh/ruff-pre-commit 33 | # Ruff version. 34 | rev: "v0.0.254" 35 | hooks: 36 | - id: ruff 37 | - repo: https://github.com/pre-commit/mirrors-prettier 38 | rev: v3.0.0-alpha.4 39 | hooks: 40 | - id: prettier 41 | types_or: [json, yaml, markdown] 42 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at tabacco.stefano@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | For answers to common questions about this code of conduct, see 74 | https://www.contributor-covenant.org/faq 75 | -------------------------------------------------------------------------------- /coverage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | coverage 17 | coverage 18 | 99% 19 | 99% 20 | 21 | 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "stake" 3 | version = "0.3.0" 4 | readme = "README.md" 5 | homepage = "https://github.com/stabacco/stake-python" 6 | repository = "https://github.com/stabacco/stake-python" 7 | description = "Unofficial https://hellostake.com API wrapper." 8 | authors = ["Stefano Tabacco "] 9 | license = "Apache-2.0" 10 | keywords = ["stake","trading","stocks","financial","python"] 11 | 12 | [tool.poetry.dependencies] 13 | python = ">=3.8,<4.0.0" 14 | python-dotenv = "^0.13.0" 15 | pydantic = "^2.3" 16 | inflection = "^0.5.0" 17 | aiohttp = "^3.9" 18 | single-version = "^1.2.2" 19 | 20 | [tool.poetry.dev-dependencies] 21 | pytest = "^7.2.0" 22 | pytest-asyncio = "^0.14.0" 23 | pre-commit-hooks = "^3.1.0" 24 | pre-commit = "^2.12.0" 25 | pytest-coverage = "^0.0" 26 | black = {version = "^19.10b0", allow-prereleases = true} 27 | pytest-mock = "^3.3.0" 28 | faker = "^4.14.0" 29 | coverage-badge = "^1.0.1" 30 | pytest-recording = "^0.12.0" 31 | 32 | [tool.poetry.group.dev.dependencies] 33 | bump-pydantic = "^0.8.0" 34 | 35 | [tool.isort] 36 | profile = "black" 37 | known_third_party = ["aiohttp", "dotenv", "faker", "inflection", "pydantic", "pytest", "single_version"] 38 | [build-system] 39 | requires = ["poetry>=0.12"] 40 | build-backend = "poetry.masonry.api" 41 | 42 | packages = [ 43 | { include = "stake", from="." }, 44 | ] 45 | 46 | [tool.ruff] 47 | # Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default. 48 | select = ["E", "F"] 49 | ignore = ["E501", "E731"] 50 | fixable = ["A", "B", "C", "D", "E", "F"] 51 | unfixable = [] 52 | 53 | # Exclude a variety of commonly ignored directories. 54 | exclude = [ 55 | ".bzr", 56 | ".direnv", 57 | ".eggs", 58 | ".git", 59 | ".mypy_cache", 60 | ".nox", 61 | ".pants.d", 62 | ".pytype", 63 | ".ruff_cache", 64 | ".tox", 65 | ".venv", 66 | "__pypackages__", 67 | "_build", 68 | "buck-out", 69 | "build", 70 | "dist", 71 | "venv", 72 | ] 73 | 74 | line-length = 100 75 | 76 | # Allow unused variables when underscore-prefixed. 77 | dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" 78 | 79 | 80 | [tool.ruff.mccabe] 81 | # Unlike Flake8, default to a complexity level of 10. 82 | max-complexity = 10 83 | -------------------------------------------------------------------------------- /stake/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from pathlib import Path 3 | 4 | from single_version import get_version 5 | 6 | from .client import * # noqa: F401, F403 7 | from .common import * # noqa: F401, F403 8 | from .constant import * # noqa: F401, F403 9 | from .funding import * # noqa: F401, F403 10 | from .fx import * # noqa: F401, F403 11 | from .market import * # noqa: F401, F403 12 | from .order import * # noqa: F401, F403 13 | from .product import * # noqa: F401, F403 14 | from .ratings import * # noqa: F401, F403 15 | from .trade import * # noqa: F401, F403 16 | from .transaction import * # noqa: F401, F403 17 | from .watchlist import * # noqa: F401, F403 18 | 19 | __version__ = get_version("stake", Path(__file__).parent.parent) 20 | -------------------------------------------------------------------------------- /stake/asx/__init__.py: -------------------------------------------------------------------------------- 1 | from . import equity, funding, market, order, product, trade, transaction # noqa 2 | from .common import * # noqa: F401, F403 3 | from .equity import * # noqa: F401, F403 4 | from .funding import * # noqa: F401, F403 5 | from .market import * # noqa: F401, F403 6 | from .order import * # noqa: F401, F403 7 | from .product import * # noqa: F401, F403 8 | from .trade import * # noqa: F401, F403 9 | from .transaction import * # noqa: F401, F403 10 | -------------------------------------------------------------------------------- /stake/asx/common.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Side(str, Enum): 5 | BUY = "BUY" 6 | SELL = "SELL" 7 | 8 | 9 | class TradeType(str, Enum): 10 | """The type of trade the user is requesting.""" 11 | 12 | MARKET: str = "MARKET_TO_LIMIT" 13 | LIMIT: str = "LIMIT" 14 | -------------------------------------------------------------------------------- /stake/asx/equity.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import BaseModel, ConfigDict, Field 4 | 5 | from stake.common import BaseClient, camelcase 6 | 7 | __all__ = ["EquityPositions"] 8 | 9 | 10 | class EquityPosition(BaseModel): 11 | available_for_trading_qty: Optional[int] = None 12 | average_price: Optional[str] = None 13 | instrument_id: Optional[str] = None 14 | market_value: Optional[str] = None 15 | mkt_price: Optional[str] = None 16 | name: Optional[str] = None 17 | open_qty: Optional[int] = None 18 | prior_close: Optional[str] = None 19 | recent_announcement: Optional[bool] = None 20 | sensitive: Optional[bool] = None 21 | symbol: Optional[str] = None 22 | unrealized_day_pl_percent: Optional[float] = Field( 23 | None, alias="unrealizedDayPLPercent" 24 | ) 25 | unrealized_day_pl: Optional[float] = Field(None, alias="unrealizedDayPL") 26 | unrealized_pl_percent: Optional[float] = Field(None, alias="unrealizedPLPercent") 27 | unrealized_pl: Optional[float] = Field(None, alias="unrealizedPL") 28 | model_config = ConfigDict(alias_generator=camelcase) 29 | 30 | 31 | class EquityPositions(BaseModel): 32 | """Represents the user's portforlio, with the list of the currently 33 | available equities.""" 34 | 35 | page_num: Optional[int] = None 36 | has_next: Optional[bool] = None 37 | equity_positions: Optional[List[EquityPosition]] = None 38 | model_config = ConfigDict(alias_generator=camelcase) 39 | 40 | 41 | class EquitiesClient(BaseClient): 42 | async def list(self) -> EquityPositions: 43 | """Displays the contents of your portfolio. 44 | 45 | Returns: 46 | EquityPositions: The list of your equities. 47 | """ 48 | data = await self._client.get(self._client.exchange.equity_positions) 49 | return EquityPositions(**data) 50 | -------------------------------------------------------------------------------- /stake/asx/funding.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | from enum import Enum 4 | from typing import List, Optional 5 | from urllib.parse import urlencode 6 | 7 | from pydantic import BaseModel, ConfigDict, Field 8 | from pydantic.types import UUID 9 | 10 | from stake.asx.transaction import Sort 11 | from stake.common import BaseClient, camelcase 12 | 13 | __all__ = ["FundingRequest", "FundingStatus"] 14 | 15 | 16 | class Action(str, Enum): 17 | DEPOSIT = "DEPOSIT" 18 | DIVIDEND_DEPOSIT = "DIVIDEND_DEPOSIT" 19 | SETTLEMENT = "SETTLEMENT" 20 | TRANSFER = "TRANSFER" 21 | WITHDRAWAL = "WITHDRAWAL" 22 | ADJUSTMENT = "ADJUSTMENT" 23 | 24 | 25 | class Currency(str, Enum): 26 | AUD = "AUD" 27 | 28 | 29 | class FundingSide(str, Enum): 30 | CREDIT = "CREDIT" 31 | DEBIT = "DEBIT" 32 | 33 | 34 | class FundingStatus(str, Enum): 35 | AWAITING_APPROVAL = "AWAITING_APPROVAL" 36 | PENDING = "PENDING" 37 | RECONCILED = "RECONCILED" 38 | 39 | 40 | class FundingRequest(BaseModel): 41 | """Example call: 42 | 43 | t = FundingRequest( 44 | statuses=[FundingStatus.RECONCILED, FundingStatus.PENDING], 45 | actions=[Action.DIVIDEND_DEPOSIT], 46 | sort=[Sort(attribute='insertedAt', direction='asc')]) 47 | """ 48 | 49 | statuses: Optional[List[FundingStatus]] = Field( 50 | [FundingStatus.RECONCILED], description="Used to filter the results." 51 | ) 52 | sort: Optional[List[Sort]] = Field( 53 | None, description="Use this to sort the results." 54 | ) 55 | actions: Optional[List[Action]] = Field( 56 | None, description="Used to filter the results. Only works for funding" 57 | ) 58 | limit: int = 100 59 | offset: int = 0 60 | 61 | def as_url_params(self) -> str: 62 | """Returns the parameters for the GET request.""" 63 | data = json.loads( 64 | self.model_dump_json( 65 | exclude_none=True, 66 | ) 67 | ) 68 | if data.get("sort"): 69 | data["sort"] = [f"{d['attribute']},{d['direction']}" for d in data["sort"]] 70 | 71 | return ( 72 | urlencode(data, doseq=True) 73 | .replace("statuses", "status") 74 | .replace("actions", "action") 75 | .replace("limit", "size") 76 | .replace("offset", "page") 77 | ) 78 | 79 | 80 | class FundingRecord(BaseModel): 81 | action: Optional[Action] = None 82 | amount: Optional[float] = None 83 | approved_by: Optional[str] = Field(None, alias="approvedBy") 84 | currency: Optional[Currency] = None 85 | customer_fee: Optional[int] = None 86 | id: Optional[UUID] = None 87 | inserted_at: Optional[datetime] = None 88 | reference: Optional[str] = None 89 | side: Optional[FundingSide] = None 90 | status: Optional[FundingStatus] = None 91 | updated_at: Optional[datetime] = None 92 | user_id: Optional[UUID] = None 93 | model_config = ConfigDict(alias_generator=camelcase) 94 | 95 | 96 | class Fundings(BaseModel): 97 | fundings: Optional[List[FundingRecord]] = Field(None, alias="items") 98 | has_next: Optional[bool] = None 99 | page: Optional[int] = None 100 | total_items: Optional[int] = Field(None, alias="totalItems") 101 | model_config = ConfigDict(alias_generator=camelcase) 102 | 103 | 104 | class CashAvailable(BaseModel): 105 | """Holds information about the cash available in your account.""" 106 | 107 | buying_power: Optional[float] = None 108 | cash_available_for_transfer: Optional[float] = None 109 | cash_available_for_withdrawal_hold: Optional[float] = None 110 | cash_available_for_withdrawal: Optional[float] = None 111 | clearing_cash: Optional[float] = None 112 | pending_buys: Optional[int] = None 113 | pending_withdrawals: Optional[int] = None 114 | settled_cash: Optional[float] = None 115 | settlement_hold: Optional[int] = None 116 | trade_settlement: Optional[int] = None 117 | model_config = ConfigDict(alias_generator=camelcase) 118 | 119 | 120 | class FundingsClient(BaseClient): 121 | async def list(self, request: FundingRequest) -> Fundings: 122 | """Returns the funding transactions executed by the user. 123 | 124 | Args: 125 | 126 | request (FundingRequest): the funding request. 127 | 128 | Returns: 129 | Fundings: the list of the fundings retrieved. 130 | """ 131 | 132 | data: dict = await self._client.get( 133 | f"{self._client.exchange.transactions}?{request.as_url_params()}" 134 | ) 135 | 136 | return Fundings(**data) 137 | 138 | async def in_flight(self) -> Fundings: 139 | """Returns the funds currently in flight.""" 140 | request = FundingRequest( 141 | statuses=[FundingStatus.PENDING, FundingStatus.AWAITING_APPROVAL] 142 | ) 143 | return await self.list(request=request) 144 | 145 | async def cash_available(self) -> CashAvailable: 146 | data = await self._client.get(self._client.exchange.cash_available) 147 | return CashAvailable(**data) 148 | -------------------------------------------------------------------------------- /stake/asx/market.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from typing import Optional 3 | 4 | import pydantic 5 | from pydantic import ConfigDict 6 | 7 | from stake.common import BaseClient, camelcase 8 | 9 | __all__ = ["MarketStatus"] 10 | 11 | 12 | class Status(pydantic.BaseModel): 13 | current: Optional[str] = None 14 | 15 | 16 | class MarketStatus(pydantic.BaseModel): 17 | last_trading_date: Optional[date] = None 18 | status: Status 19 | model_config = ConfigDict(alias_generator=camelcase) 20 | 21 | 22 | class MarketClient(BaseClient): 23 | """Retrieves informations about the current status of the market.""" 24 | 25 | async def get(self) -> MarketStatus: 26 | data = await self._client.get(self._client.exchange.market_status) 27 | return MarketStatus(**data) 28 | 29 | async def is_open(self) -> bool: 30 | status = await self.get() 31 | return status.status.current == "open" 32 | -------------------------------------------------------------------------------- /stake/asx/order.py: -------------------------------------------------------------------------------- 1 | from datetime import date, datetime 2 | from typing import List, Optional, Union 3 | from uuid import UUID 4 | 5 | from pydantic import BaseModel, ConfigDict, Field 6 | 7 | from stake.asx.common import TradeType 8 | from stake.asx.transaction import Side 9 | from stake.common import BaseClient, camelcase 10 | 11 | __all__ = ["CancelOrderRequest"] 12 | 13 | 14 | class Order(BaseModel): 15 | average_price: Optional[float] = None 16 | broker: Optional[str] = None 17 | completed_timestamp: Optional[datetime] = None 18 | estimated_brokerage: Optional[float] = None 19 | estimated_exchange_fees: Optional[float] = None 20 | expires_at: Optional[datetime] = None 21 | filled_units: Optional[float] = None 22 | instrument_code: str 23 | instrument_id: Optional[str] = None 24 | limit_price: Optional[float] = None 25 | order_completion_type: Optional[str] = None 26 | order_id: UUID = Field(alias="id") 27 | order_status: Optional[str] = None 28 | placed_timestamp: datetime 29 | side: Side 30 | type: TradeType 31 | units_remaining: Optional[int] = None 32 | validity_date: Optional[Union[date, datetime]] = None 33 | validity: Optional[str] = None 34 | model_config = ConfigDict(alias_generator=camelcase) 35 | 36 | 37 | class Brokerage(BaseModel): 38 | brokerage_fee: Optional[float] = None 39 | brokerage_discount: Optional[float] = None 40 | fixed_fee: Optional[float] = None 41 | variable_fee_percentage: Optional[float] = None 42 | variable_limit: Optional[int] = None 43 | model_config = ConfigDict(alias_generator=camelcase) 44 | 45 | 46 | class CancelOrderRequest(BaseModel): 47 | order_id: str 48 | 49 | 50 | class OrdersClient(BaseClient): 51 | """This client is in charge of dealing with your pending orders. 52 | 53 | These are the orders limit/stop etc.. that have not been traded yet. 54 | """ 55 | 56 | async def list(self) -> List[Order]: 57 | """Lists all your pending orders. 58 | 59 | Returns: 60 | List[Order]: The list of pending orders. 61 | """ 62 | data = await self._client.get(self._client.exchange.orders) 63 | return [Order(**d) for d in data] 64 | 65 | async def cancel(self, order: Union[Order, CancelOrderRequest]) -> bool: 66 | """Cancels a pending order. 67 | 68 | Args: 69 | order (Union[Order, CancelOrderRequest])): an existing order or its ID. 70 | 71 | Returns: 72 | bool: True if the deletion was succesful. 73 | """ 74 | await self._client.post( 75 | self._client.exchange.cancel_order.format(orderId=order.order_id), 76 | payload={}, 77 | ) 78 | return True 79 | 80 | async def brokerage(self, order_amount: float) -> Brokerage: 81 | """Retrieve the brokerage for an order. 82 | 83 | Args: 84 | order_amount (float): the per unit purchase price 85 | Returns: 86 | Brokerage: ??? 87 | """ 88 | 89 | data = await self._client.get( 90 | self._client.exchange.brokerage.format(orderAmount=order_amount) 91 | ) 92 | return Brokerage(**data) 93 | -------------------------------------------------------------------------------- /stake/asx/product.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import BaseModel, ConfigDict 4 | 5 | from stake.common import BaseClient, camelcase 6 | 7 | __all__ = ["ProductSearchByName"] 8 | 9 | 10 | class ProductSearchByName(BaseModel): 11 | """Request used to search for Products by their name or description.""" 12 | 13 | keyword: str 14 | 15 | 16 | class Instrument(BaseModel): 17 | 18 | instrument_id: str 19 | symbol: str 20 | name: Optional[str] = None 21 | type: str 22 | recent_announcement: Optional[bool] = None 23 | sensitive: Optional[bool] = None 24 | model_config = ConfigDict(alias_generator=camelcase) 25 | 26 | 27 | class Product(BaseModel): 28 | symbol: Optional[str] = None 29 | out_of_market_quantity: Optional[int] = None 30 | out_of_market_surplus: Optional[int] = None 31 | market_status: Optional[str] = None 32 | last_traded_exchange: Optional[str] = None 33 | last_traded_timestamp: Optional[int] = None 34 | last_trade: Optional[str] = None 35 | bid: Optional[float] = None 36 | ask: Optional[float] = None 37 | prior_close: Optional[float] = None 38 | open: Optional[float] = None 39 | high: Optional[float] = None 40 | low: Optional[float] = None 41 | points_change: Optional[float] = None 42 | percentage_change: Optional[float] = None 43 | out_of_market_price: Optional[float] = None 44 | model_config = ConfigDict(alias_generator=camelcase) 45 | 46 | 47 | class ProductsClient(BaseClient): 48 | async def get(self, symbol: str) -> Optional[Product]: 49 | """Given a symbol it will return the matching product. 50 | 51 | Examples: 52 | coles_product = self.get("COL") 53 | """ 54 | data = await self._client.get( 55 | self._client.exchange.symbol.format(symbol=symbol) 56 | ) 57 | 58 | return Product(**data) 59 | 60 | async def search(self, request: ProductSearchByName) -> List[Instrument]: 61 | products = await self._client.get( 62 | self._client.exchange.products_suggestions.format(keyword=request.keyword) 63 | ) 64 | return [Instrument(**product) for product in products["instruments"]] 65 | 66 | async def product_from_instrument( 67 | self, instrument: Instrument 68 | ) -> Optional[Product]: 69 | return await self.get(instrument.symbol) 70 | -------------------------------------------------------------------------------- /stake/asx/trade.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from enum import Enum 3 | from typing import Optional, Union 4 | 5 | from pydantic import BaseModel, ConfigDict, Field, model_validator, validate_call 6 | 7 | from stake.asx.common import TradeType 8 | from stake.asx.order import Order 9 | from stake.common import BaseClient, camelcase 10 | 11 | __all__ = [ 12 | "LimitBuyRequest", 13 | "LimitSellRequest", 14 | "MarketBuyRequest", 15 | "MarketSellRequest", 16 | ] 17 | 18 | 19 | class ExpiryDate(str, Enum): 20 | """The expiry date for the trade.""" 21 | 22 | IN_ONE_DAY: str = "GFD" 23 | IN_THIRTY_DAYS: str = "GTC" 24 | 25 | 26 | class GenericTradeRequest(BaseModel): 27 | """Issues a limit buy request.""" 28 | 29 | symbol: Optional[str] = Field( 30 | None, 31 | alias="instrumentCode", 32 | description="The symbol for the ", 33 | ) 34 | instrument_code: Optional[str] = None 35 | 36 | units: int 37 | validity: ExpiryDate = ExpiryDate.IN_THIRTY_DAYS 38 | validity_date: Optional[datetime] = None 39 | model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True) 40 | 41 | @model_validator(mode="after") 42 | @classmethod 43 | def symbol_or_instrument_type( 44 | cls, value: "GenericTradeRequest" 45 | ) -> "GenericTradeRequest": 46 | if value.symbol is None and value.instrument_code is None: 47 | raise ValueError("Either specify symbol or instrument_code") 48 | return value 49 | 50 | 51 | class LimitBuyRequest(GenericTradeRequest): 52 | side: str = "BUY" 53 | type: TradeType = TradeType.LIMIT 54 | price: float 55 | 56 | 57 | class LimitSellRequest(GenericTradeRequest): 58 | side: str = "SELL" 59 | type: TradeType = TradeType.LIMIT 60 | price: float 61 | 62 | 63 | class MarketBuyRequest(GenericTradeRequest): 64 | side: str = "BUY" 65 | type: TradeType = TradeType.MARKET 66 | price: Optional[float] = None 67 | 68 | 69 | class MarketSellRequest(GenericTradeRequest): 70 | side: str = "SELL" 71 | type: TradeType = TradeType.MARKET 72 | price: Optional[float] = None 73 | 74 | 75 | class TradesClient(BaseClient): 76 | """This client is used to buy/sell equities.""" 77 | 78 | @validate_call 79 | async def _trade( 80 | self, 81 | url: str, 82 | request: Union[ 83 | MarketBuyRequest, 84 | LimitBuyRequest, 85 | LimitSellRequest, 86 | MarketSellRequest, 87 | ], 88 | ) -> Order: 89 | """A generic function used to submit a trade, either buy or sell. 90 | 91 | Args: 92 | url: the url for buy/sell 93 | request:the trade request 94 | Returns: 95 | the Order 96 | """ 97 | if not request.instrument_code and request.symbol: 98 | # in this case we need to get the instrument name from the symbol 99 | instrument_id = await self._instrument_id_from_symbol(request.symbol) 100 | request.instrument_code = instrument_id 101 | 102 | data = await self._client.post( 103 | url, payload=request.model_dump(by_alias=True, exclude={"symbol"}) 104 | ) 105 | 106 | return Order(**data["order"]) 107 | 108 | async def _instrument_id_from_symbol(self, symbol: str) -> str: 109 | """Returns the instrument_id from an associated product.""" 110 | url = self._client.exchange.instrument_from_symbol.format(symbol=symbol) 111 | data = await self._client.post(url, payload={}) 112 | return data["instrumentId"] 113 | 114 | async def buy(self, request: Union[MarketBuyRequest, LimitBuyRequest]) -> Order: 115 | """Creates an order to buy equities. 116 | 117 | Args: 118 | request: the buy request 119 | 120 | Returns: 121 | the Order object 122 | """ 123 | # if the price has not been set(in the case of a market order), 124 | # we get the current ask price for that symbol. This seems to 125 | # be what the app is doing, the price value cannot be left null. 126 | if request.price is None: 127 | product = await self._client.products.get(request.symbol) 128 | assert product 129 | assert product.ask 130 | request.price = product.ask 131 | 132 | return await self._trade(self._client.exchange.orders, request) 133 | 134 | async def sell(self, request: Union[MarketSellRequest, LimitSellRequest]) -> Order: 135 | """Creates an order to sell equities. 136 | 137 | Args: 138 | request: the sell request 139 | 140 | Returns: 141 | the Order object 142 | """ 143 | # if the price has not been set, we get the current bid price for that symbol. 144 | if request.price is None: 145 | product = await self._client.products.get(request.symbol) 146 | assert product 147 | assert product.bid 148 | request.price = product.bid 149 | 150 | return await self._trade(self._client.exchange.orders, request) 151 | -------------------------------------------------------------------------------- /stake/asx/transaction.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import date, datetime 3 | from enum import Enum 4 | from typing import List, Optional 5 | from urllib.parse import urlencode 6 | 7 | from pydantic import BaseModel, ConfigDict, Field 8 | 9 | from stake.asx.common import Side 10 | from stake.common import BaseClient, camelcase 11 | 12 | __all__ = ["TransactionRecordRequest", "SortDirection", "Sort"] 13 | 14 | 15 | class SortDirection(str, Enum): 16 | ASC: str = "asc" 17 | DESC: str = "desc" 18 | 19 | 20 | class Sort(BaseModel): 21 | attribute: str 22 | direction: SortDirection 23 | 24 | 25 | class TransactionRecordRequest(BaseModel): 26 | """Example call: 27 | 28 | t = TransactionRecordRequest(sort=[Sort(attribute='insertedAt', direction='asc')]) 29 | """ 30 | 31 | sort: Optional[List[Sort]] = Field( 32 | None, description="Use this to sort the results." 33 | ) 34 | 35 | limit: int = 100 36 | offset: int = 0 37 | 38 | def as_url_params(self) -> str: 39 | """Returns the parameters for the GET request.""" 40 | data = json.loads( 41 | self.model_dump_json( 42 | exclude_none=True, 43 | ) 44 | ) 45 | if data.get("sort", None): 46 | data["sort"] = [f"{d['attribute']},{d['direction']}" for d in data["sort"]] 47 | 48 | return ( 49 | urlencode(data, doseq=True) 50 | .replace("limit", "size") 51 | .replace("offset", "page") 52 | ) 53 | 54 | 55 | class Transaction(BaseModel): 56 | average_price: Optional[float] = None 57 | broker_order_id: Optional[int] = None 58 | completed_timestamp: Optional[datetime] = None 59 | consideration: Optional[float] = None 60 | contract_note_number: Optional[int] = None 61 | contract_note_numbers: Optional[List[int]] = None 62 | contract_note_received: Optional[bool] = None 63 | effective_price: Optional[float] = None 64 | execution_date: Optional[date] = None 65 | instrument_id: Optional[str] = Field(None, alias="instrumentCode") 66 | limit_price: Optional[float] = None 67 | order_completion_type: Optional[str] = None 68 | order_status: Optional[str] = None 69 | placed_timestamp: Optional[datetime] = None 70 | side: Optional[Side] = None 71 | type: Optional[str] = None 72 | units: Optional[float] = None 73 | user_brokerage_fees: Optional[float] = None 74 | model_config = ConfigDict(alias_generator=camelcase) 75 | 76 | 77 | class Transactions(BaseModel): 78 | transactions: Optional[List[Transaction]] = Field(None, alias="items") 79 | has_next: Optional[bool] = None 80 | page: Optional[int] = None 81 | total_items: Optional[int] = None 82 | model_config = ConfigDict(alias_generator=camelcase) 83 | 84 | 85 | class TransactionsClient(BaseClient): 86 | async def list(self, request: TransactionRecordRequest) -> Transactions: 87 | """Returns the transactions executed by the user. 88 | 89 | Args: 90 | request (TransactionRecordRequest): 91 | used to filter the transactions we want to retrieve 92 | 93 | Returns: 94 | Transactions: The matching transactions 95 | """ 96 | 97 | data: dict = await self._client.get( 98 | f"{self._client.exchange.trade_activity}?{request.as_url_params()}" 99 | ) 100 | 101 | return Transactions(**data) 102 | -------------------------------------------------------------------------------- /stake/common.py: -------------------------------------------------------------------------------- 1 | import weakref 2 | from enum import Enum 3 | from functools import partial 4 | from typing import TYPE_CHECKING 5 | 6 | import inflection 7 | 8 | if TYPE_CHECKING: # pragma: no cover 9 | from stake.client import StakeClient 10 | 11 | camelcase = partial(inflection.camelize, uppercase_first_letter=False) 12 | 13 | __all__ = ["SideEnum"] 14 | 15 | 16 | class SideEnum(str, Enum): 17 | BUY = "B" 18 | SELL = "S" 19 | 20 | 21 | class BaseClient: 22 | # flake8: noqa 23 | def __init__(self, client: "StakeClient"): 24 | self._client = weakref.proxy(client) 25 | -------------------------------------------------------------------------------- /stake/constant.py: -------------------------------------------------------------------------------- 1 | # sourcery skip: use-fstring-for-concatenation 2 | from urllib.parse import urljoin 3 | 4 | from pydantic import BaseModel 5 | 6 | __all__ = ["ASX", "NYSE"] 7 | 8 | 9 | class NYSEUrl(BaseModel): 10 | """Contains all the visited stake urls for the NYSE.""" 11 | 12 | STAKE_URL: str = "https://global-prd-api.hellostake.com/api/" 13 | account_balance: str = urljoin( 14 | STAKE_URL, "cma/getAccountBalance", allow_fragments=True 15 | ) 16 | account_transactions: str = urljoin( 17 | STAKE_URL, "users/accounts/accountTransactions", allow_fragments=True 18 | ) 19 | brokerage: str = urljoin( 20 | STAKE_URL, "orders/brokerage?orderAmount={orderAmount}", allow_fragments=True 21 | ) 22 | cancel_order: str = urljoin( 23 | STAKE_URL, "orders/cancelOrder/{orderId}", allow_fragments=True 24 | ) 25 | cash_available: str = urljoin( 26 | STAKE_URL, "users/accounts/cashAvailableForWithdrawal", allow_fragments=True 27 | ) 28 | create_session: str = urljoin( 29 | STAKE_URL, "sessions/v2/createSession", allow_fragments=True 30 | ) 31 | equity_positions: str = urljoin( 32 | STAKE_URL, "users/accounts/v2/equityPositions", allow_fragments=True 33 | ) 34 | fund_details: str = urljoin(STAKE_URL, "fund/details", allow_fragments=True) 35 | market_status: str = urljoin(STAKE_URL, "utils/marketStatus", allow_fragments=True) 36 | orders: str = urljoin(STAKE_URL, "users/accounts/v2/orders", allow_fragments=True) 37 | products_suggestions: str = urljoin( 38 | STAKE_URL, "products/getProductSuggestions/{keyword}", allow_fragments=True 39 | ) 40 | quick_buy: str = urljoin( 41 | STAKE_URL, "purchaseorders/v2/quickBuy", allow_fragments=True 42 | ) 43 | quotes: str = urljoin( 44 | STAKE_URL, "quotes/marketData/{symbols}", allow_fragments=True 45 | ) 46 | rate: str = urljoin(STAKE_URL, "wallet/rate", allow_fragments=True) 47 | ratings: str = urljoin( 48 | STAKE_URL, 49 | "data/calendar/ratings?tickers={symbols}&pageSize={limit}", 50 | allow_fragments=True, 51 | ) 52 | sell_orders: str = urljoin(STAKE_URL, "sellorders", allow_fragments=True) 53 | symbol: str = urljoin( 54 | STAKE_URL, 55 | "products/searchProduct?symbol={symbol}&page=1&max=1", 56 | allow_fragments=True, 57 | ) 58 | transaction_history: str = urljoin( 59 | STAKE_URL, "users/accounts/transactionHistory", allow_fragments=True 60 | ) 61 | transaction_details: str = urljoin( 62 | STAKE_URL, 63 | ( 64 | "users/accounts/transactionDetails?" 65 | "reference={reference}&referenceType={reference_type}" 66 | ), 67 | allow_fragments=True, 68 | ) 69 | transactions: str = urljoin( 70 | STAKE_URL, "users/accounts/transactions", allow_fragments=True 71 | ) 72 | users: str = urljoin(STAKE_URL, "user", allow_fragments=True) 73 | 74 | # deprecated, use update_watchlist instead 75 | watchlist_modify: str = urljoin( 76 | STAKE_URL, "instruments/addRemoveInstrumentWatchlist", allow_fragments=True 77 | ) 78 | # deprecated, use read_watchlist instead 79 | watchlist: str = urljoin( 80 | STAKE_URL, "products/productsWatchlist/{userId}", allow_fragments=True 81 | ) 82 | 83 | watchlists: str = "https://api.prd.stakeover.io/us/instrument/watchlists" 84 | create_watchlist: str = "https://api.prd.stakeover.io/us/instrument/watchlist" 85 | read_watchlist: str = ( 86 | "https://api.prd.stakeover.io/us/instrument/watchlist/{watchlist_id}" 87 | ) 88 | update_watchlist: str = read_watchlist + "/items" 89 | 90 | 91 | NYSE = NYSEUrl() 92 | 93 | 94 | class ASXUrl(BaseModel): 95 | """Contains all the visited stake urls for the ASX.""" 96 | 97 | ASX_STAKE_URL: str = "https://global-prd-api.hellostake.com/api/asx/" 98 | brokerage: str = urljoin( 99 | ASX_STAKE_URL, 100 | "orders/brokerage?orderAmount={orderAmount}", 101 | allow_fragments=True, 102 | ) 103 | cash_available: str = urljoin(ASX_STAKE_URL, "cash", allow_fragments=True) 104 | cancel_order: str = urljoin( 105 | ASX_STAKE_URL, "orders/{orderId}/cancel", allow_fragments=True 106 | ) 107 | equity_positions: str = urljoin( 108 | ASX_STAKE_URL, "instrument/equityPositions", allow_fragments=True 109 | ) 110 | market_status: str = "https://early-bird-promo.hellostake.com/marketStatus" 111 | 112 | orders: str = urljoin(ASX_STAKE_URL, "orders", allow_fragments=True) 113 | 114 | products_suggestions: str = urljoin( 115 | ASX_STAKE_URL, 116 | "instrument/search?searchKey={keyword}", 117 | allow_fragments=True, 118 | ) 119 | 120 | symbol: str = urljoin( 121 | ASX_STAKE_URL, 122 | "instrument/singleQuote/{symbol}", 123 | allow_fragments=True, 124 | ) 125 | 126 | trade_activity: str = urljoin( 127 | ASX_STAKE_URL, "orders/tradeActivity", allow_fragments=True 128 | ) 129 | watchlists: str = urljoin( 130 | ASX_STAKE_URL, "instrument/v2/watchlists", allow_fragments=True 131 | ) 132 | create_watchlist: str = urljoin( 133 | ASX_STAKE_URL, "instrument/v2/watchlist", allow_fragments=True 134 | ) 135 | read_watchlist: str = urljoin( 136 | ASX_STAKE_URL, "instrument/v2/watchlist/{watchlist_id}", allow_fragments=True 137 | ) 138 | update_watchlist: str = urljoin( 139 | ASX_STAKE_URL, 140 | "instrument/v2/watchlist/{watchlist_id}/items", 141 | allow_fragments=True, 142 | ) 143 | instrument_from_symbol: str = urljoin( 144 | ASX_STAKE_URL, "instrument/view/{symbol}", allow_fragments=True 145 | ) 146 | transactions: str = urljoin(ASX_STAKE_URL, "transactions", allow_fragments=True) 147 | users: str = "https://global-prd-api.hellostake.com/api/user" 148 | 149 | 150 | ASX = ASXUrl() 151 | -------------------------------------------------------------------------------- /stake/equity.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from typing import List, Optional 3 | from uuid import UUID 4 | 5 | from pydantic import BaseModel, ConfigDict, Field 6 | 7 | from stake.common import BaseClient, SideEnum, camelcase 8 | 9 | __all__ = ["EquityCategory"] 10 | 11 | 12 | class EquityCategory(str, Enum): 13 | ETF = "ETF" 14 | STOCK = "Stock" 15 | 16 | 17 | class EquityPosition(BaseModel): 18 | ask_price: Optional[float] = None 19 | available_for_trading_qty: float 20 | average_price: float = Field(alias="avgPrice") 21 | bid_price: Optional[float] = None 22 | category: Optional[EquityCategory] = None 23 | cost_basis: float 24 | daily_return_value: float 25 | encoded_name: str 26 | instrument_id: UUID = Field(alias="instrumentID") 27 | last_trade: float 28 | market_price: float = Field(alias="mktPrice") 29 | market_value: float 30 | name: str 31 | open_qty: float 32 | period: str 33 | prior_close: float 34 | return_on_stock: Optional[float] = None 35 | side: SideEnum 36 | symbol: str 37 | unrealized_day_pl_percent: float = Field(alias="unrealizedDayPLPercent") 38 | unrealized_day_pl: float = Field(alias="unrealizedDayPL") 39 | unrealized_pl: float = Field(alias="unrealizedPL") 40 | url_image: str 41 | yearly_return_percentage: Optional[float] = None 42 | yearly_return_value: Optional[float] = None 43 | model_config = ConfigDict(alias_generator=camelcase) 44 | 45 | 46 | class EquityPositions(BaseModel): 47 | """Represents the user's portforlio, with the list of the currently 48 | available equities.""" 49 | 50 | equity_positions: List[EquityPosition] 51 | equity_value: float 52 | prices_only: bool 53 | model_config = ConfigDict(alias_generator=camelcase) 54 | 55 | 56 | class EquitiesClient(BaseClient): 57 | async def list(self) -> EquityPositions: 58 | """Displays the contents of your portfolio. 59 | 60 | Returns: 61 | EquityPositions: The list of your equities. 62 | """ 63 | data = await self._client.get(self._client.exchange.equity_positions) 64 | return EquityPositions(**data) 65 | -------------------------------------------------------------------------------- /stake/funding.py: -------------------------------------------------------------------------------- 1 | """Your current fundings.""" 2 | import asyncio 3 | import json 4 | from datetime import datetime 5 | from typing import List, Optional 6 | 7 | from pydantic import BaseModel, ConfigDict, Field 8 | 9 | from stake.common import BaseClient, camelcase 10 | from stake.transaction import TransactionHistoryType, TransactionRecordRequest 11 | 12 | 13 | class Funding(BaseModel): 14 | iof: Optional[str] = None 15 | vet: Optional[str] = None 16 | bsb: Optional[str] = None 17 | account_number: Optional[str] = None 18 | insert_date: Optional[datetime] = None 19 | channel: Optional[str] = None 20 | amount_to: Optional[float] = None 21 | amount_from: Optional[float] = None 22 | status: Optional[str] = None 23 | speed: Optional[str] = None 24 | fx_fee: Optional[float] = None 25 | express_fee: Optional[int] = None 26 | total_fee: Optional[float] = None 27 | spot_rate: Optional[float] = None 28 | reference: Optional[str] = None 29 | w8_fee: Optional[int] = None 30 | currency_from: Optional[str] = None 31 | currency_to: Optional[str] = None 32 | model_config = ConfigDict(alias_generator=camelcase) 33 | 34 | 35 | class CashSettlement(BaseModel): 36 | utc_time: datetime 37 | cash: float 38 | model_config = ConfigDict(alias_generator=camelcase) 39 | 40 | 41 | class CashAvailable(BaseModel): 42 | card_hold_amount: float 43 | cash_available_for_trade: float 44 | cash_available_for_withdrawal: float 45 | cash_balance: float 46 | cash_settlement: List[Optional[CashSettlement]] 47 | dw_cash_available_for_withdrawal: float 48 | pending_orders_amount: float 49 | pending_poli_amount: float 50 | pending_withdrawals: float 51 | reserved_cash: float 52 | model_config = ConfigDict(alias_generator=camelcase) 53 | 54 | 55 | class FundsInFlight(BaseModel): 56 | type: str 57 | insert_date_time: str 58 | estimated_arrival_time: str 59 | estimated_arrival_time_us: str = Field(alias="estimatedArrivalTimeUS") 60 | transaction_type: str 61 | to_amount: float 62 | from_amount: float 63 | model_config = ConfigDict(alias_generator=camelcase) 64 | 65 | 66 | class FundingsClient(BaseClient): 67 | async def list(self, request: TransactionRecordRequest) -> List[Funding]: 68 | payload = json.loads(request.model_dump_json(by_alias=True)) 69 | # looks like there is no way to pass filter the transactions here 70 | data = await self._client.post( 71 | self._client.exchange.transaction_history, payload=payload 72 | ) 73 | 74 | funding_transactions = [ 75 | d 76 | for d in data 77 | if d["referenceType"] == TransactionHistoryType.FUNDING.value 78 | ] 79 | 80 | details = await asyncio.gather( 81 | *[ 82 | self._client.get( 83 | self._client.exchange.transaction_details.format( 84 | reference=funding_transaction["reference"], 85 | reference_type=funding_transaction["referenceType"], 86 | ), 87 | ) 88 | for funding_transaction in funding_transactions 89 | ] 90 | ) 91 | return [Funding(**d) for d in details] 92 | 93 | async def in_flight(self) -> List[FundsInFlight]: 94 | """Returns the funds currently in flight.""" 95 | data = await self._client.get(self._client.exchange.fund_details) 96 | data = data.get("fundsInFlight") 97 | return [FundsInFlight(**d) for d in data] if data else [] 98 | 99 | async def cash_available(self) -> CashAvailable: 100 | data = await self._client.get(self._client.exchange.cash_available) 101 | return CashAvailable(**data) 102 | -------------------------------------------------------------------------------- /stake/fx.py: -------------------------------------------------------------------------------- 1 | """Currency conversion.""" 2 | from enum import Enum 3 | 4 | from pydantic import BaseModel, ConfigDict 5 | from pydantic.types import UUID4 6 | 7 | from stake.common import BaseClient, camelcase 8 | 9 | __all__ = ["FxConversionRequest", "CurrencyEnum"] 10 | 11 | 12 | class CurrencyEnum(str, Enum): 13 | AUD: str = "AUD" 14 | USD: str = "USD" 15 | 16 | 17 | class FxConversionRequest(BaseModel): 18 | from_currency: CurrencyEnum 19 | to_currency: CurrencyEnum 20 | from_amount: float 21 | model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True) 22 | 23 | 24 | class FxConversion(BaseModel): 25 | from_currency: CurrencyEnum 26 | to_currency: CurrencyEnum 27 | from_amount: float 28 | to_amount: float 29 | rate: float 30 | quote: UUID4 31 | model_config = ConfigDict(alias_generator=camelcase) 32 | 33 | 34 | class FxClient(BaseClient): 35 | async def convert( 36 | self, currency_conversion_request: FxConversionRequest 37 | ) -> FxConversion: 38 | """Converts from one currency to another.""" 39 | data = await self._client.post( 40 | self._client.exchange.rate, 41 | payload=currency_conversion_request.model_dump(by_alias=True), 42 | ) 43 | return FxConversion(**data) 44 | -------------------------------------------------------------------------------- /stake/market.py: -------------------------------------------------------------------------------- 1 | """Checks the market status.""" 2 | 3 | from typing import Optional 4 | 5 | from pydantic import BaseModel, ConfigDict, Field 6 | 7 | from stake.common import BaseClient, camelcase 8 | 9 | __all__ = ["MarketStatus"] 10 | 11 | 12 | class Status(BaseModel): 13 | change_at: Optional[str] = Field(None, alias="change_at") 14 | next: Optional[str] = None 15 | current: str 16 | model_config = ConfigDict(alias_generator=camelcase) 17 | 18 | 19 | class MarketStatus(BaseModel): 20 | status: Status 21 | model_config = ConfigDict(alias_generator=camelcase) 22 | 23 | 24 | class MarketClient(BaseClient): 25 | async def get(self) -> MarketStatus: 26 | data = await self._client.get(self._client.exchange.market_status) 27 | return MarketStatus(**data["response"]) 28 | 29 | async def is_open(self) -> bool: 30 | status = await self.get() 31 | return status.status.current == "open" 32 | -------------------------------------------------------------------------------- /stake/order.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from enum import IntEnum 3 | from typing import List, Optional, Union 4 | 5 | from pydantic import BaseModel, ConfigDict, Field 6 | 7 | from stake.common import BaseClient, SideEnum, camelcase 8 | 9 | __all__ = ["OrderTypeEnum", "CancelOrderRequest"] 10 | 11 | 12 | class OrderTypeEnum(IntEnum): 13 | MARKET = 1 14 | LIMIT = 2 15 | STOP = 3 16 | 17 | 18 | class Order(BaseModel): 19 | order_no: str 20 | order_id: str = Field(alias="orderID") 21 | order_cash_amt: int 22 | symbol: str 23 | stop_price: float 24 | side: SideEnum 25 | order_type: OrderTypeEnum 26 | cum_qty: str 27 | limit_price: float 28 | created_when: datetime 29 | order_status: int 30 | order_qty: float 31 | description: str 32 | instrument_id: str = Field(alias="instrumentID") 33 | image_url: str 34 | instrument_symbol: str 35 | instrument_name: str 36 | encoded_name: str 37 | model_config = ConfigDict(alias_generator=camelcase) 38 | 39 | 40 | class Brokerage(BaseModel): 41 | brokerage_fee: Optional[float] = None 42 | fixed_fee: Optional[float] = None 43 | variable_fee_percentage: Optional[float] = None 44 | variable_limit: Optional[int] = None 45 | model_config = ConfigDict(alias_generator=camelcase) 46 | 47 | 48 | class CancelOrderRequest(BaseModel): 49 | order_id: str 50 | 51 | 52 | class OrdersClient(BaseClient): 53 | """This client is in charge of dealing with your pending orders. 54 | 55 | These are the orders limit/stop etc.. that have not been traded yet. 56 | """ 57 | 58 | async def list(self) -> List[Order]: 59 | """Lists all your pending orders. 60 | 61 | Returns: 62 | List[Order]: The list of pending orders. 63 | """ 64 | data = await self._client.get(self._client.exchange.orders) 65 | return [Order(**d) for d in data] 66 | 67 | async def cancel(self, order: Union[Order, CancelOrderRequest]) -> bool: 68 | """Cancels a pending order. 69 | 70 | Args: 71 | order (Union[Order, CancelOrderRequest])): an existing order or its ID. 72 | 73 | Returns: 74 | bool: True if the deletion was succesful. 75 | """ 76 | await self._client.delete( 77 | self._client.exchange.cancel_order.format(orderId=order.order_id) 78 | ) 79 | return True 80 | 81 | async def brokerage(self, order_amount: float) -> Brokerage: 82 | """Retrieve the brokerage for an order. 83 | 84 | Args: 85 | order_amount (float): the per unit purchase price 86 | Returns: 87 | Brokerage: the brokerage information 88 | """ 89 | 90 | data = await self._client.get( 91 | self._client.exchange.brokerage.format(orderAmount=order_amount) 92 | ) 93 | return Brokerage(**data) 94 | -------------------------------------------------------------------------------- /stake/product.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from datetime import datetime 3 | from typing import Any, List, Optional 4 | 5 | from pydantic import BaseModel, ConfigDict 6 | from pydantic.fields import Field 7 | 8 | from stake.common import BaseClient, camelcase 9 | 10 | __all__ = ["ProductSearchByName"] 11 | 12 | 13 | class ProductSearchByName(BaseModel): 14 | """Request used to search for Products by their name or description.""" 15 | 16 | keyword: str 17 | 18 | 19 | class Instrument(BaseModel): 20 | encoded_name: Optional[str] = None 21 | image_url: Optional[str] = None 22 | instrument_id: str 23 | name: str 24 | symbol: str 25 | model_config = ConfigDict(alias_generator=camelcase) 26 | 27 | 28 | class Product(BaseModel): 29 | id: uuid.UUID 30 | instrument_type_id: Optional[str] = Field(None, alias="instrumentTypeID") 31 | symbol: str 32 | description: str 33 | category: Optional[str] = None 34 | currency_id: Optional[str] = Field(None, alias="currencyID") 35 | url_image: str 36 | sector: Optional[str] = None 37 | parent_id: Optional[str] = Field(None, alias="parentID") 38 | name: str 39 | daily_return: float 40 | daily_return_percentage: float 41 | last_traded: float 42 | monthly_return: float 43 | yearly_return_percentage: Optional[float] = None 44 | yearly_return_value: Optional[float] = None 45 | popularity: int 46 | watched: int 47 | news: int 48 | bought: int 49 | viewed: int 50 | product_type: str 51 | trade_status: Optional[int] = None 52 | encoded_name: str 53 | period: str 54 | inception_date: Optional[datetime] = None 55 | instrument_tags: List[Any] 56 | child_instruments: List[Instrument] 57 | model_config = ConfigDict(alias_generator=camelcase) 58 | 59 | 60 | class ProductsClient(BaseClient): 61 | async def get(self, symbol: str) -> Optional[Product]: 62 | """Given a symbol it will return the matching product. 63 | 64 | Examples: 65 | tesla_product = self.get("TSLA") 66 | """ 67 | data = await self._client.get( 68 | self._client.exchange.symbol.format(symbol=symbol) 69 | ) 70 | 71 | return Product(**data["products"][0]) if data["products"] else None 72 | 73 | async def search(self, request: ProductSearchByName) -> List[Instrument]: 74 | products = await self._client.get( 75 | self._client.exchange.products_suggestions.format(keyword=request.keyword) 76 | ) 77 | return [Instrument(**product) for product in products["instruments"]] 78 | 79 | async def product_from_instrument( 80 | self, instrument: Instrument 81 | ) -> Optional[Product]: 82 | return await self.get(instrument.symbol) 83 | -------------------------------------------------------------------------------- /stake/ratings.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import List, Optional 3 | 4 | import pydantic 5 | 6 | from stake.common import BaseClient 7 | 8 | __all__ = ["RatingsRequest"] 9 | 10 | 11 | class RatingsRequest(pydantic.BaseModel): 12 | """Request for retrieving the ratings for the given symbols.""" 13 | 14 | symbols: List[str] 15 | limit: Optional[int] = 50 16 | 17 | 18 | class Rating(pydantic.BaseModel): 19 | id: Optional[str] = None 20 | symbol: Optional[str] = pydantic.Field(alias="ticker") 21 | exchange: Optional[str] = None 22 | name: Optional[str] = None 23 | analyst: Optional[str] = None 24 | currency: Optional[str] = None 25 | url: Optional[str] = None 26 | importance: Optional[int] = None 27 | notes: Optional[str] = None 28 | updated: Optional[datetime] = None 29 | action_pt: Optional[str] = None 30 | action_company: Optional[str] = None 31 | rating_current: Optional[str] = None 32 | pt_current: Optional[float] = None 33 | rating_prior: Optional[str] = None 34 | pt_prior: Optional[float] = None 35 | url_calendar: Optional[str] = None 36 | url_news: Optional[str] = None 37 | analyst_name: Optional[str] = None 38 | 39 | @pydantic.field_validator("pt_prior", "rating_prior", mode="before") 40 | @classmethod 41 | def pt_prior_blank_string(cls, value, *args) -> Optional[str]: 42 | return None if value == "" else value 43 | 44 | 45 | class RatingsClient(BaseClient): 46 | """This client is in charge listing the experts' ratings for symbols.""" 47 | 48 | async def list(self, request: RatingsRequest) -> List[Rating]: 49 | """Lists all the ratings for the symbols specified in the request. 50 | 51 | Returns: 52 | List[Rating]: The list of ratings. 53 | """ 54 | data = await self._client.get( 55 | self._client.exchange.ratings.format( 56 | symbols=",".join(request.symbols), 57 | limit=request.limit, 58 | ) 59 | ) 60 | 61 | if data == {"message": "No data returned"}: 62 | return [] 63 | print(data["ratings"]) 64 | return [Rating(**d) for d in data["ratings"]] 65 | -------------------------------------------------------------------------------- /stake/trade.py: -------------------------------------------------------------------------------- 1 | import re 2 | from datetime import datetime 3 | from enum import Enum 4 | from typing import Optional, Union 5 | 6 | from pydantic import BaseModel, ConfigDict, Field, field_validator 7 | 8 | from stake.common import BaseClient, camelcase 9 | 10 | failed_transaction_regex = re.compile(r"^[0-9]{4}") 11 | 12 | __all__ = [ 13 | "LimitBuyRequest", 14 | "LimitSellRequest", 15 | "MarketBuyRequest", 16 | "MarketSellRequest", 17 | "StopBuyRequest", 18 | "StopSellRequest", 19 | "TradeType", 20 | ] 21 | 22 | 23 | class TradeType(str, Enum): 24 | """The type of trade the user is requesting.""" 25 | 26 | MARKET: str = "market" 27 | LIMIT: str = "limit" 28 | STOP: str = "stop" 29 | 30 | 31 | class MarketBuyRequest(BaseModel): 32 | """This is the request that needs to be passed to the trade client in order 33 | to buy some equity.""" 34 | 35 | # AAPL, MSFT, TSLA etc... 36 | symbol: str 37 | amount_cash: float 38 | comments: Optional[str] = None 39 | 40 | item_type: str = "instrument" 41 | order_type: TradeType = TradeType.MARKET 42 | model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True) 43 | 44 | 45 | class LimitBuyRequest(BaseModel): 46 | symbol: str 47 | limit_price: float 48 | quantity: int 49 | comments: Optional[str] = None 50 | 51 | item_type: str = "instrument" 52 | order_type: TradeType = TradeType.LIMIT 53 | model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True) 54 | 55 | 56 | class StopBuyRequest(BaseModel): 57 | symbol: str 58 | amount_cash: float 59 | price: float # must be higher than the current one 60 | comments: Optional[str] = None 61 | 62 | item_type: str = "instrument" 63 | order_type: TradeType = TradeType.STOP 64 | model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True) 65 | 66 | @field_validator("amount_cash") 67 | @classmethod 68 | def at_least_10(cls, v: float) -> float: # noqa 69 | 70 | if v < 10.0: 71 | raise ValueError("'amount_cash' must be at least '10$'.") 72 | 73 | return v 74 | 75 | 76 | class LimitSellRequest(BaseModel): 77 | symbol: str 78 | limit_price: float 79 | quantity: int 80 | comments: Optional[str] = None 81 | 82 | item_type: str = "instrument" 83 | order_type: TradeType = TradeType.LIMIT 84 | model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True) 85 | 86 | 87 | class StopSellRequest(BaseModel): 88 | symbol: str 89 | quantity: float 90 | stop_price: float 91 | comments: Optional[str] = None 92 | 93 | item_type: str = "instrument" 94 | order_type: TradeType = TradeType.STOP 95 | model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True) 96 | 97 | 98 | class MarketSellRequest(BaseModel): 99 | """Sell at marked price.""" 100 | 101 | symbol: str 102 | quantity: float 103 | comments: Optional[str] = None 104 | 105 | item_type: str = "instrument" 106 | order_type: TradeType = TradeType.MARKET 107 | model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True) 108 | 109 | 110 | class TradeResponse(BaseModel): 111 | """The response from a request to buy/sell.""" 112 | 113 | amount_cash: Optional[float] = None 114 | category: str 115 | commission: Optional[float] = None 116 | description: Optional[str] = None 117 | dw_order_id: str 118 | effective_price: Optional[float] = None 119 | encoded_name: str 120 | id: str 121 | image_url: str = Field(alias="imageURL") 122 | inserted_date: datetime 123 | item_id: str 124 | limit_price: Optional[float] = None 125 | name: str 126 | order_reject_reason: Optional[str] = None 127 | quantity: Optional[float] = None 128 | side: str 129 | status: Optional[int] = None 130 | stop_price: Optional[float] = None 131 | symbol: str 132 | updated_date: datetime 133 | model_config = ConfigDict(alias_generator=camelcase) 134 | 135 | 136 | class TradesClient(BaseClient): 137 | """This client is used to buy/sell equities.""" 138 | 139 | async def _trade( 140 | self, 141 | url: str, 142 | request: Union[ 143 | MarketBuyRequest, 144 | LimitBuyRequest, 145 | StopBuyRequest, 146 | LimitSellRequest, 147 | StopSellRequest, 148 | MarketSellRequest, 149 | ], 150 | check_success: bool = True, 151 | ) -> TradeResponse: 152 | """A generic function used to submit a trade, either buy or sell. 153 | 154 | Args: 155 | url: the url for buy/sell 156 | request: 157 | check_success: if true, an extra check will be performed to see if 158 | the transaction really occurred. This should not be needed, since it should 159 | be possible to inspect the TradeResponse for errors, but it does not look 160 | like they are recorded anywhere. 161 | 162 | Returns: 163 | the TradeResponse 164 | 165 | Raises: 166 | RuntimeError 167 | """ 168 | request_dict = request.model_dump(by_alias=True) 169 | product = await self._client.products.get(request_dict.pop("symbol")) 170 | assert product 171 | 172 | request_dict["orderType"] = request_dict["orderType"].value 173 | request_dict["userId"] = str(self._client.user.id) 174 | request_dict["itemId"] = str(product.id) 175 | data = await self._client.post(url, request_dict) 176 | trade = TradeResponse(**data[0]) 177 | 178 | if check_success: 179 | await self._verify_successful_trade(trade) 180 | 181 | return trade 182 | 183 | async def _verify_successful_trade(self, trade: TradeResponse) -> None: 184 | """We check the status of the trade by trying to find the matching 185 | transaction and inspecting its properties. This should not be needed 186 | but there is nothing i can see in the trading response that would help 187 | figuring out if the trade request was successful or not. 188 | 189 | Args: 190 | trade: the responded trade 191 | 192 | Raises: 193 | RuntimeError if the trade was not successful. 194 | """ 195 | 196 | transactions = await self._client.get(self._client.exchange.transactions) 197 | 198 | if not transactions: 199 | raise RuntimeError( 200 | "The trade did not succeed (Reason: no transaction found)." 201 | ) 202 | 203 | # wait for the transaction to be available 204 | for transaction in transactions["transactions"]: 205 | if transaction["orderId"] == trade.dw_order_id: 206 | if re.search(failed_transaction_regex, transaction["updatedReason"]): 207 | raise RuntimeError( 208 | "The trade did not succeed " 209 | f"(Reason: {transaction['updatedReason']}" 210 | ) 211 | else: 212 | return 213 | 214 | raise RuntimeError("Could not find a matching transaction.") 215 | 216 | async def buy( 217 | self, request: Union[MarketBuyRequest, LimitBuyRequest, StopBuyRequest] 218 | ) -> TradeResponse: 219 | """Creates an order to buy equities. 220 | 221 | Args: 222 | request: the buy request 223 | 224 | Returns: 225 | the TradeResponse object 226 | """ 227 | return await self._trade(self._client.exchange.quick_buy, request) 228 | 229 | async def sell(self, request: MarketSellRequest) -> TradeResponse: 230 | """Creates an order to sell equities. 231 | 232 | Args: 233 | request: the sell request 234 | 235 | Returns: 236 | the TradeResponse object 237 | """ 238 | return await self._trade(self._client.exchange.sell_orders, request) 239 | -------------------------------------------------------------------------------- /stake/transaction.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import json 3 | from datetime import datetime, timedelta 4 | from enum import Enum 5 | from typing import Dict, List, Optional 6 | 7 | from pydantic import BaseModel, ConfigDict, Field 8 | from pydantic.types import UUID, UUID4 9 | 10 | from stake.common import BaseClient, camelcase 11 | 12 | __all__ = ["TransactionRecordRequest"] 13 | 14 | 15 | class TransactionRecordEnumDirection(str, Enum): 16 | prev: str = "prev" 17 | next_: str = "next" 18 | 19 | 20 | class TransactionRecordRequest(BaseModel): 21 | to: datetime = Field(default_factory=datetime.utcnow) 22 | from_: datetime = Field( 23 | default_factory=lambda *_: datetime.utcnow() - timedelta(days=365), alias="from" 24 | ) 25 | limit: int = 1000 26 | offset: Optional[datetime] = None 27 | direction: TransactionRecordEnumDirection = TransactionRecordEnumDirection.prev 28 | 29 | 30 | class Instrument(BaseModel): 31 | id: UUID4 32 | symbol: str 33 | name: str 34 | 35 | 36 | class Transaction(BaseModel): 37 | account_amount: float 38 | account_balance: float 39 | account_type: str 40 | comment: str 41 | dividend_tax: Optional[dict] = None 42 | dividend: Optional[dict] = None 43 | dnb: bool 44 | fee_base: int 45 | fee_exchange: int 46 | fee_sec: float 47 | fee_taf: float 48 | fee_xtra_shares: int 49 | fill_px: float 50 | fill_qty: float 51 | fin_tran_id: str = Field(alias="finTranID") 52 | fin_tran_type_id: str = Field(alias="finTranTypeID") 53 | instrument: Optional[Instrument] = None 54 | merger_acquisition: Optional[Dict] = None 55 | order_id: Optional[str] = Field(None, alias="orderID") 56 | order_no: Optional[str] = None 57 | position_delta: Optional[float] = None 58 | send_commission_to_inteliclear: bool 59 | symbol: Optional[str] = None 60 | system_amount: int 61 | tran_amount: float 62 | tran_source: str 63 | tran_when: datetime 64 | updated_reason: Optional[str] = None 65 | wlp_amount: int 66 | wlp_fin_tran_type_id: Optional[UUID] = Field(None, alias="wlpFinTranTypeID") 67 | model_config = ConfigDict(alias_generator=camelcase) 68 | 69 | 70 | class TransactionHistoryType(str, enum.Enum): 71 | BUY = "Buy" 72 | CORPORATE_ACTION = "Corporate Action" 73 | DIVIDEND = "Dividend" 74 | DIVIDEND_TAX = "Dividend Tax" 75 | FUNDING = "Funding" 76 | SELL = "Sell" 77 | 78 | 79 | class TransactionsClient(BaseClient): 80 | async def list(self, request: TransactionRecordRequest) -> List[Transaction]: 81 | """Returns the transactions executed by the user. 82 | 83 | Args: 84 | request (TransactionRecordRequest): specify the from+/to datetimes 85 | for the transaction collection. 86 | 87 | Returns: 88 | List[Transaction]: the transactions executed in the time frame. 89 | """ 90 | payload = json.loads(request.model_dump_json(by_alias=True)) 91 | 92 | data = await self._client.post( 93 | self._client.exchange.account_transactions, payload=payload 94 | ) 95 | transactions = [] 96 | for d in data: 97 | if d.get("instrument"): 98 | d["symbol"] = d.get("instrument")["symbol"] 99 | transactions.append(Transaction(**d)) 100 | return transactions 101 | -------------------------------------------------------------------------------- /stake/user.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel, ConfigDict 4 | from pydantic.fields import Field 5 | 6 | from stake.common import camelcase 7 | 8 | 9 | class User(BaseModel): 10 | id: str = Field(alias="userId") 11 | first_name: str 12 | last_name: str 13 | email_address: str 14 | mac_status: str 15 | account_type: str 16 | region_identifier: str 17 | dw_account_number: Optional[str] = Field(None, alias="dw_AccountNumber") 18 | can_trade_on_unsettled_funds: Optional[bool] = None 19 | username: Optional[str] = None 20 | model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True) 21 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stabacco/stake-python/6efe9b090748a832352e75ce22a44139bce71101/tests/__init__.py -------------------------------------------------------------------------------- /tests/cassettes/test_equity/test_list_equities[exchange1].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "leonardzachary", "emailAddress": "ellen70@bates-williams.com", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "R6-1813041A", "macAccountNumber": "K4-3517949y", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "middleName": null, "lastName": "Alexander", "phoneNumber": 21 | "9011530005", "signUpPhase": 0, "ackSignedWhen": "2022-01-22", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "A1-2107594j", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/asx/instrument/equityPositions 46 | response: 47 | body: 48 | string: 49 | '{"pageNum": 0, "hasNext": false, "equityPositions": [{"instrumentId": 50 | "FUEL.XAU", "symbol": "FUEL", "name": "BETA GLOBAL ENERGY ETF UNITS", "openQty": 51 | "100", "availableForTradingQty": "170", "averagePrice": "5.9400", "marketValue": 52 | "953.7000", "mktPrice": "5.6100", "priorClose": "5.5900", "unrealizedDayPL": 53 | "3.4000", "unrealizedDayPLPercent": 1.0, "unrealizedPL": 1.0, "unrealizedPLPercent": 54 | "-5.56", "recentAnnouncement": false, "sensitive": false}, {"instrumentId": 55 | "WDS.XAU", "symbol": "WDS", "name": "WOODSIDE ENERGY FPO", "openQty": "100", 56 | "availableForTradingQty": "3", "averagePrice": "0", "marketValue": "93.4200", 57 | "mktPrice": "31.1400", "priorClose": "32.5700", "unrealizedDayPL": "-4.2900", 58 | "unrealizedDayPLPercent": 1.0, "unrealizedPL": 1.0, "unrealizedPLPercent": 59 | "0", "recentAnnouncement": true, "sensitive": true}, {"instrumentId": "WTC.XAU", 60 | "symbol": "WTC", "name": "WISETECH GLOBAL LTD FPO", "openQty": "100", "availableForTradingQty": 61 | "10", "averagePrice": "51.5000", "marketValue": "491.8000", "mktPrice": "49.1800", 62 | "priorClose": "47.4900", "unrealizedDayPL": "16.9000", "unrealizedDayPLPercent": 63 | 1.0, "unrealizedPL": 1.0, "unrealizedPLPercent": "-4.50", "recentAnnouncement": 64 | false, "sensitive": false}, {"instrumentId": "ALU.XAU", "symbol": "ALU", "name": 65 | "ALTIUM LIMITED FPO", "openQty": "100", "availableForTradingQty": "20", "averagePrice": 66 | "35.0000", "marketValue": "615.0000", "mktPrice": "30.7500", "priorClose": 67 | "29.6800", "unrealizedDayPL": "21.4000", "unrealizedDayPLPercent": 1.0, "unrealizedPL": 68 | 1.0, "unrealizedPLPercent": "-12.14", "recentAnnouncement": false, "sensitive": 69 | false}, {"instrumentId": "BHP.XAU", "symbol": "BHP", "name": "BHP GROUP LIMITED 70 | FPO", "openQty": "100", "availableForTradingQty": "20", "averagePrice": "50.8000", 71 | "marketValue": "735.8000", "mktPrice": "36.7900", "priorClose": "37.1100", 72 | "unrealizedDayPL": "-6.4000", "unrealizedDayPLPercent": 1.0, "unrealizedPL": 73 | 1.0, "unrealizedPLPercent": "-27.58", "recentAnnouncement": false, "sensitive": 74 | false}, {"instrumentId": "FOOD.XAU", "symbol": "FOOD", "name": "BETA GLOBAL 75 | AGRI ETF UNITS", "openQty": "100", "availableForTradingQty": "100", "averagePrice": 76 | "8.4700", "marketValue": "704.0000", "mktPrice": "7.0400", "priorClose": "7.0000", 77 | "unrealizedDayPL": "4.0000", "unrealizedDayPLPercent": 1.0, "unrealizedPL": 78 | 1.0, "unrealizedPLPercent": "-16.88", "recentAnnouncement": false, "sensitive": 79 | false}]}' 80 | headers: {} 81 | status: 82 | code: 200 83 | message: OK 84 | url: https://global-prd-api.hellostake.com/api/asx/instrument/equityPositions 85 | version: 1 86 | -------------------------------------------------------------------------------- /tests/cassettes/test_funding/test_cash_available[exchange0].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/users/accounts/cashAvailableForWithdrawal 46 | response: 47 | body: 48 | string: 49 | '{"cashAvailableForWithdrawal": 1000, "cashAvailableForTrade": 800, 50 | "cashBalance": 1000.0, "reservedCash": 0.0, "liquidCash": 1000.0, "dwCashAvailableForWithdrawal": 51 | 1000, "pendingOrdersAmount": 0.0, "pendingWithdrawals": 0.0, "cardHoldAmount": 52 | 0.0, "pendingPoliAmount": 0.0, "cashSettlement": [{"utcTime": "2022-07-27T13:30:00.001Z", 53 | "cash": 0.0}, {"utcTime": "2022-07-28T13:30:00.001Z", "cash": 0.0}, null]}' 54 | headers: {} 55 | status: 56 | code: 200 57 | message: OK 58 | url: https://global-prd-api.hellostake.com/api/users/accounts/cashAvailableForWithdrawal 59 | version: 1 60 | -------------------------------------------------------------------------------- /tests/cassettes/test_funding/test_cash_available[exchange1].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/asx/cash 46 | response: 47 | body: 48 | string: 49 | '{"settledCash": 10000.0, "tradeSettlement": 0.0, "buyingPower": 1000.0, 50 | "pendingBuys": 0.0, "settlementHold": 0.0, "pendingWithdrawals": 0.0, "cashAvailableForWithdrawal": 51 | 1000, "cashAvailableForTransfer": 1000, "cashAvailableForWithdrawalHold": 52 | 0.0, "clearingCash": 0.0}' 53 | headers: {} 54 | status: 55 | code: 200 56 | message: OK 57 | url: https://global-prd-api.hellostake.com/api/asx/cash 58 | version: 1 59 | -------------------------------------------------------------------------------- /tests/cassettes/test_funding/test_funds_in_flight[exchange0].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/fund/details 46 | response: 47 | body: 48 | string: '{"fundsInFlight": []}' 49 | headers: {} 50 | status: 51 | code: 200 52 | message: OK 53 | url: https://global-prd-api.hellostake.com/api/fund/details 54 | version: 1 55 | -------------------------------------------------------------------------------- /tests/cassettes/test_funding/test_funds_in_flight[exchange1].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/asx/transactions?status=PENDING&status=AWAITING_APPROVAL&size=100&page=0 46 | response: 47 | body: 48 | string: '{"items": [], "hasNext": false, "page": 0, "totalItems": 0}' 49 | headers: {} 50 | status: 51 | code: 200 52 | message: OK 53 | url: https://global-prd-api.hellostake.com/api/asx/transactions?status=PENDING&status=AWAITING_APPROVAL&size=100&page=0 54 | version: 1 55 | -------------------------------------------------------------------------------- /tests/cassettes/test_funding/test_list_fundings[exchange0-request_0].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: POST 45 | uri: https://global-prd-api.hellostake.com/api/users/accounts/transactionHistory 46 | response: 47 | body: 48 | string: '[{"transactionType": 49 | "Funding", "finTranTypeID": "JNLC", "timestamp": "2021-09-30T15:45:50.425Z", 50 | "tranAmount": 1000, "feeAmount": 0.0, "orderID": "HHI.1cf93550-8eb4-4c32-a229-826cf8c1be59", 51 | "symbol": null, "side": "CREDIT", "text": "Deposit", "comment": "f1-9011914a", 52 | "amountPerShare": 0.0, "taxRate": 0.0, "reference": "r9-6577610c", "referenceType": 53 | "Funding"},{"transactionType": "Funding", "finTranTypeID": "JNLC", "timestamp": 54 | "2021-08-10T15:45:02.683Z", "tranAmount": 1000, "feeAmount": 0.0, "orderID": 55 | "HHI.1cf93550-8eb4-4c32-a229-826cf8c1be59", "symbol": null, "side": "CREDIT", 56 | "text": "Deposit", "comment": "f1-9011914a", "amountPerShare": 0.0, "taxRate": 57 | 0.0, "reference": "r9-6577610c", "referenceType": "Funding"}]' 58 | headers: {} 59 | status: 60 | code: 200 61 | message: OK 62 | url: https://global-prd-api.hellostake.com/api/users/accounts/transactionHistory 63 | - request: 64 | body: null 65 | headers: 66 | Accept: 67 | - application/json 68 | Content-Type: 69 | - application/json 70 | method: GET 71 | uri: https://global-prd-api.hellostake.com/api/users/accounts/transactionDetails?reference=r9-6577610c&referenceType=Funding 72 | response: 73 | body: 74 | string: 75 | '{"insertDate": 1628229668979, "channel": "Poli", "amountTo": 2196.01, 76 | "amountFrom": 3000.0, "status": "RECONCILED", "speed": "Regular", "fxFee": 77 | 20.39, "expressFee": 0.0, "channelFee": null, "totalFee": 20.39, "feesDisplayCurrency": 78 | "USD", "spotRate": 0.7388, "reference": "r9-6577610c", "w8fee": 0.0, "currencyFrom": 79 | "AUD", "currencyTo": "USD", "iof": null, "vet": null, "bsb": null, "accountNumber": 80 | null, "transactionDetails": null}' 81 | headers: {} 82 | status: 83 | code: 200 84 | message: OK 85 | url: https://global-prd-api.hellostake.com/api/users/accounts/transactionDetails?reference=r9-6577610c&referenceType=Funding 86 | - request: 87 | body: null 88 | headers: 89 | Accept: 90 | - application/json 91 | Content-Type: 92 | - application/json 93 | method: GET 94 | uri: https://global-prd-api.hellostake.com/api/users/accounts/transactionDetails?reference=r9-6577610c&referenceType=Funding 95 | response: 96 | body: 97 | string: 98 | '{"insertDate": 1632879790963, "channel": "Poli", "amountTo": 3796.79, 99 | "amountFrom": 5300.0, "status": "RECONCILED", "speed": "Regular", "fxFee": 100 | 35.64, "expressFee": 0.0, "channelFee": null, "totalFee": 35.64, "feesDisplayCurrency": 101 | "USD", "spotRate": 0.7231, "reference": "r9-6577610c", "w8fee": 0.0, "currencyFrom": 102 | "AUD", "currencyTo": "USD", "iof": null, "vet": null, "bsb": null, "accountNumber": 103 | null, "transactionDetails": null}' 104 | headers: {} 105 | status: 106 | code: 200 107 | message: OK 108 | url: https://global-prd-api.hellostake.com/api/users/accounts/transactionDetails?reference=r9-6577610c&referenceType=Funding 109 | version: 1 110 | -------------------------------------------------------------------------------- /tests/cassettes/test_funding/test_list_fundings[exchange1-request_1].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/asx/transactions?status=RECONCILED&size=3&page=0 46 | response: 47 | body: 48 | string: 49 | '{"items": [{"id": "6fd4187a-559b-4326-b9bb-6fb6312ece7b", "side": "CREDIT", 50 | "amount": 18.27, "currency": "AUD", "action": "DIVIDEND_DEPOSIT", "reference": 51 | "r9-6577610c", "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", "status": 52 | "RECONCILED", "approvedBy": null, "customerFee": 0.0, "insertedAt": "2022-07-18T07:32:28.836634", 53 | "updatedAt": "2022-07-27T07:13:13.455605"}, {"id": "6fd4187a-559b-4326-b9bb-6fb6312ece7b", 54 | "side": "DEBIT", "amount": 1148.28, "currency": "AUD", "action": "SETTLEMENT", 55 | "reference": "r9-6577610c", "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 56 | "status": "RECONCILED", "approvedBy": null, "customerFee": 0.0, "insertedAt": 57 | "2022-07-26T20:02:16.50554", "updatedAt": "2022-07-26T20:02:17.488227"}, {"id": 58 | "6fd4187a-559b-4326-b9bb-6fb6312ece7b", "side": "CREDIT", "amount": 26.58, 59 | "currency": "AUD", "action": "DIVIDEND_DEPOSIT", "reference": "r9-6577610c", 60 | "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", "status": "RECONCILED", 61 | "approvedBy": null, "customerFee": 0.0, "insertedAt": "2022-01-18T21:24:30.449019", 62 | "updatedAt": "2022-07-14T02:59:09.799755"}], "hasNext": true, "page": 0, "totalItems": 63 | 37}' 64 | headers: {} 65 | status: 66 | code: 200 67 | message: OK 68 | url: https://global-prd-api.hellostake.com/api/asx/transactions?status=RECONCILED&size=3&page=0 69 | version: 1 70 | -------------------------------------------------------------------------------- /tests/cassettes/test_fx/test_fx_conversion.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "rita19", "emailAddress": "torresbenjamin@gmail.com", "dw_AccountId": 17 | "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": "w4-1267174s", 18 | "macAccountNumber": "H1-7641957H", "status": null, "macStatus": "BASIC_USER", 19 | "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "lastName": "Alexander", "phoneNumber": "9011530005", 21 | "signUpPhase": 0, "ackSignedWhen": "2021-05-18", "createdDate": 1574303699770, 22 | "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "L7-2127933N", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "mfaenabled": false}' 31 | headers: {} 32 | status: 33 | code: 200 34 | message: OK 35 | url: https://global-prd-api.hellostake.com/api/user 36 | - request: 37 | body: null 38 | headers: 39 | Accept: 40 | - application/json 41 | Content-Type: 42 | - application/json 43 | method: POST 44 | uri: https://global-prd-api.hellostake.com/api/wallet/rate 45 | response: 46 | body: 47 | string: 48 | '{"fromCurrency": "USD", "toCurrency": "AUD", "fromAmount": 1000.0, 49 | "toAmount": 1368.5, "rate": 1.3685, "quote": "7f730dad-86b0-44d7-8810-ed49af26b702"}' 50 | headers: {} 51 | status: 52 | code: 200 53 | message: OK 54 | url: https://global-prd-api.hellostake.com/api/wallet/rate 55 | version: 1 56 | -------------------------------------------------------------------------------- /tests/cassettes/test_market/test_check_market_status[exchange0].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "leonardzachary", "emailAddress": "ellen70@bates-williams.com", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "R6-1813041A", "macAccountNumber": "K4-3517949y", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "middleName": null, "lastName": "Alexander", "phoneNumber": 21 | "9011530005", "signUpPhase": 0, "ackSignedWhen": "2022-01-22", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "A1-2107594j", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/utils/marketStatus 46 | response: 47 | body: 48 | string: 49 | '{"response": {"message": "Market is open Monday through Friday 9:30AM 50 | to 4:00PM EST", "unixtime": "1658228491.36769", "error": "Success", "status": 51 | {"change_at": "08:00:00", "next": "pre", "current": "close"}, "elapsedtime": 52 | "0", "date": "2022-07-19 07:01:31.3676874-04:00", "versionNumber": "2.56.0"}}' 53 | headers: {} 54 | status: 55 | code: 202 56 | message: Accepted 57 | url: https://global-prd-api.hellostake.com/api/utils/marketStatus 58 | version: 1 59 | -------------------------------------------------------------------------------- /tests/cassettes/test_market/test_check_market_status[exchange1].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "leonardzachary", "emailAddress": "ellen70@bates-williams.com", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "R6-1813041A", "macAccountNumber": "K4-3517949y", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "middleName": null, "lastName": "Alexander", "phoneNumber": 21 | "9011530005", "signUpPhase": 0, "ackSignedWhen": "2022-01-22", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "A1-2107594j", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://early-bird-promo.hellostake.com/marketStatus 46 | response: 47 | body: 48 | string: 49 | '{"lastTradingDate": "2022-07-19", "status": {"current": "close"}, "marketLimits": 50 | [[0, 0.005, 0.125], [0.005, 0.02, 0.075], [0.02, 0.05, 0.0625], [0.05, 0.1, 51 | 0.05], [0.1, 0.2, 0.045], [0.2, 0.5, 0.03], [0.5, 1, 0.0125], [1, 2, 0.0125], 52 | [2, 5, 0.01], [5, 15, 0.0075], [15, 39, 0.005], [30, 50, 0.0025], [50, 100, 53 | 0.0025], [100, 250, 0.0025], [250, 500, 0.0025], [500, 9999, 0.0025]], "passiveLimits": 54 | [[0, 0.005, 1], [0.005, 0.02, 0.6], [0.02, 0.05, 0.5], [0.05, 0.1, 0.4], [0.1, 55 | 0.2, 0.4], [0.2, 0.5, 0.4], [0.5, 1, 0.3], [1, 2, 0.3], [2, 5, 0.3], [5, 15, 56 | 0.3], [15, 39, 0.2], [30, 50, 0.2], [50, 100, 0.2], [100, 250, 0.2], [250, 57 | 500, 0.2], [500, 9999, 0.2]]}' 58 | headers: {} 59 | status: 60 | code: 200 61 | message: OK 62 | url: https://early-bird-promo.hellostake.com/marketStatus 63 | version: 1 64 | -------------------------------------------------------------------------------- /tests/cassettes/test_order/test_brokerage[exchange0].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/orders/brokerage?orderAmount=1.0 46 | response: 47 | body: 48 | string: 49 | '{"brokerageFee": 3, "fixedFee": 3, "variableFeePercentage": 0.01, 50 | "variableLimit": 30000}' 51 | headers: {} 52 | status: 53 | code: 200 54 | message: OK 55 | url: https://global-prd-api.hellostake.com/api/orders/brokerage?orderAmount=1.0 56 | version: 1 57 | -------------------------------------------------------------------------------- /tests/cassettes/test_order/test_brokerage[exchange1].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "leonardzachary", "emailAddress": "ellen70@bates-williams.com", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "R6-1813041A", "macAccountNumber": "K4-3517949y", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "middleName": null, "lastName": "Alexander", "phoneNumber": 21 | "9011530005", "signUpPhase": 0, "ackSignedWhen": "2022-01-22", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "A1-2107594j", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/asx/orders/brokerage?orderAmount=1.0 46 | response: 47 | body: 48 | string: 49 | '{"brokerageFee": 3.00, "brokerageDiscount": 0.00, "fixedFee": 3.00, 50 | "variableFeePercentage": 0.01, "variableLimit": 30000}' 51 | headers: {} 52 | status: 53 | code: 200 54 | message: OK 55 | url: https://global-prd-api.hellostake.com/api/asx/orders/brokerage?orderAmount=1.0 56 | version: 1 57 | -------------------------------------------------------------------------------- /tests/cassettes/test_order/test_cancel_order[exchange0].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/users/accounts/v2/orders 46 | response: 47 | body: 48 | string: 49 | '[{"orderNo": "x5-4334954E", "orderID": "HHI.1cf93550-8eb4-4c32-a229-826cf8c1be59", 50 | "orderCashAmt": 100.0, "symbol": "AAPL", "price": 0.0, "stopPrice": 0.0, "side": 51 | "B", "orderType": 1, "cumQty": "0.0", "limitPrice": 0.0, "commission": 0.0, 52 | "createdWhen": "2021-07-04 21:46:25", "orderStatus": 0, "orderQty": 0.6541078, 53 | "description": "Market Order", "instrumentID": "a67422af-8504-43df-9e63-7361eb0bd99e", 54 | "imageUrl": "https://drivewealth.imgix.net/symbols/aapl.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 55 | "instrumentSymbol": "AAPL", "instrumentName": "Apple, Inc.", "encodedName": 56 | "apple-inc-aapl", "expires": "2022-07-27"}]' 57 | headers: {} 58 | status: 59 | code: 200 60 | message: OK 61 | url: https://global-prd-api.hellostake.com/api/users/accounts/v2/orders 62 | - request: 63 | body: null 64 | headers: 65 | Accept: 66 | - application/json 67 | Content-Type: 68 | - application/json 69 | method: DELETE 70 | uri: https://global-prd-api.hellostake.com/api/orders/cancelOrder/HHI.1cf93550-8eb4-4c32-a229-826cf8c1be59 71 | response: 72 | body: 73 | string: "" 74 | headers: {} 75 | status: 76 | code: 200 77 | message: OK 78 | url: https://global-prd-api.hellostake.com/api/orders/cancelOrder/HHI.1cf93550-8eb4-4c32-a229-826cf8c1be59 79 | - request: 80 | body: null 81 | headers: 82 | Accept: 83 | - application/json 84 | Content-Type: 85 | - application/json 86 | method: GET 87 | uri: https://global-prd-api.hellostake.com/api/users/accounts/v2/orders 88 | response: 89 | body: 90 | string: "[]" 91 | headers: {} 92 | status: 93 | code: 200 94 | message: OK 95 | url: https://global-prd-api.hellostake.com/api/users/accounts/v2/orders 96 | version: 1 97 | -------------------------------------------------------------------------------- /tests/cassettes/test_order/test_list_orders[exchange0].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "leonardzachary", "emailAddress": "ellen70@bates-williams.com", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "R6-1813041A", "macAccountNumber": "K4-3517949y", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "middleName": null, "lastName": "Alexander", "phoneNumber": 21 | "9011530005", "signUpPhase": 0, "ackSignedWhen": "2022-01-22", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "A1-2107594j", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/users/accounts/v2/orders 46 | response: 47 | body: 48 | string: 49 | '[{"orderNo": "L4-2572575L", "orderID": "HHI.1cf93550-8eb4-4c32-a229-826cf8c1be59", 50 | "orderCashAmt": 0.0, "symbol": "TQQQ", "price": 0.0, "stopPrice": 0.0, "side": 51 | "B", "orderType": 2, "cumQty": "0.0", "limitPrice": 26.7, "commission": 0.0, 52 | "createdWhen": "2020-05-16 04:29:32", "orderStatus": 0, "orderQty": 10.0, 53 | "description": "Limit Order: Buy at a limit of $26.70", "instrumentID": "63698af7-e71e-4424-9ce7-d34b70fbe260", 54 | "imageUrl": "https://drivewealth.imgix.net/symbols/tqqq.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 55 | "instrumentSymbol": "TQQQ", "instrumentName": "ProShares UltraPro QQQ ETF", 56 | "encodedName": "proshares-ultrapro-qqq-etf-tqqq", "expires": "2022-07-21"}]' 57 | headers: {} 58 | status: 59 | code: 200 60 | message: OK 61 | url: https://global-prd-api.hellostake.com/api/users/accounts/v2/orders 62 | version: 1 63 | -------------------------------------------------------------------------------- /tests/cassettes/test_order/test_list_orders[exchange1].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "leonardzachary", "emailAddress": "ellen70@bates-williams.com", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "R6-1813041A", "macAccountNumber": "K4-3517949y", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "middleName": null, "lastName": "Alexander", "phoneNumber": 21 | "9011530005", "signUpPhase": 0, "ackSignedWhen": "2022-01-22", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "A1-2107594j", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/asx/orders 46 | response: 47 | body: 48 | string: 49 | '[{"id": "20ce85f9-2d8d-49ae-8191-39f785c3d5d6", "broker": "FINCLEAR", 50 | "brokerOrderId": null, "brokerOrderVersionId": null, "brokerInstructionId": 51 | 4736823, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 52 | "instrumentId": null, "instrumentCode": "OOO.XAU", "side": "BUY", "limitPrice": 53 | 6.0, "validity": "GTC", "validityDate": null, "type": "LIMIT", "placedTimestamp": 54 | "2022-07-21T17:08:38.771843", "completedTimestamp": null, "expiresAt": "2022-08-22T00:00:00", 55 | "orderStatus": "STAKE_PENDING_CREATE", "orderCompletionType": null, "filledUnits": 56 | 0, "averagePrice": null, "unitsRemaining": 221, "estimatedBrokerage": 0.0, 57 | "estimatedExchangeFees": 0.0}]' 58 | headers: {} 59 | status: 60 | code: 200 61 | message: OK 62 | url: https://global-prd-api.hellostake.com/api/asx/orders 63 | version: 1 64 | -------------------------------------------------------------------------------- /tests/cassettes/test_product/test_find_products_by_name[exchange0].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "leonardzachary", "emailAddress": "ellen70@bates-williams.com", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "R6-1813041A", "macAccountNumber": "K4-3517949y", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "middleName": null, "lastName": "Alexander", "phoneNumber": 21 | "9011530005", "signUpPhase": 0, "ackSignedWhen": "2022-01-22", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "A1-2107594j", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/products/getProductSuggestions/techno 46 | response: 47 | body: 48 | string: 49 | '{"instruments": [{"name": "SKYWATER TECHNOLOGY INC", "symbol": "SKYT", 50 | "encodedName": "skywater-technology-inc-skyt", "instrumentId": "01fdcf5b-6c6d-4928-a875-b4f9212ace3d", 51 | "imageUrl": "https://drivewealth.imgix.net/symbols/dw_default_logo.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 52 | "exchange": null}, {"name": "WARRIOR TECHNOLOGIES ACQ-A", "symbol": "WARR", 53 | "encodedName": "warrior-technologies-acqa-warr", "instrumentId": "03cb7f33-27fb-4ae7-a3f3-ed23be742aa3", 54 | "imageUrl": "https://drivewealth.imgix.net/symbols/WARR.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 55 | "exchange": null}, {"name": "BlackRock Science & Technology Trust II", "symbol": 56 | "BSTZ", "encodedName": "blackrock-science-technology-trust-ii-bstz", "instrumentId": 57 | "04028ea9-6e66-4392-bfef-98a9a48cb9cc", "imageUrl": "https://drivewealth.imgix.net/symbols/bstz_1617916369.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 58 | "exchange": null}, {"name": "Axonics Modulation Technologies Inc", "symbol": 59 | "AXNX", "encodedName": "axonics-modulation-technologies-inc-axnx", "instrumentId": 60 | "05cb4946-46d7-4a49-adab-36d730beaee6", "imageUrl": "https://drivewealth.imgix.net/symbols/axnx.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 61 | "exchange": null}, {"name": "iRhythm Technologies, Inc.", "symbol": "IRTC", 62 | "encodedName": "irhythm-technologies-inc-irtc", "instrumentId": "05f00a67-b822-4023-871a-1d6215a0eef6", 63 | "imageUrl": "https://drivewealth.imgix.net/symbols/irtc.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 64 | "exchange": null}, {"name": "ASPEN TECHNOLOGY INC", "symbol": "AZPN", "encodedName": 65 | "aspen-technology-inc-azpn", "instrumentId": "08b7f76d-fa40-44b0-951e-b77d1a9206e3", 66 | "imageUrl": "https://drivewealth.imgix.net/symbols/azpn_1652797208.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 67 | "exchange": "NSQ"}, {"name": "FARO Technologies Inc.", "symbol": "FARO", "encodedName": 68 | "faro-technologies-inc-faro", "instrumentId": "08c65f62-394e-442a-86c1-0ad93b3efe21", 69 | "imageUrl": "https://drivewealth.imgix.net/symbols/faro.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 70 | "exchange": null}, {"name": "Shift Technologies, Inc.", "symbol": "SFT", "encodedName": 71 | "shift-technologies-inc-sft", "instrumentId": "0951b489-3ca6-41dd-a813-5a97d32043ea", 72 | "imageUrl": "https://drivewealth.imgix.net/symbols/sft_1610660958.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 73 | "exchange": null}, {"name": "Aquabounty Technologies Inc", "symbol": "AQB", 74 | "encodedName": "aquabounty-technologies-inc-aqb", "instrumentId": "0a9ad06a-cbc3-4ae5-86da-8942452908d0", 75 | "imageUrl": "https://drivewealth.imgix.net/symbols/aqb_1616442632.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 76 | "exchange": null}, {"name": "Micron Technology Inc.", "symbol": "MU", "encodedName": 77 | "micron-technology-inc-mu", "instrumentId": "0baafbff-dcc6-4645-8d27-4b4baf8a37ac", 78 | "imageUrl": "https://d3an3cesqmrf1x.cloudfront.net/images/symbols/mu.png", 79 | "exchange": null}], "instrumentTags": [{"tagID": "448f6003-9af8-4f25-8f51-2cf924255f09", 80 | "tagName": "Nanotechnology"}, {"tagID": "55832928-f3cb-4229-9d27-1baa5b25ec15", 81 | "tagName": "Wearable Technology"}, {"tagID": "794e2f64-f33c-4ec7-918a-7ec5c7c998cf", 82 | "tagName": "Technology"}, {"tagID": "e1cdd8d5-c9be-460b-ab56-934907c7d69d", 83 | "tagName": "Biotechnology"}]}' 84 | headers: {} 85 | status: 86 | code: 200 87 | message: OK 88 | url: https://global-prd-api.hellostake.com/api/products/getProductSuggestions/techno 89 | version: 1 90 | -------------------------------------------------------------------------------- /tests/cassettes/test_product/test_find_products_by_name[exchange1].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "leonardzachary", "emailAddress": "ellen70@bates-williams.com", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "R6-1813041A", "macAccountNumber": "K4-3517949y", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "middleName": null, "lastName": "Alexander", "phoneNumber": 21 | "9011530005", "signUpPhase": 0, "ackSignedWhen": "2022-01-22", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "A1-2107594j", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/asx/instrument/search?searchKey=techno 46 | response: 47 | body: 48 | string: 49 | '{"instruments": [{"instrumentId": "AIM.XAU", "symbol": "AIM", "name": 50 | "Ai-Media Technologies Limited", "type": "EQUITY", "recentAnnouncement": false, 51 | "sensitive": false}, {"instrumentId": "CPV.XAU", "symbol": "CPV", "name": 52 | "ClearVue Technologies Limited", "type": "EQUITY", "recentAnnouncement": false, 53 | "sensitive": false}, {"instrumentId": "LCT.XAU", "symbol": "LCT", "name": 54 | "Living Cell Technologies Limited", "type": "EQUITY", "recentAnnouncement": 55 | false, "sensitive": false}, {"instrumentId": "VR1.XAU", "symbol": "VR1", "name": 56 | "Vection Technologies Ltd", "type": "EQUITY", "recentAnnouncement": false, 57 | "sensitive": false}, {"instrumentId": "FFT.XAU", "symbol": "FFT", "name": 58 | "Future First Technologies Ltd", "type": "EQUITY", "recentAnnouncement": false, 59 | "sensitive": false}, {"instrumentId": "VHT.XAU", "symbol": "VHT", "name": 60 | "Volpara Health Technologies Limited", "type": "EQUITY", "recentAnnouncement": 61 | false, "sensitive": false}, {"instrumentId": "VTI.XAU", "symbol": "VTI", "name": 62 | "Visioneering Technologies Inc", "type": "EQUITY", "recentAnnouncement": true, 63 | "sensitive": false}, {"instrumentId": "EGY.XAU", "symbol": "EGY", "name": 64 | "Energy Technologies Limited", "type": "EQUITY", "recentAnnouncement": false, 65 | "sensitive": false}, {"instrumentId": "RTE.XAU", "symbol": "RTE", "name": 66 | "Retech Technology., Co Limited", "type": "EQUITY", "recentAnnouncement": 67 | false, "sensitive": false}, {"instrumentId": "AGI.XAU", "symbol": "AGI", "name": 68 | "Ainsworth Game Technology Limited", "type": "EQUITY", "recentAnnouncement": 69 | true, "sensitive": false}], "instrumentTags": [{"tagName": "Information Technology", 70 | "tagId": "45"}, {"tagName": "Pharmaceuticals, Biotechnology & Life Sciences", 71 | "tagId": "3520"}, {"tagName": "Technology Hardware & Equipment", "tagId": 72 | "4520"}, {"tagName": "Health Care Technology", "tagId": "351030"}, {"tagName": 73 | "Biotechnology", "tagId": "352010"}, {"tagName": "Technology Hardware, Storage 74 | & Peripherals", "tagId": "452020"}, {"tagName": "Health Care Technology", 75 | "tagId": "35103010"}, {"tagName": "Biotechnology", "tagId": "35201010"}, {"tagName": 76 | "Technology Hardware, Storage & Peripherals", "tagId": "45202030"}, {"tagName": 77 | "Technology Distributors", "tagId": "45203030"}]}' 78 | headers: {} 79 | status: 80 | code: 200 81 | message: OK 82 | url: https://global-prd-api.hellostake.com/api/asx/instrument/search?searchKey=techno 83 | version: 1 84 | -------------------------------------------------------------------------------- /tests/cassettes/test_product/test_get_product[exchange1-symbols1].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "leonardzachary", "emailAddress": "ellen70@bates-williams.com", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "R6-1813041A", "macAccountNumber": "K4-3517949y", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "middleName": null, "lastName": "Alexander", "phoneNumber": 21 | "9011530005", "signUpPhase": 0, "ackSignedWhen": "2022-01-22", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "A1-2107594j", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/asx/instrument/singleQuote/ANZ 46 | response: 47 | body: 48 | string: 49 | '{"marketStatus": "CLOSED", "lastTradedExchange": "ASX", "symbol": "ANZ", 50 | "lastTradedTimestamp": 1658383848, "lastTrade": "21.9300", "bid": "21.9200", 51 | "ask": "21.9300", "priorClose": "21.6400", "open": "22.3200", "high": "22.3400", 52 | "low": "21.2700", "pointsChange": "0.2900", "percentageChange": "1.34", "outOfMarketPrice": 53 | "21.9300", "outOfMarketQuantity": "3393713", "outOfMarketSurplus": "-69162"}' 54 | headers: {} 55 | status: 56 | code: 200 57 | message: OK 58 | url: https://global-prd-api.hellostake.com/api/asx/instrument/singleQuote/ANZ 59 | - request: 60 | body: null 61 | headers: 62 | Accept: 63 | - application/json 64 | Content-Type: 65 | - application/json 66 | method: GET 67 | uri: https://global-prd-api.hellostake.com/api/asx/instrument/singleQuote/WDS 68 | response: 69 | body: 70 | string: 71 | '{"marketStatus": "CLOSED", "lastTradedExchange": "ASX", "symbol": "WDS", 72 | "lastTradedTimestamp": 1658383848, "lastTrade": "31.1400", "bid": "30.9600", 73 | "ask": "30.9900", "priorClose": "32.5700", "open": "31.8400", "high": "32.1400", 74 | "low": "30.7000", "pointsChange": "-1.5800", "percentageChange": "-4.39", 75 | "outOfMarketPrice": "31.1400", "outOfMarketQuantity": "2070129", "outOfMarketSurplus": 76 | "-16665"}' 77 | headers: {} 78 | status: 79 | code: 200 80 | message: OK 81 | url: https://global-prd-api.hellostake.com/api/asx/instrument/singleQuote/WDS 82 | - request: 83 | body: null 84 | headers: 85 | Accept: 86 | - application/json 87 | Content-Type: 88 | - application/json 89 | method: GET 90 | uri: https://global-prd-api.hellostake.com/api/asx/instrument/singleQuote/COL 91 | response: 92 | body: 93 | string: 94 | '{"marketStatus": "CLOSED", "lastTradedExchange": "ASX", "symbol": "COL", 95 | "lastTradedTimestamp": 1658383850, "lastTrade": "18.8800", "bid": "18.8800", 96 | "ask": "18.8900", "priorClose": "18.6300", "open": "18.7900", "high": "18.9700", 97 | "low": "18.6400", "pointsChange": "0.2500", "percentageChange": "1.34", "outOfMarketPrice": 98 | "18.8800", "outOfMarketQuantity": "1076585", "outOfMarketSurplus": "42836"}' 99 | headers: {} 100 | status: 101 | code: 200 102 | message: OK 103 | url: https://global-prd-api.hellostake.com/api/asx/instrument/singleQuote/COL 104 | version: 1 105 | -------------------------------------------------------------------------------- /tests/cassettes/test_ratings/test_list_ratings.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "rita19", "emailAddress": "torresbenjamin@gmail.com", "dw_AccountId": 17 | "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": "w4-1267174s", 18 | "macAccountNumber": "H1-7641957H", "status": null, "macStatus": "BASIC_USER", 19 | "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "lastName": "Alexander", "phoneNumber": "9011530005", 21 | "signUpPhase": 0, "ackSignedWhen": "2021-05-18", "createdDate": 1574303699770, 22 | "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "L7-2127933N", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "mfaenabled": false}' 31 | headers: {} 32 | status: 33 | code: 200 34 | message: OK 35 | url: https://global-prd-api.hellostake.com/api/user 36 | - request: 37 | body: null 38 | headers: 39 | Accept: 40 | - application/json 41 | Content-Type: 42 | - application/json 43 | method: GET 44 | uri: https://global-prd-api.hellostake.com/api/data/calendar/ratings?tickers=AAPL,MSFT&pageSize=4 45 | response: 46 | body: 47 | string: 48 | '{"ratings": [{"id": "a154926a-4540-437c-aef4-c0de3671cfe6", "date": 49 | "2022-01-14", "time": "07:04:52", "ticker": "AAPL", "exchange": "NASDAQ", 50 | "name": "Apple", "analyst": "Loop Capital", "currency": "USD", "url": "https://www.benzinga.com/stock/AAPL/ratings", 51 | "importance": 0, "notes": "", "updated": 1642161976, "action_pt": "Raises", 52 | "action_company": "Maintains", "rating_current": "Buy", "pt_current": "210.0000", 53 | "rating_prior": "", "pt_prior": "165.0000", "url_calendar": "https://www.benzinga.com/stock/AAPL/ratings", 54 | "url_news": "https://www.benzinga.com/stock-articles/AAPL/analyst-ratings", 55 | "analyst_name": "Ananda Baruah"}, {"id": "a154926a-4540-437c-aef4-c0de3671cfe6", 56 | "date": "2021-12-22", "time": "09:51:07", "ticker": "AAPL", "exchange": "NASDAQ", 57 | "name": "Apple", "analyst": "Citigroup", "currency": "USD", "url": "https://www.benzinga.com/stock/AAPL/ratings", 58 | "importance": 0, "notes": "", "updated": 1640184748, "action_pt": "Raises", 59 | "action_company": "Maintains", "rating_current": "Buy", "pt_current": "200.0000", 60 | "rating_prior": "", "pt_prior": "170.0000", "url_calendar": "https://www.benzinga.com/stock/AAPL/ratings", 61 | "url_news": "https://www.benzinga.com/stock-articles/AAPL/analyst-ratings", 62 | "analyst_name": "Jim Suva"}, {"id": "a154926a-4540-437c-aef4-c0de3671cfe6", 63 | "date": "2021-12-22", "time": "05:01:24", "ticker": "MSFT", "exchange": "NASDAQ", 64 | "name": "Microsoft", "analyst": "SMBC Nikko", "currency": "USD", "url": "https://www.benzinga.com/stock/MSFT/ratings", 65 | "importance": 0, "notes": "", "updated": 1640167354, "action_pt": "Announces", 66 | "action_company": "Initiates Coverage On", "rating_current": "Outperform", 67 | "pt_current": "410.0000", "rating_prior": "", "pt_prior": "", "url_calendar": 68 | "https://www.benzinga.com/stock/MSFT/ratings", "url_news": "https://www.benzinga.com/stock-articles/MSFT/analyst-ratings", 69 | "analyst_name": "Steve Koenig"}, {"id": "a154926a-4540-437c-aef4-c0de3671cfe6", 70 | "date": "2021-12-14", "time": "08:12:14", "ticker": "AAPL", "exchange": "NASDAQ", 71 | "name": "Apple", "analyst": "Evercore ISI Group", "currency": "USD", "url": 72 | "https://www.benzinga.com/stock/AAPL/ratings", "importance": 0, "notes": "", 73 | "updated": 1639487616, "action_pt": "Raises", "action_company": "Maintains", 74 | "rating_current": "Outperform", "pt_current": "200.0000", "rating_prior": 75 | "", "pt_prior": "180.0000", "url_calendar": "https://www.benzinga.com/stock/AAPL/ratings", 76 | "url_news": "https://www.benzinga.com/stock-articles/AAPL/analyst-ratings", 77 | "analyst_name": "Amit Daryanani"}]}' 78 | headers: {} 79 | status: 80 | code: 200 81 | message: OK 82 | url: https://global-prd-api.hellostake.com/api/data/calendar/ratings?tickers=AAPL,MSFT&pageSize=4 83 | version: 1 84 | -------------------------------------------------------------------------------- /tests/cassettes/test_ratings/test_list_ratings_unknown.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/data/calendar/ratings?tickers=NOTEXIST&pageSize=4 46 | response: 47 | body: 48 | string: '{"message": "No data returned"}' 49 | headers: {} 50 | status: 51 | code: 200 52 | message: OK 53 | url: https://global-prd-api.hellostake.com/api/data/calendar/ratings?tickers=NOTEXIST&pageSize=4 54 | version: 1 55 | -------------------------------------------------------------------------------- /tests/cassettes/test_trade/test_sell[exchange0-request_0].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/asx/instrument/singleQuote/COL 46 | response: 47 | body: 48 | string: 49 | '{"marketStatus": "CLOSED", "lastTradedExchange": "ASX", "symbol": "COL", 50 | "lastTradedTimestamp": 1658988616, "lastTrade": "18.6400", "bid": "18.6300", 51 | "ask": "18.6400", "priorClose": "18.7100", "open": "18.6900", "high": "18.6900", 52 | "low": "18.4900", "pointsChange": "-0.0700", "percentageChange": "-0.37", 53 | "outOfMarketPrice": "18.6400", "outOfMarketQuantity": "848925", "outOfMarketSurplus": 54 | "-56140"}' 55 | headers: {} 56 | status: 57 | code: 200 58 | message: OK 59 | url: https://global-prd-api.hellostake.com/api/asx/instrument/singleQuote/COL 60 | - request: 61 | body: null 62 | headers: 63 | Accept: 64 | - application/json 65 | Content-Type: 66 | - application/json 67 | method: POST 68 | uri: https://global-prd-api.hellostake.com/api/asx/instrument/view/COL 69 | response: 70 | body: 71 | string: '{"instrumentId": "COL.XAU"}' 72 | headers: {} 73 | status: 74 | code: 201 75 | message: Created 76 | url: https://global-prd-api.hellostake.com/api/asx/instrument/view/COL 77 | - request: 78 | body: null 79 | headers: 80 | Accept: 81 | - application/json 82 | Content-Type: 83 | - application/json 84 | method: POST 85 | uri: https://global-prd-api.hellostake.com/api/asx/orders 86 | response: 87 | body: 88 | string: 89 | '{"order": {"id": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "broker": 90 | "FINCLEAR", "brokerOrderId": 11111, "brokerOrderVersionId": null, "brokerInstructionId": 91 | 4841281, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 92 | "instrumentId": null, "instrumentCode": "COL.XAU", "side": "SELL", "limitPrice": 93 | 18.63, "validity": "GTC", "validityDate": null, "type": "MARKET_TO_LIMIT", 94 | "placedTimestamp": "2022-07-28T20:49:39.621702", "completedTimestamp": null, 95 | "expiresAt": "2022-08-29T00:00:00", "orderStatus": "STAKE_PENDING_CREATE", 96 | "orderCompletionType": null, "filledUnits": 0, "averagePrice": null, "unitsRemaining": 97 | 20, "estimatedBrokerage": 0.0, "estimatedExchangeFees": 0.0}}' 98 | headers: {} 99 | status: 100 | code: 200 101 | message: OK 102 | url: https://global-prd-api.hellostake.com/api/asx/orders 103 | - request: 104 | body: null 105 | headers: 106 | Accept: 107 | - application/json 108 | Content-Type: 109 | - application/json 110 | method: GET 111 | uri: https://global-prd-api.hellostake.com/api/asx/orders 112 | response: 113 | body: 114 | string: 115 | '[{"id": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "broker": "FINCLEAR", 116 | "brokerOrderId": 11111, "brokerOrderVersionId": null, "brokerInstructionId": 117 | 4841281, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 118 | "instrumentId": null, "instrumentCode": "COL.XAU", "side": "SELL", "limitPrice": 119 | 18.63, "validity": "GTC", "validityDate": null, "type": "MARKET_TO_LIMIT", 120 | "placedTimestamp": "2022-07-28T20:49:39.621702", "completedTimestamp": null, 121 | "expiresAt": "2022-08-29T00:00:00", "orderStatus": "STAKE_PENDING_CREATE", 122 | "orderCompletionType": null, "filledUnits": 0, "averagePrice": null, "unitsRemaining": 123 | 20, "estimatedBrokerage": 0.0, "estimatedExchangeFees": 0.0}]' 124 | headers: {} 125 | status: 126 | code: 200 127 | message: OK 128 | url: https://global-prd-api.hellostake.com/api/asx/orders 129 | - request: 130 | body: null 131 | headers: 132 | Accept: 133 | - application/json 134 | Content-Type: 135 | - application/json 136 | method: POST 137 | uri: https://global-prd-api.hellostake.com/api/asx/orders/1cf93550-8eb4-4c32-a229-826cf8c1be59/cancel 138 | response: 139 | body: 140 | string: 141 | '{"order": {"id": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "broker": 142 | "FINCLEAR", "brokerOrderId": 11111, "brokerOrderVersionId": null, "brokerInstructionId": 143 | 4841281, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 144 | "instrumentId": null, "instrumentCode": "COL.XAU", "side": "SELL", "limitPrice": 145 | 18.63, "validity": "GTC", "validityDate": null, "type": "MARKET_TO_LIMIT", 146 | "placedTimestamp": "2022-07-28T20:49:39.621702", "completedTimestamp": null, 147 | "expiresAt": "2022-08-29T00:00:00", "orderStatus": "CLOSED", "orderCompletionType": 148 | "CANCELLED", "filledUnits": 0, "averagePrice": null, "unitsRemaining": 20, 149 | "estimatedBrokerage": 0.0, "estimatedExchangeFees": 0.0}}' 150 | headers: {} 151 | status: 152 | code: 200 153 | message: OK 154 | url: https://global-prd-api.hellostake.com/api/asx/orders/1cf93550-8eb4-4c32-a229-826cf8c1be59/cancel 155 | version: 1 156 | -------------------------------------------------------------------------------- /tests/cassettes/test_trade/test_sell[exchange1-request_1].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: POST 45 | uri: https://global-prd-api.hellostake.com/api/asx/instrument/view/COL 46 | response: 47 | body: 48 | string: '{"instrumentId": "COL.XAU"}' 49 | headers: {} 50 | status: 51 | code: 201 52 | message: Created 53 | url: https://global-prd-api.hellostake.com/api/asx/instrument/view/COL 54 | - request: 55 | body: null 56 | headers: 57 | Accept: 58 | - application/json 59 | Content-Type: 60 | - application/json 61 | method: POST 62 | uri: https://global-prd-api.hellostake.com/api/asx/orders 63 | response: 64 | body: 65 | string: 66 | '{"order": {"id": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "broker": 67 | "FINCLEAR", "brokerOrderId": 11111, "brokerOrderVersionId": null, "brokerInstructionId": 68 | 4841283, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 69 | "instrumentId": null, "instrumentCode": "COL.XAU", "side": "SELL", "limitPrice": 70 | 15.0, "validity": "GTC", "validityDate": null, "type": "LIMIT", "placedTimestamp": 71 | "2022-07-28T20:49:40.810211", "completedTimestamp": null, "expiresAt": "2022-08-29T00:00:00", 72 | "orderStatus": "STAKE_PENDING_CREATE", "orderCompletionType": null, "filledUnits": 73 | 0, "averagePrice": null, "unitsRemaining": 20, "estimatedBrokerage": 0.0, 74 | "estimatedExchangeFees": 0.0}}' 75 | headers: {} 76 | status: 77 | code: 200 78 | message: OK 79 | url: https://global-prd-api.hellostake.com/api/asx/orders 80 | - request: 81 | body: null 82 | headers: 83 | Accept: 84 | - application/json 85 | Content-Type: 86 | - application/json 87 | method: GET 88 | uri: https://global-prd-api.hellostake.com/api/asx/orders 89 | response: 90 | body: 91 | string: 92 | '[{"id": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "broker": "FINCLEAR", 93 | "brokerOrderId": 11111, "brokerOrderVersionId": null, "brokerInstructionId": 94 | 4841283, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 95 | "instrumentId": null, "instrumentCode": "COL.XAU", "side": "SELL", "limitPrice": 96 | 15.0, "validity": "GTC", "validityDate": null, "type": "LIMIT", "placedTimestamp": 97 | "2022-07-28T20:49:40.810211", "completedTimestamp": null, "expiresAt": "2022-08-29T00:00:00", 98 | "orderStatus": "STAKE_PENDING_CREATE", "orderCompletionType": null, "filledUnits": 99 | 0, "averagePrice": null, "unitsRemaining": 20, "estimatedBrokerage": 0.0, 100 | "estimatedExchangeFees": 0.0}]' 101 | headers: {} 102 | status: 103 | code: 200 104 | message: OK 105 | url: https://global-prd-api.hellostake.com/api/asx/orders 106 | - request: 107 | body: null 108 | headers: 109 | Accept: 110 | - application/json 111 | Content-Type: 112 | - application/json 113 | method: POST 114 | uri: https://global-prd-api.hellostake.com/api/asx/orders/1cf93550-8eb4-4c32-a229-826cf8c1be59/cancel 115 | response: 116 | body: 117 | string: 118 | '{"order": {"id": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "broker": 119 | "FINCLEAR", "brokerOrderId": 11111, "brokerOrderVersionId": null, "brokerInstructionId": 120 | 4841283, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 121 | "instrumentId": null, "instrumentCode": "COL.XAU", "side": "SELL", "limitPrice": 122 | 15.0, "validity": "GTC", "validityDate": null, "type": "LIMIT", "placedTimestamp": 123 | "2022-07-28T20:49:40.810211", "completedTimestamp": null, "expiresAt": "2022-08-29T00:00:00", 124 | "orderStatus": "CLOSED", "orderCompletionType": "CANCELLED", "filledUnits": 125 | 0, "averagePrice": null, "unitsRemaining": 20, "estimatedBrokerage": 0.0, 126 | "estimatedExchangeFees": 0.0}}' 127 | headers: {} 128 | status: 129 | code: 200 130 | message: OK 131 | url: https://global-prd-api.hellostake.com/api/asx/orders/1cf93550-8eb4-4c32-a229-826cf8c1be59/cancel 132 | version: 1 133 | -------------------------------------------------------------------------------- /tests/cassettes/test_trade/test_successful_trade[exchange1-request_1].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/asx/instrument/singleQuote/COL 46 | response: 47 | body: 48 | string: 49 | '{"marketStatus": "CLOSED", "lastTradedExchange": "ASX", "symbol": "COL", 50 | "lastTradedTimestamp": 1658902258, "lastTrade": "18.7100", "bid": "18.7100", 51 | "ask": "18.7200", "priorClose": "18.7800", "open": "18.8900", "high": "18.9400", 52 | "low": "18.6400", "pointsChange": "-0.0700", "percentageChange": "-0.37", 53 | "outOfMarketPrice": "18.7100", "outOfMarketQuantity": "652055", "outOfMarketSurplus": 54 | "38105"}' 55 | headers: {} 56 | status: 57 | code: 200 58 | message: OK 59 | url: https://global-prd-api.hellostake.com/api/asx/instrument/singleQuote/COL 60 | - request: 61 | body: null 62 | headers: 63 | Accept: 64 | - application/json 65 | Content-Type: 66 | - application/json 67 | method: POST 68 | uri: https://global-prd-api.hellostake.com/api/asx/instrument/view/COL 69 | response: 70 | body: 71 | string: '{"instrumentId": "COL.XAU"}' 72 | headers: {} 73 | status: 74 | code: 201 75 | message: Created 76 | url: https://global-prd-api.hellostake.com/api/asx/instrument/view/COL 77 | - request: 78 | body: null 79 | headers: 80 | Accept: 81 | - application/json 82 | Content-Type: 83 | - application/json 84 | method: POST 85 | uri: https://global-prd-api.hellostake.com/api/asx/orders 86 | response: 87 | body: 88 | string: 89 | '{"order": {"id": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "broker": 90 | "FINCLEAR", "brokerOrderId": 11111, "brokerOrderVersionId": null, "brokerInstructionId": 91 | 4816685, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 92 | "instrumentId": null, "instrumentCode": "COL.XAU", "side": "BUY", "limitPrice": 93 | 18.72, "validity": "GTC", "validityDate": null, "type": "MARKET_TO_LIMIT", 94 | "placedTimestamp": "2022-07-27T22:22:45.164059", "completedTimestamp": null, 95 | "expiresAt": "2022-08-26T00:00:00", "orderStatus": "STAKE_PENDING_CREATE", 96 | "orderCompletionType": null, "filledUnits": 0, "averagePrice": null, "unitsRemaining": 97 | 20, "estimatedBrokerage": 0.0, "estimatedExchangeFees": 0.0}}' 98 | headers: {} 99 | status: 100 | code: 200 101 | message: OK 102 | url: https://global-prd-api.hellostake.com/api/asx/orders 103 | - request: 104 | body: null 105 | headers: 106 | Accept: 107 | - application/json 108 | Content-Type: 109 | - application/json 110 | method: GET 111 | uri: https://global-prd-api.hellostake.com/api/asx/orders 112 | response: 113 | body: 114 | string: 115 | '[{"id": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "broker": "FINCLEAR", 116 | "brokerOrderId": 11111, "brokerOrderVersionId": null, "brokerInstructionId": 117 | 4816685, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 118 | "instrumentId": null, "instrumentCode": "COL.XAU", "side": "BUY", "limitPrice": 119 | 18.72, "validity": "GTC", "validityDate": null, "type": "MARKET_TO_LIMIT", 120 | "placedTimestamp": "2022-07-27T22:22:45.164059", "completedTimestamp": null, 121 | "expiresAt": "2022-08-26T00:00:00", "orderStatus": "STAKE_PENDING_CREATE", 122 | "orderCompletionType": null, "filledUnits": 0, "averagePrice": null, "unitsRemaining": 123 | 20, "estimatedBrokerage": 0.0, "estimatedExchangeFees": 0.0}]' 124 | headers: {} 125 | status: 126 | code: 200 127 | message: OK 128 | url: https://global-prd-api.hellostake.com/api/asx/orders 129 | - request: 130 | body: null 131 | headers: 132 | Accept: 133 | - application/json 134 | Content-Type: 135 | - application/json 136 | method: POST 137 | uri: https://global-prd-api.hellostake.com/api/asx/orders/1cf93550-8eb4-4c32-a229-826cf8c1be59/cancel 138 | response: 139 | body: 140 | string: 141 | '{"order": {"id": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "broker": 142 | "FINCLEAR", "brokerOrderId": 11111, "brokerOrderVersionId": null, "brokerInstructionId": 143 | 4816685, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 144 | "instrumentId": null, "instrumentCode": "COL.XAU", "side": "BUY", "limitPrice": 145 | 18.72, "validity": "GTC", "validityDate": null, "type": "MARKET_TO_LIMIT", 146 | "placedTimestamp": "2022-07-27T22:22:45.164059", "completedTimestamp": null, 147 | "expiresAt": "2022-08-26T00:00:00", "orderStatus": "CLOSED", "orderCompletionType": 148 | "CANCELLED", "filledUnits": 0, "averagePrice": null, "unitsRemaining": 20, 149 | "estimatedBrokerage": 0.0, "estimatedExchangeFees": 0.0}}' 150 | headers: {} 151 | status: 152 | code: 200 153 | message: OK 154 | url: https://global-prd-api.hellostake.com/api/asx/orders/1cf93550-8eb4-4c32-a229-826cf8c1be59/cancel 155 | version: 1 156 | -------------------------------------------------------------------------------- /tests/cassettes/test_trade/test_successful_trade[exchange3-request_3].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: POST 45 | uri: https://global-prd-api.hellostake.com/api/asx/instrument/view/COL 46 | response: 47 | body: 48 | string: '{"instrumentId": "COL.XAU"}' 49 | headers: {} 50 | status: 51 | code: 201 52 | message: Created 53 | url: https://global-prd-api.hellostake.com/api/asx/instrument/view/COL 54 | - request: 55 | body: null 56 | headers: 57 | Accept: 58 | - application/json 59 | Content-Type: 60 | - application/json 61 | method: POST 62 | uri: https://global-prd-api.hellostake.com/api/asx/orders 63 | response: 64 | body: 65 | string: 66 | '{"order": {"id": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "broker": 67 | "FINCLEAR", "brokerOrderId": 11111, "brokerOrderVersionId": null, "brokerInstructionId": 68 | 4816686, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 69 | "instrumentId": null, "instrumentCode": "COL.XAU", "side": "BUY", "limitPrice": 70 | 12.0, "validity": "GTC", "validityDate": null, "type": "LIMIT", "placedTimestamp": 71 | "2022-07-27T22:22:49.881946", "completedTimestamp": null, "expiresAt": "2022-08-26T00:00:00", 72 | "orderStatus": "STAKE_PENDING_CREATE", "orderCompletionType": null, "filledUnits": 73 | 0, "averagePrice": null, "unitsRemaining": 20, "estimatedBrokerage": 0.0, 74 | "estimatedExchangeFees": 0.0}}' 75 | headers: {} 76 | status: 77 | code: 200 78 | message: OK 79 | url: https://global-prd-api.hellostake.com/api/asx/orders 80 | - request: 81 | body: null 82 | headers: 83 | Accept: 84 | - application/json 85 | Content-Type: 86 | - application/json 87 | method: GET 88 | uri: https://global-prd-api.hellostake.com/api/asx/orders 89 | response: 90 | body: 91 | string: 92 | '[{"id": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "broker": "FINCLEAR", 93 | "brokerOrderId": 11111, "brokerOrderVersionId": null, "brokerInstructionId": 94 | 4816686, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 95 | "instrumentId": null, "instrumentCode": "COL.XAU", "side": "BUY", "limitPrice": 96 | 12.0, "validity": "GTC", "validityDate": null, "type": "LIMIT", "placedTimestamp": 97 | "2022-07-27T22:22:49.881946", "completedTimestamp": null, "expiresAt": "2022-08-26T00:00:00", 98 | "orderStatus": "STAKE_PENDING_CREATE", "orderCompletionType": null, "filledUnits": 99 | 0, "averagePrice": null, "unitsRemaining": 20, "estimatedBrokerage": 0.0, 100 | "estimatedExchangeFees": 0.0}]' 101 | headers: {} 102 | status: 103 | code: 200 104 | message: OK 105 | url: https://global-prd-api.hellostake.com/api/asx/orders 106 | - request: 107 | body: null 108 | headers: 109 | Accept: 110 | - application/json 111 | Content-Type: 112 | - application/json 113 | method: POST 114 | uri: https://global-prd-api.hellostake.com/api/asx/orders/1cf93550-8eb4-4c32-a229-826cf8c1be59/cancel 115 | response: 116 | body: 117 | string: 118 | '{"order": {"id": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "broker": 119 | "FINCLEAR", "brokerOrderId": 11111, "brokerOrderVersionId": null, "brokerInstructionId": 120 | 4816686, "brokerInstructionVersionId": 2, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 121 | "instrumentId": null, "instrumentCode": "COL.XAU", "side": "BUY", "limitPrice": 122 | 12.0, "validity": "GTC", "validityDate": null, "type": "LIMIT", "placedTimestamp": 123 | "2022-07-27T22:22:49.881946", "completedTimestamp": null, "expiresAt": "2022-08-26T00:00:00", 124 | "orderStatus": "CLOSED", "orderCompletionType": "CANCELLED", "filledUnits": 125 | 0, "averagePrice": null, "unitsRemaining": 20, "estimatedBrokerage": 0.0, 126 | "estimatedExchangeFees": 0.0}}' 127 | headers: {} 128 | status: 129 | code: 200 130 | message: OK 131 | url: https://global-prd-api.hellostake.com/api/asx/orders/1cf93550-8eb4-4c32-a229-826cf8c1be59/cancel 132 | version: 1 133 | -------------------------------------------------------------------------------- /tests/cassettes/test_transaction/test_list_transactions[exchange0-request_0].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: POST 45 | uri: https://global-prd-api.hellostake.com/api/users/accounts/accountTransactions 46 | response: 47 | body: 48 | string: 49 | '[{"accountAmount": 0.62, "accountBalance": 29442.32, "accountType": 50 | "LIVE", "comment": "f1-9011914a", "dnb": false, "finTranID": "1cf93550-8eb4-4c32-a229-826cf8c1be59", 51 | "finTranTypeID": "DIV", "feeSec": 0.0, "feeTaf": 0.0, "feeBase": 0.0, "feeXtraShares": 52 | 0.0, "feeExchange": 0.0, "fillQty": 0.0, "fillPx": 0.0, "sendCommissionToInteliclear": 53 | false, "systemAmount": 0.0, "tranAmount": 1000, "tranSource": "INTE", "tranWhen": 54 | "2020-06-09 03:35:08", "wlpAmount": 0.0, "wlpFinTranTypeID": "b553ef2c-13f8-411b-977c-00fbc5efbe59", 55 | "instrument": {"id": "20076f7d-ff21-4741-86be-33dac978ceff", "symbol": "LIT", 56 | "name": "Global X Lithium & Battery Tech ETF"}, "dividend": {"type": "CASH", 57 | "amountPerShare": 0.0622, "taxCode": "FULLY_TAXABLE"}, "dividendTax": null, 58 | "mergerAcquisition": null, "positionDelta": null, "orderID": "HHI.1cf93550-8eb4-4c32-a229-826cf8c1be59", 59 | "orderNo": "x5-4334954E"}, {"accountAmount": 3.5, "accountBalance": 29445.82, 60 | "accountType": "LIVE", "comment": "f1-9011914a", "dnb": false, "finTranID": 61 | "1cf93550-8eb4-4c32-a229-826cf8c1be59", "finTranTypeID": "DIV", "feeSec": 62 | 0.0, "feeTaf": 0.0, "feeBase": 0.0, "feeXtraShares": 0.0, "feeExchange": 0.0, 63 | "fillQty": 0.0, "fillPx": 0.0, "sendCommissionToInteliclear": false, "systemAmount": 64 | 0.0, "tranAmount": 1000, "tranSource": "INTE", "tranWhen": "2020-06-09 03:35:08", 65 | "wlpAmount": 0.0, "wlpFinTranTypeID": "b553ef2c-13f8-411b-977c-00fbc5efbe59", 66 | "instrument": {"id": "20076f7d-ff21-4741-86be-33dac978ceff", "symbol": "COP", 67 | "name": "ConocoPhillips"}, "dividend": {"type": "CASH", "amountPerShare": 68 | 0.7, "taxCode": "FULLY_TAXABLE"}, "dividendTax": null, "mergerAcquisition": 69 | null, "positionDelta": null, "orderID": "HHI.1cf93550-8eb4-4c32-a229-826cf8c1be59", 70 | "orderNo": "x5-4334954E"}, {"accountAmount": -0.53, "accountBalance": 29445.29, 71 | "accountType": "LIVE", "comment": "f1-9011914a", "dnb": false, "finTranID": 72 | "1cf93550-8eb4-4c32-a229-826cf8c1be59", "finTranTypeID": "DIVTAX", "feeSec": 73 | 0.0, "feeTaf": 0.0, "feeBase": 0.0, "feeXtraShares": 0.0, "feeExchange": 0.0, 74 | "fillQty": 0.0, "fillPx": 0.0, "sendCommissionToInteliclear": false, "systemAmount": 75 | 0.0, "tranAmount": 1000, "tranSource": "INTE", "tranWhen": "2020-06-09 03:35:08", 76 | "wlpAmount": 0.0, "wlpFinTranTypeID": "87f1acf2-b9ea-4781-b989-e403459626bb", 77 | "instrument": {"id": "20076f7d-ff21-4741-86be-33dac978ceff", "symbol": "COP", 78 | "name": "ConocoPhillips"}, "dividend": null, "dividendTax": {"rate": 0.15, 79 | "type": "NON_RESIDENT_ALIEN"}, "mergerAcquisition": null, "positionDelta": 80 | null, "orderID": "HHI.1cf93550-8eb4-4c32-a229-826cf8c1be59", "orderNo": "x5-4334954E"}]' 81 | headers: {} 82 | status: 83 | code: 200 84 | message: OK 85 | url: https://global-prd-api.hellostake.com/api/users/accounts/accountTransactions 86 | version: 1 87 | -------------------------------------------------------------------------------- /tests/cassettes/test_transaction/test_list_transactions[exchange1-request_1].yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "michael29", "emailAddress": "reevesmegan@gilmore-wright.biz", 17 | "dw_AccountId": "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": 18 | "z0-0593879b", "macAccountNumber": "d9-0481457G", "status": null, "macStatus": 19 | "BASIC_USER", "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Rita", "middleName": null, "lastName": "Jones", "phoneNumber": 21 | "(640)242-4270x965", "signUpPhase": 0, "ackSignedWhen": "2021-11-15", "createdDate": 22 | 1574303699770, "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "W2-6612029X", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "accountStatus": "OPEN", "mfaenabled": 31 | false}' 32 | headers: {} 33 | status: 34 | code: 200 35 | message: OK 36 | url: https://global-prd-api.hellostake.com/api/user 37 | - request: 38 | body: null 39 | headers: 40 | Accept: 41 | - application/json 42 | Content-Type: 43 | - application/json 44 | method: GET 45 | uri: https://global-prd-api.hellostake.com/api/asx/orders/tradeActivity?size=3&page=0 46 | response: 47 | body: 48 | string: 49 | '{"items": [{"brokerOrderId": 11111, "instrumentCode": "CBA.XAU", "type": 50 | "MARKET_TO_LIMIT", "side": "BUY", "limitPrice": 97.3, "averagePrice": 97.08, 51 | "completedTimestamp": "2022-07-25T05:17:10.595", "placedTimestamp": "2022-07-25T15:17:10.231591", 52 | "orderStatus": "CLOSED", "orderCompletionType": "FILLED", "consideration": 53 | 582.48, "effectivePrice": 97.08, "units": 6, "userBrokerageFees": 0.0, "executionDate": 54 | "2022-07-25", "contractNoteReceived": true, "contractNoteNumber": "1799186", 55 | "contractNoteNumbers": ["1799186"]}, {"brokerOrderId": 11111, "instrumentCode": 56 | "COL.XAU", "type": "MARKET_TO_LIMIT", "side": "BUY", "limitPrice": 18.95, 57 | "averagePrice": 18.86, "completedTimestamp": "2022-07-25T05:04:26.126", "placedTimestamp": 58 | "2022-07-25T15:04:25.685884", "oexerderStatus": "CLOSED", "orderCompletionType": 59 | "FILLED", "consideration": 565.8, "effectivePrice": 18.86, "units": 30, "userBrokerageFees": 60 | 0.0, "executionDate": "2022-07-25", "contractNoteReceived": true, "contractNoteNumber": 61 | "1799124", "contractNoteNumbers": ["1799124"]}, {"brokerOrderId": 11111, "instrumentCode": 62 | "VMM.XAU", "type": "MARKET_TO_LIMIT", "side": "SELL", "limitPrice": 0.295, 63 | "averagePrice": 0.295, "completedTimestamp": "2022-05-20T05:49:08.817", "placedTimestamp": 64 | "2022-05-20T15:46:46.189472", "orderStatus": "CLOSED", "orderCompletionType": 65 | "FILLED", "consideration": 590.0, "effectivePrice": 0.295, "units": 2000, 66 | "userBrokerageFees": 0.0, "executionDate": "2022-05-20", "contractNoteReceived": 67 | true, "contractNoteNumber": "1575807", "contractNoteNumbers": ["1575807"]}], 68 | "hasNext": true, "page": 0, "totalItems": 38}' 69 | headers: {} 70 | status: 71 | code: 200 72 | message: OK 73 | url: https://global-prd-api.hellostake.com/api/asx/orders/tradeActivity?size=3&page=0 74 | version: 1 75 | -------------------------------------------------------------------------------- /tests/cassettes/test_watchlist/test_add_to_watchlist.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "rita19", "emailAddress": "torresbenjamin@gmail.com", "dw_AccountId": 17 | "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": "w4-1267174s", 18 | "macAccountNumber": "H1-7641957H", "status": null, "macStatus": "BASIC_USER", 19 | "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "lastName": "Alexander", "phoneNumber": "9011530005", 21 | "signUpPhase": 0, "ackSignedWhen": "2021-05-18", "createdDate": 1574303699770, 22 | "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "L7-2127933N", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "mfaenabled": false}' 31 | headers: {} 32 | status: 33 | code: 200 34 | message: OK 35 | url: https://global-prd-api.hellostake.com/api/user 36 | - request: 37 | body: null 38 | headers: 39 | Accept: 40 | - application/json 41 | Content-Type: 42 | - application/json 43 | method: GET 44 | uri: https://global-prd-api.hellostake.com/api/products/searchProduct?symbol=SPOT&page=1&max=1 45 | response: 46 | body: 47 | string: 48 | '{"products": [{"id": "b738ff53-b576-4f09-822e-91128def0560", "instrumentTypeID": 49 | null, "symbol": "SPOT", "description": "Spotify Technology SA a Luxembourg-based 50 | company, which offers digital music-streaming services. The Company enables 51 | users to discover new releases, which includes the latest singles and albums; 52 | playlists, which includes ready-made playlists put together by music fans 53 | and experts, and over millions of songs so that users can play their favorites, 54 | discover new tracks and build a personalized collection. Users can either 55 | select Spotify Free, which includes only shuffle play or Spotify Premium, 56 | which encompasses a range of features, such as shuffle play, advertisement 57 | free, unlimited skips, listen offline, play any track and high quality audio. 58 | The Company operates through a number of subsidiaries, including Spotify LTD 59 | and is present in over 20 countries.", "category": "Stock", "currencyID": 60 | null, "urlImage": "https://drivewealth.imgix.net/symbols/spot.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 61 | "sector": null, "name": "Spotify Technology SA", "dailyReturn": -3.32, "dailyReturnPercentage": 62 | -1.57, "lastTraded": 207.55, "monthlyReturn": 0.0, "yearlyReturnPercentage": 63 | 92.8, "yearlyReturnValue": 131.68, "popularity": 9470.0, "watched": 8075, 64 | "news": 0, "bought": 9479, "viewed": 23400, "productType": "Instrument", "exchange": 65 | null, "status": "ACTIVE", "type": "EQUITY", "encodedName": "spotify-technology-sa-spot", 66 | "period": "YEAR RETURN", "inceptionDate": 1522713600000, "instrumentTags": 67 | [], "childInstruments": []}]}' 68 | headers: {} 69 | status: 70 | code: 200 71 | message: OK 72 | url: https://global-prd-api.hellostake.com/api/products/searchProduct?symbol=SPOT&page=1&max=1 73 | - request: 74 | body: null 75 | headers: 76 | Accept: 77 | - application/json 78 | Content-Type: 79 | - application/json 80 | method: POST 81 | uri: https://global-prd-api.hellostake.com/api/instruments/addRemoveInstrumentWatchlist 82 | response: 83 | body: 84 | string: 85 | '{"instrumentId": "4ef83d08-bd4e-4f2f-883c-6ec43e85e8f6", "watching": 86 | true}' 87 | headers: {} 88 | status: 89 | code: 202 90 | message: Accepted 91 | url: https://global-prd-api.hellostake.com/api/instruments/addRemoveInstrumentWatchlist 92 | version: 1 93 | -------------------------------------------------------------------------------- /tests/cassettes/test_watchlist/test_remove_from_watchlist.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - application/json 7 | Content-Type: 8 | - application/json 9 | method: GET 10 | uri: https://global-prd-api.hellostake.com/api/user 11 | response: 12 | body: 13 | string: 14 | '{"canTradeOnUnsettledFunds": false, "cpfValue": null, "emailVerified": 15 | true, "hasFunded": true, "hasTraded": true, "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 16 | "username": "rita19", "emailAddress": "torresbenjamin@gmail.com", "dw_AccountId": 17 | "1cf93550-8eb4-4c32-a229-826cf8c1be59", "dw_AccountNumber": "w4-1267174s", 18 | "macAccountNumber": "H1-7641957H", "status": null, "macStatus": "BASIC_USER", 19 | "dwStatus": null, "truliooStatus": "APPROVED", "truliooStatusWithWatchlist": 20 | null, "firstName": "Tammy", "lastName": "Alexander", "phoneNumber": "9011530005", 21 | "signUpPhase": 0, "ackSignedWhen": "2021-05-18", "createdDate": 1574303699770, 22 | "stakeApprovedDate": null, "accountType": "INDIVIDUAL", "masterAccountId": 23 | null, "referralCode": "L7-2127933N", "referredByCode": null, "regionIdentifier": 24 | "AUS", "assetSummary": null, "fundingStatistics": null, "tradingStatistics": 25 | null, "w8File": [], "rewardJourneyTimestamp": null, "rewardJourneyStatus": 26 | null, "userProfile": {"residentialAddress": null, "postalAddress": null}, 27 | "ledgerBalance": 0.0, "investorAccreditations": null, "proscoreStatus": null, 28 | "fxSpeed": "Regular", "facilitaStatus": null, "dateOfBirth": null, "upToDateDetails2021": 29 | "NO_REQUIREMENTS", "stakeKycStatus": "KYC_APPROVED", "awxMigrationDocsRequired": 30 | null, "documentsStatus": "NO_ACTION", "mfaenabled": false}' 31 | headers: {} 32 | status: 33 | code: 200 34 | message: OK 35 | url: https://global-prd-api.hellostake.com/api/user 36 | - request: 37 | body: null 38 | headers: 39 | Accept: 40 | - application/json 41 | Content-Type: 42 | - application/json 43 | method: GET 44 | uri: https://global-prd-api.hellostake.com/api/products/searchProduct?symbol=SPOT&page=1&max=1 45 | response: 46 | body: 47 | string: 48 | '{"products": [{"id": "93b3755d-0e2d-4d83-8f3b-ddd52cefaeee", "instrumentTypeID": 49 | null, "symbol": "SPOT", "description": "Spotify Technology SA a Luxembourg-based 50 | company, which offers digital music-streaming services. The Company enables 51 | users to discover new releases, which includes the latest singles and albums; 52 | playlists, which includes ready-made playlists put together by music fans 53 | and experts, and over millions of songs so that users can play their favorites, 54 | discover new tracks and build a personalized collection. Users can either 55 | select Spotify Free, which includes only shuffle play or Spotify Premium, 56 | which encompasses a range of features, such as shuffle play, advertisement 57 | free, unlimited skips, listen offline, play any track and high quality audio. 58 | The Company operates through a number of subsidiaries, including Spotify LTD 59 | and is present in over 20 countries.", "category": "Stock", "currencyID": 60 | null, "urlImage": "https://drivewealth.imgix.net/symbols/spot.png?fit=fillmax&w=125&h=125&bg=FFFFFF", 61 | "sector": null, "name": "Spotify Technology SA", "dailyReturn": -3.32, "dailyReturnPercentage": 62 | -1.57, "lastTraded": 207.55, "monthlyReturn": 0.0, "yearlyReturnPercentage": 63 | 92.8, "yearlyReturnValue": 131.68, "popularity": 9470.0, "watched": 8075, 64 | "news": 0, "bought": 9479, "viewed": 23400, "productType": "Instrument", "exchange": 65 | null, "status": "ACTIVE", "type": "EQUITY", "encodedName": "spotify-technology-sa-spot", 66 | "period": "YEAR RETURN", "inceptionDate": 1522713600000, "instrumentTags": 67 | [], "childInstruments": []}]}' 68 | headers: {} 69 | status: 70 | code: 200 71 | message: OK 72 | url: https://global-prd-api.hellostake.com/api/products/searchProduct?symbol=SPOT&page=1&max=1 73 | - request: 74 | body: null 75 | headers: 76 | Accept: 77 | - application/json 78 | Content-Type: 79 | - application/json 80 | method: POST 81 | uri: https://global-prd-api.hellostake.com/api/instruments/addRemoveInstrumentWatchlist 82 | response: 83 | body: 84 | string: 85 | '{"instrumentId": "4ef83d08-bd4e-4f2f-883c-6ec43e85e8f6", "watching": 86 | false}' 87 | headers: {} 88 | status: 89 | code: 202 90 | message: Accepted 91 | url: https://global-prd-api.hellostake.com/api/instruments/addRemoveInstrumentWatchlist 92 | version: 1 93 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import sys 4 | import uuid 5 | 6 | import pytest 7 | from dotenv import load_dotenv 8 | from faker import Faker 9 | 10 | from stake.client import StakeClient 11 | 12 | load_dotenv() 13 | 14 | 15 | @pytest.fixture 16 | async def tracing_client(request, mocker): 17 | async with StakeClient() as client: 18 | yield client 19 | 20 | 21 | @pytest.fixture(scope="session") 22 | def event_loop(): 23 | if sys.version_info < (3, 10): 24 | loop = asyncio.get_event_loop() 25 | else: 26 | try: 27 | loop = asyncio.get_running_loop() 28 | except RuntimeError: 29 | loop = asyncio.new_event_loop() 30 | 31 | asyncio.set_event_loop(loop) 32 | yield loop 33 | loop.close() 34 | 35 | 36 | def redact_sensitive_data(response): 37 | 38 | if not response["body"].get("string", None): 39 | response["headers"] = {} 40 | return response 41 | 42 | fake = Faker() 43 | fake.seed_instance(1234) 44 | fake_user_id = "7c9bbfae-0000-47b7-0000-0e66d868c2cf" 45 | fake_order_id = uuid.UUID(str(uuid.uuid3(uuid.NAMESPACE_URL, "test")), version=4) 46 | 47 | fake_transaction_id = f"HHI.{str(fake_order_id)}" 48 | obfuscated_fields = { 49 | "ackSignedWhen": str(fake.date_this_decade()), 50 | "brokerOrderId": 11111, 51 | "buyingPower": 1000.0, 52 | "cashAvailableForTrade": 800, 53 | "cashAvailableForWithdrawal": 1000, 54 | "cashAvailableForTransfer": 1000, 55 | "cashBalance": 1000.0, 56 | "comment": fake.pystr_format(), 57 | "createdWhen": str(fake.date_time_this_decade()), 58 | "dw_AccountId": str(fake_order_id), 59 | "dw_AccountNumber": fake.pystr_format(), 60 | "dw_id": str(fake_order_id), 61 | "dwAccountId": str(fake_order_id), 62 | "dwCashAvailableForWithdrawal": 1000, 63 | "dwOrderId": fake_transaction_id, 64 | "emailAddress": fake.email(), 65 | "finTranID": str(fake_order_id), 66 | "firstName": fake.first_name(), 67 | "id": str(fake_order_id), 68 | "lastName": fake.last_name(), 69 | "liquidCash": 1000.0, 70 | "macAccountNumber": fake.pystr_format(), 71 | "openQty": 100, 72 | "orderID": fake_transaction_id, 73 | "orderId": fake_transaction_id, 74 | "orderNo": fake.pystr_format(), 75 | "password": fake.password(), 76 | "phoneNumber": fake.phone_number(), 77 | "productWatchlistID": str(fake_order_id), 78 | "reference": fake.pystr_format(), 79 | "referenceNumber": fake.pystr_format(), 80 | "referralCode": fake.pystr_format(), 81 | "settledCash": 10000.00, 82 | "tranAmount": 1000, 83 | "tranWhen": str(fake.date_time_this_decade()), 84 | "unrealizedDayPLPercent": 1.0, 85 | "unrealizedPL": 1.0, 86 | "userId": "7c9bbfae-0000-47b7-0000-0e66d868c2cf", 87 | "userID": fake_user_id, 88 | "username": fake.simple_profile()["username"], 89 | "watchlistId": str(fake_order_id), 90 | } 91 | 92 | def _redact_response_body(body): 93 | if not body: 94 | return body 95 | 96 | if isinstance(body, list): 97 | body = [_redact_response_body(res) for res in body] 98 | elif isinstance(body, dict): 99 | for field, value in body.items(): 100 | if isinstance(value, list): 101 | body[field] = [_redact_response_body(res) for res in value] 102 | elif isinstance(value, dict): 103 | body[field] = _redact_response_body(value) 104 | else: 105 | body[field] = obfuscated_fields.get(field, value) 106 | 107 | return body 108 | 109 | body = json.loads(response["body"]["string"]) 110 | 111 | response["body"]["string"] = bytes(json.dumps(_redact_response_body(body)), "utf-8") 112 | 113 | response["headers"] = {} 114 | return response 115 | 116 | 117 | @pytest.fixture(scope="module") 118 | def vcr_config(): 119 | return { 120 | "filter_headers": ["stake-session-token"], 121 | "before_record_response": redact_sensitive_data, 122 | } 123 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stake import CredentialsLoginRequest, SessionTokenLoginRequest, StakeClient 4 | from stake.client import InvalidLoginException 5 | 6 | 7 | def test_credentials_login_serializing(): 8 | 9 | request = CredentialsLoginRequest( 10 | username="unknown@user.com", 11 | remember_me_days=15, 12 | password="WeirdPassword", 13 | ) 14 | 15 | assert request.model_dump(by_alias=True) == { 16 | "username": "unknown@user.com", 17 | "password": "WeirdPassword", 18 | "platformType": "WEB_f5K2x3", 19 | "rememberMeDays": 15, 20 | "otp": None, 21 | } 22 | 23 | 24 | @pytest.mark.asyncio 25 | async def test_credentials_login(): 26 | request = CredentialsLoginRequest( 27 | username="unknown@user.com", password="WeirdPassword" 28 | ) 29 | 30 | with pytest.raises(InvalidLoginException): 31 | async with StakeClient(request=request) as client: 32 | assert client 33 | 34 | request = SessionTokenLoginRequest(token="invalidtoken002") 35 | with pytest.raises(InvalidLoginException): 36 | async with StakeClient(request=request) as client: 37 | assert client 38 | -------------------------------------------------------------------------------- /tests/test_equity.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import stake 4 | from stake import constant 5 | 6 | 7 | @pytest.mark.parametrize("exchange", (constant.NYSE, constant.ASX)) 8 | @pytest.mark.vcr() 9 | @pytest.mark.asyncio 10 | async def test_list_equities(tracing_client: stake.StakeClient, exchange): 11 | 12 | tracing_client.set_exchange(exchange) 13 | equities = await tracing_client.equities.list() 14 | 15 | assert equities.__class__.__name__ == "EquityPositions" 16 | assert equities.equity_positions 17 | 18 | for e in equities.equity_positions: 19 | assert not (set(e.model_fields.keys()).difference(e.model_fields_set)) 20 | -------------------------------------------------------------------------------- /tests/test_funding.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | import pytest 4 | 5 | import stake 6 | from stake import constant 7 | from stake.asx.funding import FundingRequest as ASXFundingRequest 8 | from stake.funding import TransactionRecordRequest as NYSEFundingRequest 9 | 10 | 11 | @pytest.mark.parametrize( 12 | "exchange, request_", 13 | ( 14 | (constant.NYSE, NYSEFundingRequest(limit=1000)), 15 | (constant.ASX, ASXFundingRequest(limit=3)), 16 | ), 17 | ) 18 | @pytest.mark.vcr() 19 | @pytest.mark.asyncio 20 | async def test_list_fundings( 21 | tracing_client: stake.StakeClient, 22 | exchange: Union[constant.ASXUrl, constant.NYSEUrl], 23 | request_: Union[ASXFundingRequest, NYSEFundingRequest], 24 | ): 25 | tracing_client.set_exchange(exchange=exchange) 26 | await tracing_client.fundings.list(request_) 27 | 28 | 29 | @pytest.mark.parametrize("exchange", (constant.NYSE, constant.ASX)) 30 | @pytest.mark.vcr() 31 | @pytest.mark.asyncio 32 | async def test_cash_available( 33 | tracing_client: stake.StakeClient, 34 | exchange: Union[constant.ASXUrl, constant.NYSEUrl], 35 | ): 36 | tracing_client.set_exchange(exchange) 37 | cash_available = await tracing_client.fundings.cash_available() 38 | assert cash_available.cash_available_for_withdrawal == 1000.0 39 | 40 | 41 | @pytest.mark.parametrize("exchange", (constant.NYSE, constant.ASX)) 42 | @pytest.mark.vcr() 43 | @pytest.mark.asyncio 44 | async def test_funds_in_flight( 45 | tracing_client: stake.StakeClient, 46 | exchange: Union[constant.ASXUrl, constant.NYSEUrl], 47 | ): 48 | tracing_client.set_exchange(exchange=exchange) 49 | await tracing_client.fundings.in_flight() 50 | -------------------------------------------------------------------------------- /tests/test_fx.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import stake 4 | from stake import FxConversionRequest 5 | 6 | 7 | @pytest.mark.vcr() 8 | @pytest.mark.asyncio 9 | async def test_fx_conversion(tracing_client: stake.StakeClient): 10 | request = FxConversionRequest( 11 | from_currency="USD", to_currency="AUD", from_amount=1000.0 12 | ) 13 | conversion_result = await tracing_client.fx.convert(request) 14 | assert conversion_result.rate > 1.0 15 | -------------------------------------------------------------------------------- /tests/test_integration.py: -------------------------------------------------------------------------------- 1 | # this module contains tests that run real stake api calls 2 | 3 | 4 | import pytest 5 | 6 | import stake 7 | from stake import constant 8 | from stake.transaction import TransactionRecordRequest 9 | 10 | 11 | @pytest.mark.parametrize("exchange", (constant.NYSE,)) 12 | @pytest.mark.asyncio 13 | async def test_integration_NYSE(exchange): 14 | async with stake.StakeClient(exchange=exchange) as session: 15 | await session.watchlist.list_watchlists() 16 | await session.equities.list() 17 | await session.orders.list() 18 | await session.market.get() 19 | await session.market.is_open() 20 | await session.fundings.cash_available() 21 | 22 | request = stake.TransactionRecordRequest(limit=10) 23 | transactions = await session.transactions.list(request) 24 | assert len(transactions) == 10 25 | await session.fundings.list(TransactionRecordRequest(limit=100)) 26 | 27 | 28 | @pytest.mark.parametrize("exchange", (constant.ASX,)) 29 | @pytest.mark.asyncio 30 | async def test_integration_ASX(exchange): 31 | async with stake.StakeClient(exchange=exchange) as session: 32 | await session.watchlist.list_watchlists() 33 | await session.equities.list() 34 | await session.orders.list() 35 | await session.market.get() 36 | await session.market.is_open() 37 | await session.fundings.cash_available() 38 | 39 | from stake.asx.funding import FundingRequest as ASXFundingRequest 40 | from stake.asx.funding import Sort 41 | from stake.asx.transaction import ( 42 | TransactionRecordRequest as ASXTransactionRecordRequest, 43 | ) 44 | 45 | request = ASXFundingRequest( 46 | limit=10, sort=[Sort(direction="asc", attribute="insertedAt")] 47 | ) 48 | result = await session.fundings.list(request=request) 49 | assert len(result.fundings) == 10 50 | 51 | request = ASXTransactionRecordRequest( 52 | limit=10, sort=[Sort(direction="desc", attribute="insertedAt")] 53 | ) 54 | result = await session.transactions.list(request=request) 55 | assert len(result.transactions) == 10 56 | -------------------------------------------------------------------------------- /tests/test_market.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import stake 4 | from stake import constant 5 | 6 | 7 | @pytest.mark.parametrize("exchange", (constant.NYSE, constant.ASX)) 8 | @pytest.mark.vcr() 9 | @pytest.mark.asyncio 10 | async def test_check_market_status(tracing_client: stake.StakeClient, exchange): 11 | tracing_client.set_exchange(exchange) 12 | market_status = await tracing_client.market.get() 13 | assert market_status.status.current == "close" 14 | -------------------------------------------------------------------------------- /tests/test_order.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from stake import constant 4 | from stake.client import StakeClient 5 | 6 | 7 | @pytest.mark.parametrize("exchange", (constant.NYSE, constant.ASX)) 8 | @pytest.mark.vcr() 9 | @pytest.mark.asyncio 10 | async def test_list_orders(tracing_client: StakeClient, exchange): 11 | tracing_client.set_exchange(exchange) 12 | orders = await tracing_client.orders.list() 13 | assert len(orders) 14 | assert orders[0].order_id 15 | 16 | 17 | @pytest.mark.parametrize("exchange", (constant.NYSE, constant.ASX)) 18 | @pytest.mark.vcr() 19 | @pytest.mark.asyncio 20 | async def test_cancel_order(tracing_client: StakeClient, exchange): 21 | """Warning, will cancel the first pending order.""" 22 | tracing_client.set_exchange(exchange) 23 | orders = await tracing_client.orders.list() 24 | how_many_orders = len(orders) 25 | cancel_order = await tracing_client.orders.cancel(orders[0]) 26 | assert cancel_order 27 | orders = await tracing_client.orders.list() 28 | assert len(orders) == how_many_orders - 1 29 | 30 | 31 | @pytest.mark.parametrize("exchange", (constant.NYSE, constant.ASX)) 32 | @pytest.mark.vcr() 33 | @pytest.mark.asyncio 34 | async def test_brokerage(tracing_client: StakeClient, exchange): 35 | tracing_client.set_exchange(exchange) 36 | brokerage = await tracing_client.orders.brokerage(order_amount=1.0) 37 | assert brokerage 38 | -------------------------------------------------------------------------------- /tests/test_product.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import List 3 | 4 | import aiohttp 5 | import pytest 6 | from pydantic import BaseModel 7 | 8 | from stake import asx, constant 9 | from stake.client import HttpClient, StakeClient 10 | from stake.constant import NYSE 11 | from stake.product import Product 12 | 13 | 14 | @pytest.mark.parametrize("exchange", (constant.NYSE, constant.ASX)) 15 | @pytest.mark.vcr() 16 | @pytest.mark.asyncio 17 | async def test_find_products_by_name(tracing_client: StakeClient, exchange): 18 | from stake.product import ProductSearchByName 19 | 20 | tracing_client.set_exchange(exchange) 21 | request = ProductSearchByName(keyword="techno") 22 | products = await tracing_client.products.search(request) 23 | assert len(products) == 10 24 | assert products[0].__class__.__name__ == "Instrument" 25 | 26 | 27 | @pytest.mark.asyncio 28 | async def test_product_serializer(): 29 | 30 | async with aiohttp.ClientSession(raise_for_status=True) as session: 31 | await session.get(HttpClient.url(NYSE.symbol.format(symbol="MSFT"))) 32 | 33 | async def _get_symbol(symbol): 34 | response = await session.get( 35 | HttpClient.url(NYSE.symbol.format(symbol=symbol)) 36 | ) 37 | return await response.json() 38 | 39 | coros = [_get_symbol(symbol) for symbol in {"MSFT", "TSLA", "GOOG"}] 40 | 41 | results = await asyncio.gather(*coros) 42 | assert [ 43 | Product(**serialized_product["products"][0]) 44 | for serialized_product in results 45 | ] 46 | 47 | 48 | @pytest.mark.parametrize( 49 | "exchange, symbols", 50 | ((constant.NYSE, ["TSLA", "MSFT", "GOOG"]), (constant.ASX, ["ANZ", "WDS", "COL"])), 51 | ) 52 | @pytest.mark.vcr() 53 | @pytest.mark.asyncio 54 | async def test_get_product( 55 | tracing_client: StakeClient, exchange: BaseModel, symbols: List[str] 56 | ): 57 | tracing_client.set_exchange(exchange) 58 | for symbol in symbols: 59 | product = await tracing_client.products.get(symbol) 60 | assert product.__class__.__name__ == "Product" 61 | 62 | 63 | @pytest.mark.parametrize( 64 | "exchange, keyword", ((constant.ASX, "CBA"), (constant.NYSE, "MSFT")) 65 | ) 66 | @pytest.mark.asyncio 67 | async def test_search_products( 68 | tracing_client: StakeClient, exchange: BaseModel, keyword: str 69 | ): 70 | tracing_client.set_exchange(exchange) 71 | search_results = await tracing_client.products.search( 72 | asx.ProductSearchByName(keyword=keyword) 73 | ) 74 | assert search_results 75 | 76 | product = await tracing_client.products.product_from_instrument(search_results[0]) 77 | assert product 78 | -------------------------------------------------------------------------------- /tests/test_ratings.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import stake 4 | from stake import RatingsRequest 5 | 6 | 7 | @pytest.mark.vcr() 8 | @pytest.mark.asyncio 9 | async def test_list_ratings(tracing_client: stake.StakeClient): 10 | request = RatingsRequest(symbols=["AAPL", "MSFT"], limit=4) 11 | ratings = await tracing_client.ratings.list(request) 12 | assert len(ratings) == 4 13 | assert ratings[0].symbol in ("AAPL", "MSFT") 14 | 15 | 16 | @pytest.mark.vcr() 17 | @pytest.mark.asyncio 18 | async def test_list_ratings_unknown(tracing_client: stake.StakeClient): 19 | request = RatingsRequest(symbols=["NOTEXIST"], limit=4) 20 | ratings = await tracing_client.ratings.list(request) 21 | assert len(ratings) == 0 22 | -------------------------------------------------------------------------------- /tests/test_trade.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import stake 4 | from stake import constant 5 | from stake.asx import trade as asx_trade 6 | from stake.trade import LimitBuyRequest, MarketBuyRequest, StopBuyRequest 7 | 8 | 9 | @pytest.mark.vcr() 10 | @pytest.mark.asyncio 11 | async def test_stop_buy(tracing_client: stake.StakeClient): 12 | # amountCash too low. 13 | with pytest.raises(ValueError): 14 | await tracing_client.trades.buy( 15 | StopBuyRequest(symbol="AAPL", price=400, amountCash=5) 16 | ) 17 | 18 | # price too low. 19 | with pytest.raises(RuntimeError): 20 | await tracing_client.trades.buy( 21 | StopBuyRequest(symbol="AMD", price=10, amountCash=10) 22 | ) 23 | 24 | 25 | @pytest.mark.vcr() 26 | @pytest.mark.asyncio 27 | async def test_limit_buy(tracing_client: stake.StakeClient): 28 | with pytest.raises(RuntimeError): 29 | await tracing_client.trades.buy( 30 | LimitBuyRequest(symbol="AAPL", limitPrice=460, quantity=1000) 31 | ) 32 | 33 | 34 | @pytest.mark.vcr() 35 | @pytest.mark.asyncio 36 | async def test_limit_sell(tracing_client: stake.StakeClient): 37 | with pytest.raises(RuntimeError): 38 | await tracing_client.trades.sell( 39 | LimitBuyRequest(symbol="AAPL", limitPrice=400, quantity=100) 40 | ) 41 | 42 | 43 | @pytest.mark.parametrize( 44 | "exchange, request_", 45 | ( 46 | ( 47 | constant.NYSE, 48 | MarketBuyRequest(symbol="TSLA", amount_cash=20, comment="from cloud"), 49 | ), 50 | (constant.ASX, asx_trade.MarketBuyRequest(symbol="COL", units=20)), 51 | ( 52 | constant.NYSE, 53 | LimitBuyRequest( 54 | symbol="TSLA", limit_price=120, quantity=1, comment="from cloud" 55 | ), 56 | ), 57 | (constant.ASX, asx_trade.LimitBuyRequest(symbol="COL", units=20, price=12.0)), 58 | ), 59 | ) 60 | @pytest.mark.asyncio 61 | @pytest.mark.vcr() 62 | async def test_successful_trade(tracing_client: stake.StakeClient, exchange, request_): 63 | tracing_client.set_exchange(exchange) 64 | 65 | trade = await tracing_client.trades.buy(request_) 66 | assert trade 67 | 68 | orders = await tracing_client.orders.list() 69 | 70 | # cancel the order 71 | await tracing_client.orders.cancel(orders[0]) 72 | 73 | 74 | @pytest.mark.parametrize( 75 | "exchange, request_", 76 | ( 77 | (constant.ASX, asx_trade.MarketSellRequest(symbol="COL", units=20)), 78 | (constant.ASX, asx_trade.LimitSellRequest(symbol="COL", units=20, price=15.0)), 79 | ), 80 | ) 81 | @pytest.mark.asyncio 82 | @pytest.mark.vcr() 83 | async def test_sell(tracing_client: stake.StakeClient, exchange, request_): 84 | tracing_client.set_exchange(exchange) 85 | 86 | trade = await tracing_client.trades.sell(request_) 87 | assert trade 88 | 89 | orders = await tracing_client.orders.list() 90 | assert orders 91 | # cancel the order 92 | await tracing_client.orders.cancel(orders[0]) 93 | -------------------------------------------------------------------------------- /tests/test_transaction.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | import pytest 4 | 5 | import stake 6 | from stake import constant, transaction 7 | from stake.asx import transaction as asx_transaction 8 | 9 | 10 | @pytest.mark.parametrize( 11 | "exchange, request_", 12 | ( 13 | (constant.NYSE, transaction.TransactionRecordRequest(limit=3)), 14 | (constant.ASX, asx_transaction.TransactionRecordRequest(limit=3)), 15 | ), 16 | ) 17 | @pytest.mark.vcr() 18 | @pytest.mark.asyncio 19 | async def test_list_transactions( 20 | tracing_client: stake.StakeClient, 21 | exchange: Union[constant.ASXUrl, constant.NYSEUrl], 22 | request_: Union[ 23 | asx_transaction.TransactionRecordRequest, transaction.TransactionRecordRequest 24 | ], 25 | ): 26 | tracing_client.set_exchange(exchange) 27 | transactions = await tracing_client.transactions.list(request_) 28 | 29 | assert transactions 30 | -------------------------------------------------------------------------------- /tests/test_watchlist.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from pydantic import BaseModel 3 | 4 | import stake 5 | from stake import constant 6 | from stake.watchlist import ( 7 | AddToWatchlistRequest, 8 | CreateWatchlistRequest, 9 | DeleteWatchlistRequest, 10 | RemoveFromWatchlistRequest, 11 | UpdateWatchlistRequest, 12 | Watchlist, 13 | ) 14 | 15 | # flake8: noqa 16 | 17 | 18 | @pytest.mark.vcr() 19 | @pytest.mark.asyncio 20 | async def test_add_to_watchlist(tracing_client: stake.StakeClient): 21 | added = await tracing_client.watchlist.add(AddToWatchlistRequest(symbol="SPOT")) 22 | assert added.watching 23 | 24 | 25 | @pytest.mark.vcr() 26 | @pytest.mark.asyncio 27 | async def test_remove_from_watchlist(tracing_client: stake.StakeClient): 28 | 29 | removed = await tracing_client.watchlist.remove( 30 | RemoveFromWatchlistRequest(symbol="SPOT") 31 | ) 32 | assert not removed.watching 33 | 34 | 35 | @pytest.mark.vcr() 36 | @pytest.mark.asyncio 37 | async def test_list_watchlist(tracing_client: stake.StakeClient): 38 | watched = await tracing_client.watchlist.list() 39 | assert len(watched) == 10 40 | 41 | 42 | @pytest.mark.parametrize( 43 | "exchange, symbols", 44 | ( 45 | (constant.NYSE, ["TSLA", "GOOG", "MSFT", "NOK"]), 46 | (constant.ASX, ["COL", "WDS", "BHP", "OOO"]), 47 | ), 48 | ) 49 | @pytest.mark.vcr() 50 | @pytest.mark.asyncio 51 | async def test_create_watchlist( 52 | tracing_client: stake.StakeClient, exchange: BaseModel, symbols: str 53 | ): 54 | name = f"test_watchlist__{exchange.__class__.__name__}" 55 | tracing_client.set_exchange(exchange) 56 | watched = await tracing_client.watchlist.create_watchlist( 57 | CreateWatchlistRequest(name=name) 58 | ) 59 | if not watched: 60 | return 61 | assert len(symbols[:3]) == 3 62 | update_request = UpdateWatchlistRequest( 63 | id=watched.watchlist_id, tickers=symbols[:3] 64 | ) 65 | 66 | watched = await tracing_client.watchlist.add_to_watchlist(request=update_request) 67 | assert watched.count == 3 68 | # update again, with the same symbols, nothing should change. 69 | watched = await tracing_client.watchlist.add_to_watchlist(request=update_request) 70 | assert watched.count == 3 71 | 72 | update_request = UpdateWatchlistRequest( 73 | id=watched.watchlist_id, 74 | tickers=symbols, 75 | ) 76 | watched = await tracing_client.watchlist.remove_from_watchlist( 77 | request=update_request 78 | ) 79 | 80 | assert watched.count == 0 81 | 82 | result = await tracing_client.watchlist.delete_watchlist( 83 | request=DeleteWatchlistRequest(id=update_request.id) 84 | ) 85 | assert result 86 | --------------------------------------------------------------------------------