Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

account and positions #13

Merged
merged 1 commit into from
Jun 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions cschwabpy/models/trade_models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,108 @@
from cschwabpy.models import JSONSerializableBaseModel
from typing import Optional, List, Any
from pydantic import Field
from enum import Enum


class AccountType(str, Enum):
MARGIN = "MARGIN"
CASH = "CASH"
IRA = "IRA"


class AccountNumberModel(JSONSerializableBaseModel):
accountNumber: str
hashValue: str


class MarginBalance(JSONSerializableBaseModel):
availableFunds: Optional[float] = None
availableFundsNonMarginableTrade: Optional[float] = None
buyingPower: Optional[float] = None
buyingPowerNonMarginableTrade: Optional[float] = None
dayTradingBuyingPower: Optional[float] = None
dayTradingBuyingPowerCall: Optional[float] = None
dayTradingEquityCall: Optional[float] = None
equity: Optional[float] = None
equityPercentage: Optional[float] = None
longMarginValue: Optional[float] = None
longOptionMarketValue: Optional[float] = None
longStockValue: Optional[float] = None
maintenanceCall: Optional[float] = None
maintenanceRequirement: Optional[float] = None
margin: Optional[float] = None
marginEquity: Optional[float] = None
moneyMarketFund: Optional[float] = None
mutualFundValue: Optional[float] = None
sma: Optional[float] = None
stockBuyingPower: Optional[float] = None
optionBuyingPower: Optional[float] = None
regTCall: Optional[float] = None
shortMarginValue: Optional[float] = None
shortOptionMarketValue: Optional[float] = None
shortStockValue: Optional[float] = None
totalCash: Optional[float] = None
isInCall: Optional[bool] = None
unsettledCash: Optional[float] = None
pendingDeposits: Optional[float] = None
marginBalance: Optional[float] = None
shortBalance: Optional[float] = None
accountValue: Optional[float] = None


class MarginInitialBalance(MarginBalance):
accruedInterest: Optional[float] = None
availableFundsNonMarginableTrade: Optional[float] = None
bondValue: Optional[float] = None
cashBalance: Optional[float] = None
cashAvailableForTrading: Optional[float] = None
cashReceipts: Optional[float] = None
liquidationValue: Optional[float] = None


class Position(JSONSerializableBaseModel):
shortQuantity: Optional[float] = None
averagePrice: Optional[float] = None
currentDayProfitLoss: Optional[float] = None
currentDayProfitLossPercentage: Optional[float] = None
longQuantity: Optional[float] = None
settledLongQuantity: Optional[float] = None
settledShortQuantity: Optional[float] = None
agedQuantity: Optional[float] = None
instrument: Optional[Any] = None # TODO: AccountInstrument type
marketValue: Optional[float] = None
maintenanceRequirement: Optional[float] = None
averageLongPrice: Optional[float] = None
averageShortPrice: Optional[float] = None
taxLotAverageLongPrice: Optional[float] = None
taxLotAverageShortPrice: Optional[float] = None
longOpenProfitLoss: Optional[float] = None
shortOpenProfitLoss: Optional[float] = None
previousSessionLongQuantity: Optional[float] = None
previousSessionShortQuantity: Optional[float] = None
currentDayCost: Optional[float] = None


class Account(JSONSerializableBaseModel):
type_: Optional[AccountType] = Field(None, alias="type")
accountNumber: str
roundTrips: Optional[int] = 0
isDayTrader: Optional[bool] = False
isClosingOnlyRestricted: Optional[bool] = False
pfcbFlag: Optional[bool] = False
positions: List[Position] = []
initialBalances: Optional[MarginInitialBalance] = None
currentBalances: Optional[MarginBalance] = None
projectedBalances: Optional[MarginBalance] = None


class MarginAccount(Account):
type_: AccountType = Field(AccountType.MARGIN, alias="type")


class CashAccount(Account):
type_: AccountType = Field(AccountType.CASH, alias="type")


