Skip to content

Commit

Permalink
Move from price effect to +/- for numbers (#169)
Browse files Browse the repository at this point in the history
* begin remove price effects

* finish change, update tests

* fix bug in tests
  • Loading branch information
Graeme22 authored Oct 10, 2024
1 parent 9deb4e5 commit 4be4016
Show file tree
Hide file tree
Showing 13 changed files with 262 additions and 148 deletions.
4 changes: 2 additions & 2 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
Fixes ...

## Pre-merge checklist
- [ ] Code formatted correctly with `uv run ruff format .`
- [ ] Code formatted correctly (check with `make lint`)
- [ ] Code implemented for both sync and async
- [ ] Passing tests locally
- [ ] Passing tests locally (check with `make test`, make sure you have `TT_USERNAME`, `TT_PASSWORD`, and `TT_ACCOUNT` environment variables set)
- [ ] New tests added (if applicable)

Please note that, in order to pass the tests, you'll need to set up your Tastytrade credentials as repository secrets on your local fork. Read more at CONTRIBUTING.md.
7 changes: 3 additions & 4 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ jobs:
uv sync
- name: Lint with ruff
run: |
uv run ruff check .
- name: Type check with mypy
uv run ruff check tastytrade/ tests/
- name: Type check with pyright
run: |
uv run mypy -p tastytrade
uv run mypy -p tests
uv run pyright tastytrade/ tests/
- name: Test with pytest
run: |
uv run pytest --cov=tastytrade --cov-report=term-missing tests/ --cov-fail-under=95
Expand Down
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,6 @@ target/
.ipynb_checkpoints
*.ipynb

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.8
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ install:
uv sync

lint:
uv run ruff check --fix .
uv run mypy -p tastytrade
uv run mypy -p tests
uv run ruff format tastytrade/ tests/
uv run ruff check tastytrade/ tests/
uv run pyright tastytrade/ tests/

test:
uv run pytest --cov=tastytrade --cov-report=term-missing tests/ --cov-fail-under=95
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ Documentation = "https://tastyworks-api.readthedocs.io/en/latest"

[tool.uv]
dev-dependencies = [
"mypy>=1.11.2",
"pytest>=8.3.3",
"pytest-aio>=1.5.0",
"pytest-cov>=5.0.0",
"ruff>=0.6.9",
"types-pytz>=2024.2.0.20241003",
"pyright>=1.1.384",
]

[tool.setuptools.package-data]
Expand Down
108 changes: 79 additions & 29 deletions tastytrade/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from decimal import Decimal
from typing import Any, Dict, List, Literal, Optional, Union

from pydantic import BaseModel
from pydantic import BaseModel, model_validator

from tastytrade.order import (
InstrumentType,
Expand All @@ -13,12 +13,13 @@
PlacedComplexOrder,
PlacedOrder,
PlacedOrderResponse,
PriceEffect,
)
from tastytrade.session import Session
from tastytrade.utils import (
PriceEffect,
TastytradeError,
TastytradeJsonDataclass,
_set_sign_for,
today_in_new_york,
validate_response,
)
Expand Down Expand Up @@ -61,12 +62,10 @@ class AccountBalance(TastytradeJsonDataclass):
cash_available_to_withdraw: Decimal
day_trade_excess: Decimal
pending_cash: Decimal
pending_cash_effect: PriceEffect
long_cryptocurrency_value: Decimal
short_cryptocurrency_value: Decimal
cryptocurrency_margin_requirement: Decimal
unsettled_cryptocurrency_fiat_amount: Decimal
unsettled_cryptocurrency_fiat_effect: PriceEffect
closed_loop_available_balance: Decimal
equity_offering_margin_requirement: Decimal
long_bond_value: Decimal
Expand All @@ -81,9 +80,18 @@ class AccountBalance(TastytradeJsonDataclass):
updated_at: datetime
apex_starting_day_margin_equity: Optional[Decimal] = None
buying_power_adjustment: Optional[Decimal] = None
buying_power_adjustment_effect: Optional[PriceEffect] = None
time_of_day: Optional[str] = None

@model_validator(mode="before")
@classmethod
def validate_price_effects(cls, data: Any) -> Any:
if isinstance(data, dict):
key = "unsettled-cryptocurrency-fiat-amount"
effect = data.get("unsettled-cryptocurrency-fiat-effect")
if effect == PriceEffect.DEBIT:
data[key] = -abs(Decimal(data[key]))
return _set_sign_for(data, ["pending_cash", "buying_power_adjustment"])


class AccountBalanceSnapshot(TastytradeJsonDataclass):
"""
Expand Down Expand Up @@ -117,19 +125,27 @@ class AccountBalanceSnapshot(TastytradeJsonDataclass):
cash_available_to_withdraw: Decimal
day_trade_excess: Decimal
pending_cash: Decimal
pending_cash_effect: PriceEffect
snapshot_date: date
time_of_day: Optional[str] = None
long_cryptocurrency_value: Optional[Decimal] = None
short_cryptocurrency_value: Optional[Decimal] = None
cryptocurrency_margin_requirement: Optional[Decimal] = None
unsettled_cryptocurrency_fiat_amount: Optional[Decimal] = None
unsettled_cryptocurrency_fiat_effect: Optional[PriceEffect] = None
closed_loop_available_balance: Optional[Decimal] = None
equity_offering_margin_requirement: Optional[Decimal] = None
long_bond_value: Optional[Decimal] = None
bond_margin_requirement: Optional[Decimal] = None

@model_validator(mode="before")
@classmethod
def validate_price_effects(cls, data: Any) -> Any:
if isinstance(data, dict):
key = "unsettled-cryptocurrency-fiat-amount"
effect = data.get("unsettled-cryptocurrency-fiat-effect")
if effect == PriceEffect.DEBIT:
data[key] = -abs(Decimal(data[key]))
return _set_sign_for(data, ["pending_cash"])


class CurrentPosition(TastytradeJsonDataclass):
"""
Expand Down Expand Up @@ -161,15 +177,22 @@ class CurrentPosition(TastytradeJsonDataclass):
deliverable_type: Optional[str] = None
average_yearly_market_close_price: Optional[Decimal] = None
average_daily_market_close_price: Optional[Decimal] = None
realized_day_gain_effect: Optional[PriceEffect] = None
realized_day_gain_date: Optional[date] = None
realized_today_effect: Optional[PriceEffect] = None
realized_today_date: Optional[date] = None

@model_validator(mode="before")
@classmethod
def validate_price_effects(cls, data: Any) -> Any:
return _set_sign_for(data, ["realized_day_gain", "realized_today"])


class FeesInfo(TastytradeJsonDataclass):
total_fees: Decimal
total_fees_effect: PriceEffect

@model_validator(mode="before")
@classmethod
def validate_price_effects(cls, data: Any) -> Any:
return _set_sign_for(data, ["total_fees"])


class Lot(TastytradeJsonDataclass):
Expand All @@ -195,23 +218,32 @@ class MarginReportEntry(TastytradeJsonDataclass):
description: str
code: str
buying_power: Decimal
buying_power_effect: PriceEffect
margin_calculation_type: str
margin_requirement: Decimal
margin_requirement_effect: PriceEffect
expected_price_range_up_percent: Optional[Decimal] = None
expected_price_range_down_percent: Optional[Decimal] = None
groups: Optional[List[Dict[str, Any]]] = None
initial_requirement: Optional[Decimal] = None
initial_requirement_effect: Optional[PriceEffect] = None
maintenance_requirement: Optional[Decimal] = None
maintenance_requirement_effect: Optional[PriceEffect] = None
point_of_no_return_percent: Optional[Decimal] = None
price_increase_percent: Optional[Decimal] = None
price_decrease_percent: Optional[Decimal] = None
underlying_symbol: Optional[str] = None
underlying_type: Optional[str] = None

@model_validator(mode="before")
@classmethod
def validate_price_effects(cls, data: Any) -> Any:
return _set_sign_for(
data,
[
"buying_power",
"margin_requirement",
"initial_requirement",
"maintenance_requirement",
],
)


class MarginReport(TastytradeJsonDataclass):
"""
Expand All @@ -223,23 +255,32 @@ class MarginReport(TastytradeJsonDataclass):
margin_calculation_type: str
option_level: str
margin_requirement: Decimal
margin_requirement_effect: PriceEffect
maintenance_requirement: Decimal
maintenance_requirement_effect: PriceEffect
margin_equity: Decimal
margin_equity_effect: PriceEffect
option_buying_power: Decimal
option_buying_power_effect: PriceEffect
reg_t_margin_requirement: Decimal
reg_t_margin_requirement_effect: PriceEffect
reg_t_option_buying_power: Decimal
reg_t_option_buying_power_effect: PriceEffect
maintenance_excess: Decimal
maintenance_excess_effect: PriceEffect
last_state_timestamp: int
groups: List[Union[MarginReportEntry, EmptyDict]]
initial_requirement: Optional[Decimal] = None
initial_requirement_effect: Optional[PriceEffect] = None

@model_validator(mode="before")
@classmethod
def validate_price_effects(cls, data: Any) -> Any:
return _set_sign_for(
data,
[
"maintenance_requirement",
"margin_requirement",
"margin_equity",
"maintenance_excess",
"option_buying_power",
"reg_t_margin_requirement",
"reg_t_option_buying_power",
"initial_requirement",
],
)


class MarginRequirement(TastytradeJsonDataclass):
Expand Down Expand Up @@ -355,9 +396,7 @@ class Transaction(TastytradeJsonDataclass):
executed_at: datetime
transaction_date: date
value: Decimal
value_effect: PriceEffect
net_value: Decimal
net_value_effect: PriceEffect
is_estimated_fee: bool
symbol: Optional[str] = None
instrument_type: Optional[InstrumentType] = None
Expand All @@ -366,13 +405,9 @@ class Transaction(TastytradeJsonDataclass):
quantity: Optional[Decimal] = None
price: Optional[Decimal] = None
regulatory_fees: Optional[Decimal] = None
regulatory_fees_effect: Optional[PriceEffect] = None
clearing_fees: Optional[Decimal] = None
clearing_fees_effect: Optional[PriceEffect] = None
commission: Optional[Decimal] = None
commission_effect: Optional[PriceEffect] = None
proprietary_index_option_fees: Optional[Decimal] = None
proprietary_index_option_fees_effect: Optional[PriceEffect] = None
ext_exchange_order_number: Optional[str] = None
ext_global_order_number: Optional[int] = None
ext_group_id: Optional[str] = None
Expand All @@ -385,14 +420,29 @@ class Transaction(TastytradeJsonDataclass):
leg_count: Optional[int] = None
destination_venue: Optional[str] = None
other_charge: Optional[Decimal] = None
other_charge_effect: Optional[PriceEffect] = None
other_charge_description: Optional[str] = None
reverses_id: Optional[int] = None
cost_basis_reconciliation_date: Optional[date] = None
lots: Optional[List[Lot]] = None
agency_price: Optional[Decimal] = None
principal_price: Optional[Decimal] = None

@model_validator(mode="before")
@classmethod
def validate_price_effects(cls, data: Any) -> Any:
return _set_sign_for(
data,
[
"value",
"net_value",
"regulatory_fees",
"clearing_fees",
"proprietary_index_option_fees",
"commission",
"other_charge",
],
)


class Account(TastytradeJsonDataclass):
"""
Expand Down
Loading

0 comments on commit 4be4016

Please sign in to comment.