Skip to content

Commit

Permalink
account and positions
Browse files Browse the repository at this point in the history
  • Loading branch information
rcholic committed Jun 7, 2024
1 parent 8204e9e commit 5d9fed5
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 0 deletions.
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

0 comments on commit 5d9fed5

Please sign in to comment.