class SecuritiesAccount(JSONSerializableBaseModel):
securitiesAccount: Account
119 changes: 119 additions & 0 deletions tests/data/mock_schwab_api_resp.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,123 @@
{
"securities_account": [
{
"securitiesAccount": {
"accountNumber": "123",
"roundTrips": 0,
"isDayTrader": false,
"isClosingOnlyRestricted": false,
"pfcbFlag": false,
"positions": [
{
"shortQuantity": 0,
"averagePrice": 0,
"currentDayProfitLoss": 0,
"currentDayProfitLossPercentage": 0,
"longQuantity": 0,
"settledLongQuantity": 0,
"settledShortQuantity": 0,
"agedQuantity": 0,
"instrument": {
"cusip": "123",
"symbol": "spy",
"description": "sfsdfsadf",
"instrumentId": 0,
"netChange": 0,
"type": "SWEEP_VEHICLE"
},
"marketValue": 0,
"maintenanceRequirement": 0,
"averageLongPrice": 0,
"averageShortPrice": 0,
"taxLotAverageLongPrice": 0,
"taxLotAverageShortPrice": 0,
"longOpenProfitLoss": 0,
"shortOpenProfitLoss": 0,
"previousSessionLongQuantity": 0,
"previousSessionShortQuantity": 0,
"currentDayCost": 0
}
],
"initialBalances": {
"accruedInterest": 0,
"availableFundsNonMarginableTrade": 0,
"bondValue": 0,
"buyingPower": 0,
"cashBalance": 0,
"cashAvailableForTrading": 0,
"cashReceipts": 0,
"dayTradingBuyingPower": 0,
"dayTradingBuyingPowerCall": 0,
"dayTradingEquityCall": 0,
"equity": 0,
"equityPercentage": 0,
"liquidationValue": 0,
"longMarginValue": 0,
"longOptionMarketValue": 0,
"longStockValue": 0,
"maintenanceCall": 0,
"maintenanceRequirement": 0,
"margin": 0,
"marginEquity": 0,
"moneyMarketFund": 0,
"mutualFundValue": 0,
"regTCall": 0,
"shortMarginValue": 0,
"shortOptionMarketValue": 0,
"shortStockValue": 0,
"totalCash": 0,
"isInCall": 0,
"unsettledCash": 0,
"pendingDeposits": 0,
"marginBalance": 0,
"shortBalance": 0,
"accountValue": 0
},
"currentBalances": {
"availableFunds": 0,
"availableFundsNonMarginableTrade": 0,
"buyingPower": 0,
"buyingPowerNonMarginableTrade": 0,
"dayTradingBuyingPower": 0,
"dayTradingBuyingPowerCall": 0,
"equity": 0,
"equityPercentage": 0,
"longMarginValue": 0,
"maintenanceCall": 0,
"maintenanceRequirement": 0,
"marginBalance": 0,
"regTCall": 0,
"shortBalance": 0,
"shortMarginValue": 0,
"sma": 0,
"isInCall": 0,
"stockBuyingPower": 0,
"optionBuyingPower": 0
},
"projectedBalances": {
"availableFunds": 0,
"availableFundsNonMarginableTrade": 0,
"buyingPower": 0,
"buyingPowerNonMarginableTrade": 0,
"dayTradingBuyingPower": 0,
"dayTradingBuyingPowerCall": 0,
"equity": 0,
"equityPercentage": 0,
"longMarginValue": 0,
"maintenanceCall": 0,
"maintenanceRequirement": 0,
"marginBalance": 0,
"regTCall": 0,
"shortBalance": 0,
"shortMarginValue": 0,
"sma": 0,
"isInCall": 0,
"stockBuyingPower": 0,
"optionBuyingPower": 0
}
}
}
],
"account_numbers": [
{
"accountNumber": "123456789",
Expand Down
19 changes: 19 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
OptionContractType,
OptionContractStrategy,
)
from cschwabpy.models.trade_models import SecuritiesAccount, MarginAccount, CashAccount
from cschwabpy.models.token import Tokens, LocalTokenStore
from cschwabpy.SchwabAsyncClient import SchwabAsyncClient

Expand Down Expand Up @@ -50,6 +51,24 @@ def test_option_chain_parsing() -> None:
print(df.put_df.head(5))


def test_parsing_securities_account():
json_mock = get_mock_response()["securities_account"]
accounts: typing.List[SecuritiesAccount] = []
for sec_account in json_mock:
securities_account = SecuritiesAccount(**sec_account).securitiesAccount
accounts.append(securities_account)
assert securities_account is not None
assert securities_account.accountNumber == "123"
# assert securities_account.accountType == "MARGIN"
assert securities_account.isDayTrader == False
assert securities_account.roundTrips == 0
assert securities_account.positions is not None
assert len(securities_account.positions) == 1
assert securities_account.initialBalances is not None

assert len(accounts) == 1


@pytest.mark.asyncio
async def test_download_option_chain(httpx_mock: HTTPXMock):
mock_option_chain_resp = get_mock_response()
Expand Down