itsjafer / schwab-api

A python library for placing trades on Charles Schwab
MIT License
201 stars 64 forks source link

Selecting account for orders_v2 #19

Closed WinesProof closed 6 months ago

WinesProof commented 8 months ago

I was able to specify the account in orders_v2() by adding: self.headers["schwab-client-account"] = account_id

The bigger issue I'm having is that the response doesn't seem to populate the RemainingQty (it's always zero in my attempts), so I had to figure out a much more convoluted and probably unreliable way to figure out how my orders executed.

gyias commented 8 months ago

I ran into a similar issue where the account_info API only returns the first account's information. Not sure if these originate from the same root cause, though.

In my case, it turns out that the new login code that logs in directly to the trade ticket was the root cause. For some reason, the Schwab homepage returns only the selected account's info for "https://client.schwab.com/api/PositionV2/PositionsDataV2" requests if you log in directly to that page. The weird thing is that it works fine if you go to the summary page and then come back to the trade ticket page. I'm trying to circumvent it by taking advantage of the behavior, but wonder if there is a better solution?

I'll file a separate issue if you think this is not the same issue.

WinesProof commented 8 months ago

I think that's a separate issue since the current implementation of get_account_info() uses the "older" api at client.schwab.com, while orders_v2 uses the "new" api at ausgateway.schwab.com.

FWIW, I wrote my own method to retrieve account info from a "v2" api endpoint (ausgateway.schwab.com). It seemed to require some extra handling of the bearer token, and I don't have time to figure out child options with it. For anyone interested in carrying that forward, here's what I do:

    def update_token(self):
        r = self.session.get("https://client.schwab.com/api/auth/authorize/scope/api")
        token = json.loads(r.text)['token']
        self.headers['authorization'] = f"Bearer {token}"

    def get_account_info(self):
        account_info = dict()
        self.update_token()
        url = (
            "https://ausgateway.schwab.com/api/is.Holdings/V1/Holdings/Holdings?="
            "&includeCostBasis=true&includeRatings=true&includeUnderlyingOption=true")
        r = self.session.get(url, headers=self.headers)
        response = json.loads(r.text)
        for account in response['accounts']:
            positions = list()
            for security_group in account["groupedPositions"]:
                if security_group["groupName"] == "Cash":
                    continue
                for position in security_group["positions"]:
                    positions.append(
                        Position(
                            position["symbolDetail"]["symbol"],
                            position["symbolDetail"]["description"],
                            int(position["quantity"]),
                            float(position["costDetail"]["costBasisDetail"]["costBasis"]),
                            float(position["priceDetail"]["marketValue"])
                        )._as_dict()
                    )
            account_info[int(account["accountId"])] = Account(
                account["accountId"],
                positions,
                account["totals"]["marketValue"],
                account["totals"]["cashInvestments"],
                account["totals"]["accountValue"],
                account["totals"].get("costBasis", 0),
            )._as_dict()

        return account_info
itsjafer commented 8 months ago

Hi @WinesProof, I've adopted the code above as a function in v0.3.3 and added an optional account_id parameter to orders_v2().

Strongly appreciate you paving the way here!

NelsonDane commented 8 months ago

I would also like to add that I am also seeing only 1 account being returned with the get_account_info() function. When running on 0.2.3 it returns all of my accounts, but when I run it on 0.3.0 and up it only returns 1 account.

gyias commented 8 months ago

I would also like to add that I am also seeing only 1 account being returned with the get_account_info() function. When running on 0.2.3 it returns all of my accounts, but when I run it on 0.3.0 and up it only returns 1 account.

Upgrading to the latest version and then switching to get_account_info_v2 worked for me.

NelsonDane commented 8 months ago

Perfect, that worked for me!

cnMuggle commented 8 months ago

I see the same issue at 0.3.5 (from 0.2.3) of coming back only 1 account using get_account_info() but must use get_account_info_v2() to see all my accounts. @itsjafer I was wondering does that make sense to copy the current login() to login_v2() for all the v2 type login and then copy the legacy login() from 0.2.3 to latest in the authentication.py for best backward compatibility?

I have tested it on my side and can do the add but it will create extra work for who has modified based this thread or using the latest library. So would like to know people's opinions.

itsjafer commented 8 months ago

Indeed, in hindsight, I should've created an entirely new login function to avoid backwards compatibility issues. I'm considering a legacy parameter taken by login that avoids the navigation for now, so that those using the v2 functions aren't inconvenienced since that seems to be the path forward with future functionality

QingqiLei commented 8 months ago

I ran into a similar issue where the account_info API only returns the first account's information. Not sure if these originate from the same root cause, though.

In my case, it turns out that the new login code that logs in directly to the trade ticket was the root cause. For some reason, the Schwab homepage returns only the selected account's info for "https://client.schwab.com/api/PositionV2/PositionsDataV2" requests if you log in directly to that page. The weird thing is that it works fine if you go to the summary page and then come back to the trade ticket page. I'm trying to circumvent it by taking advantage of the behavior, but wonder if there is a better solution?

I'll file a separate issue if you think this is not the same issue.

Hi, I also get info for the selected account, not all accounts.

What do you do to go to the summary page and then come back to the trade ticket page? I tried add self.session.get(urls.account_summary()) before the position request. But still returns one account info.

itsjafer commented 8 months ago

I ran into a similar issue where the account_info API only returns the first account's information. Not sure if these originate from the same root cause, though. In my case, it turns out that the new login code that logs in directly to the trade ticket was the root cause. For some reason, the Schwab homepage returns only the selected account's info for "https://client.schwab.com/api/PositionV2/PositionsDataV2" requests if you log in directly to that page. The weird thing is that it works fine if you go to the summary page and then come back to the trade ticket page. I'm trying to circumvent it by taking advantage of the behavior, but wonder if there is a better solution? I'll file a separate issue if you think this is not the same issue.

Hi, I also get info for the selected account, not all accounts.

What do you do to go to the summary page and then come back to the trade ticket page? I tried add self.session.get(urls.account_summary()) before the position request. But still returns one account info.

Hi @QingqiLei could you try v0.3.6? I've merged in https://github.com/itsjafer/schwab-api/pull/32 which might solve the problem for v1 methods

itsjafer commented 6 months ago

Closing this, because choosing an account for orders_v2 appears to be working now.