Skip to content

Commit

Permalink
[v0.0.3] Updated Revoke; Added debug hooks
Browse files Browse the repository at this point in the history
  - Exchange: Updated revoke params for SIWC changelog
  - Exchange: Verified meta on transactions:send
  - Exchange: Added `dbg` call to show token
  - Exchange: Added `tick` call to show expiration
  - PyExch / DCA / Results: Added debug hooks
  - DCA: Added a wait for deposits to show on balance
  - DCA: No longer do market orders at min_size
  - Results: See try/except for date bug in API
  - Results: Add calc to compare to DCA better
  - PKG: Added setup.cfg

  ### Todo:

  - [ ] BugRpt try/except in Results
  - [ ] BugRpt meta on trans:send undocumented
  - [ ] BugRpt login URL in SIWC revoke docs
  - [ ] BugRpt revoke docs not fully updated
  • Loading branch information
brianddk committed Apr 2, 2024
1 parent a37c3cd commit b52d941
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 54 deletions.
108 changes: 64 additions & 44 deletions examples/coinbase_dca.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@

HOUR = 60 * 60 # one-hr in seconds

TAKER = 0 - 0.000001 # do buys 1% above market (taker action)
DEPOSIT = True
CANCEL_OPEN = True
TAKER = 0.01 # do some taker action
SPREAD = 0.05 # do buys 1% above to 5% below (peanut butter spread)
THRESHOLD = 0.95 # Percent of holds to clear before starting
MKRFEE = 0.0060 # fee for maker limit orders
TKRFEE = 0.0080 # fee for taker limit orders
DCAUSD = 10.00 # USD to deposit on our DCAs
DEPOSIT = True
CANCEL_OPEN = True
DEPSOIT_DELAY = 12 * HOUR # If we've deposited in the last 12hrs, skip

MAXCNT = 500 # 500 The maximum number of orders allowed
Expand All @@ -31,6 +32,7 @@

def main():
current = datetime.now().astimezone(timezone.utc)
dcausd = DCAUSD + current.day / 100 # set pennies to day of month

cbv3 = Exchange.create("keystore.json", "coinbase.v3_api")
cbv3.keystore.close() # Trezor devices should only have one handle at a time
Expand All @@ -39,26 +41,6 @@ def main():
account_id = cboa.keystore.get("coinbase.state.usd_wallet")
pmt_method_id = cboa.keystore.get("coinbase.state.ach_payment")

if CANCEL_OPEN:
# Get outstanding orders to cancel
#
resp = cbv3.v3_client.list_orders(order_status=["OPEN"])
params = []
for order in resp["orders"]:
if (
order["status"] == "OPEN"
and order["product_id"] == PRODID
and order["side"] == "BUY"
):
params += [order["order_id"]]

# Cancel the outstanding orders
#
if params:
sublist = [params[i : i + MAXCAN] for i in range(0, len(params), MAXCAN)]
for params in sublist:
resp = cbv3.v3_client.cancel_orders(order_ids=params)

# Get my account_id of WALTID (USD Wallet)
#
if not account_id:
Expand All @@ -75,6 +57,12 @@ def main():
cboa.keystore.save()
# print(f"DBG: account['name:{WALTID}']:", account_id)

# Determine 90% funding for waitclock
#
balance = get_balance(cbv3, account_id)
hold = float(cbv3._response["account"]["hold"]["value"])
target = THRESHOLD * (hold + dcausd) + balance

if DEPOSIT:
# Check to see if we've deposited today
#
Expand Down Expand Up @@ -115,7 +103,6 @@ def main():
cboa.keystore.save()
# print(f"DBG: payment_method['type:{PMTTYP}']:", pmt_method_id)

dcausd = DCAUSD + current.day / 100 # set pennies to day of month
# Make the deposit
#
resp = cboa.oa2_client.deposit(
Expand All @@ -129,18 +116,25 @@ def main():
)
# print(f"DBG: deposit['amt:{dcausd}']:", dumps(data_toDict(resp)))

sleep(3) # There really is a settle time from cancel to avail balance... insane.

# Get my available balance of WALTID (USD Wallet)
#
resp = cbv3.v3_client.get_account(account_id)
if (
resp["account"]["available_balance"]["currency"] == "USD"
and resp["account"]["name"] == WALTID
):
balance = float(resp["account"]["available_balance"]["value"])
if CANCEL_OPEN:
# Get outstanding orders to cancel
#
resp = cbv3.v3_client.list_orders(order_status=["OPEN"])
params = []
for order in resp["orders"]:
if (
order["status"] == "OPEN"
and order["product_id"] == PRODID
and order["side"] == "BUY"
):
params += [order["order_id"]]

assert account_id and balance
# Cancel the outstanding orders
#
if params:
sublist = [params[i : i + MAXCAN] for i in range(0, len(params), MAXCAN)]
for params in sublist:
resp = cbv3.v3_client.cancel_orders(order_ids=params)

# Get today's min_size and price for PRODID (BTC-USD)
#
Expand All @@ -149,11 +143,21 @@ def main():
min_size = float(product["base_min_size"])
spot = float(product["price"])

assert account_id and balance and min_size and spot
assert account_id and min_size and spot

# Get my available balance of WALTID (USD Wallet)
#
adjust = 0.0 if DEPOSIT and need_deposit else THRESHOLD * dcausd
target -= adjust
while abs(balance - target) > 1 and balance < target:
sleep(1)
print(f"Waiting: balance={balance:.2f}, target={target:.2f}")
balance = get_balance(cbv3, account_id)

# "Peanut Butter Spread" the buys as small as possible from spot down to SPREAD (5%) below.
#

# price_hi = 66_857.20
price_hi = spot * (1 + TAKER)
price_lo = price_hi * (1 - SPREAD)
price_av = (price_hi + price_lo) / 2
Expand All @@ -176,24 +180,39 @@ def main():
if resp["order"]["status"] == "FILLED":
cost = float(resp["order"]["total_value_after_fees"])
xprice = float(resp["order"]["average_filled_price"])
balance -= cost
# balance -= cost
cbal = balance - cost
balance = get_balance(cbv3, account_id)
print(
f"{count} Limit buy of {params['base_size']} btc at {xprice:.2f}, at a cost of {cost:.2f}, leaving balance of {balance:.2f}"
f"{count} Limit buy of {params['base_size']} btc at {xprice:.2f}, at a cost of {cost:.2f}, leaving balance of {balance:.2f} ({cbal:.2f})"
)
price -= step
assert price and step and cost and balance

return cboa


def get_balance(cbv3, account_id):
resp = cbv3._response = cbv3.v3_client.get_account(account_id)
if (
resp["account"]["available_balance"]["currency"] == "USD"
and resp["account"]["name"] == WALTID
):
balance = float(resp["account"]["available_balance"]["value"])

assert account_id and balance
return balance


def mk_order(cbv3, params, min_size):
retry = 3
for i in range(retry):
resp = cbv3.v3_client.limit_order_gtc_buy(**params)
if resp["success"]:
break
if resp["error_response"]["error"] == "INVALID_LIMIT_PRICE_POST_ONLY":
params.update(dict(post_only=False, base_size=f"{min_size:.8f}"))
# params.update(dict(post_only=False, base_size=f"{min_size:.8f}"))
params.update(dict(post_only=False))
for j in range(retry):
resp = cbv3.v3_client.limit_order_gtc_buy(**params)
if resp["success"]:
Expand Down Expand Up @@ -228,8 +247,9 @@ def mk_size(price, count, step, balance, spot, min_size):


if __name__ == "__main__":
# try:
cboa = main()
# except Exception as e:
# ex = e
# print(ex)
# main()
try:
cboa = main()
except Exception as e:
ex = e
breakpoint()
33 changes: 27 additions & 6 deletions examples/coinbase_resluts.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def main():
start=str(earlier),
end=str(now),
)
spot = float(candles["candles"][0]["close"]) * (1 + TAKER)

date_candle = dict()
for candle in candles["candles"]:
Expand All @@ -53,9 +54,14 @@ def main():
for order in fills["fills"]:
if order["side"] != "BUY" or order["trade_type"] != "FILL":
break
dt = datetime.strptime(order["trade_time"], "%Y-%m-%dT%H:%M:%S.%fZ").replace(
tzinfo=timezone.utc
)
try:
dt = datetime.strptime(
order["trade_time"], "%Y-%m-%dT%H:%M:%S.%fZ"
).replace(tzinfo=timezone.utc)
except ValueError:
dt = datetime.strptime(order["trade_time"], "%Y-%m-%dT%H:%M:%SZ").replace(
tzinfo=timezone.utc
)
key = dt.strftime("%Y-%m-%d")
# date_fill[DATE][FILLS] = list()
# date_fill[DATE][CANDEL] = candle
Expand All @@ -68,7 +74,7 @@ def main():
assert candle, "Every date should match"
date_fill[key]["candle"] = candle

avg_usd = ord_usd = ord_btc = 0
buc_btc = avg_usd = ord_usd = ord_btc = 0
for key, value in date_fill.items():
usd = btc = 0
for order in value["fills"]:
Expand All @@ -92,13 +98,28 @@ def main():
ord_usd += usd
ord_btc += btc
avg_usd += avg_price * btc
buc_btc += 1 / avg_price

count = len(date_fill.keys())
saved_usd = avg_usd - ord_usd
saved_pct = saved_usd / avg_usd * 100
saved_dca = (ord_btc - (buc_btc * ord_usd / count)) / ord_btc * 100

print(
f"Total: Bought {ord_btc:.8f} BTC for {ord_usd/ord_btc:.2f} instead of {avg_usd/ord_btc:.2f}"
)
print(
f" Saving {saved_pct:.4f}% (${saved_usd:.2f}) vs buy-on-avg, and {saved_dca:.4f}% vs dca-market-buy"
)
print(
f"Total: Bought {ord_btc:.8f} BTC for {ord_usd/ord_btc:.2f} instead of {avg_usd/ord_btc:.2f}, saving ${saved_usd:.2f} ({saved_pct:.2f}%)"
f" Saving {100-(ord_usd/ord_btc)/spot*100:.2f}% vs one-time market buy at current price of: {spot:.2f}"
)


if __name__ == "__main__":
main()
# main()
try:
main()
except Exception as e:
ex = e
breakpoint()
18 changes: 18 additions & 0 deletions pyexch/exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,20 @@ def my_ipv6(self):
def new_uuid(self):
return uuid4()

def dbg(self):
token = self.keystore.get("coinbase.oauth2.token")
return f"DBG: {token[:4]}...{token[-4:]}"

def tick(self):
import time

expiration = self.keystore.get("coinbase.oauth2.expiration")
left = expiration - time.time()
hrs = left // (60 * 60)
min = (left - hrs * 60 * 60) // 60
sec = left - hrs * 60 * 60 - min * 60
return f"DBG: countdown {int(hrs):02}:{int(min):02}:{sec:0>02.2f}"


class Coinbase(Exchange):
def __init__(self, keystore):
Expand Down Expand Up @@ -303,6 +317,8 @@ def oa2_auth(self):
)
# rule https://forums.coinbasecloud.dev/t/walletsend-is-limited-1-00-day-per-user/866/2
# broke https://forums.coinbasecloud.dev/t/oauth-application-maximum-of-1-00-per-month/7096/13
# Confirmed this meta tags allow to set the send limit. Next is to try a send with CB-2FA-TOKEN
# header: https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/sign-in-with-coinbase-2fa
if "wallet:transactions:send" in self.keystore.get("coinbase.oauth2.scope"):
self._params.update(
{
Expand Down Expand Up @@ -387,6 +403,8 @@ def oa2_revoke(self):
uri = self.keystore.get("coinbase.oauth2.revoke_url")
self._params = dict(
token=self.keystore.get("coinbase.oauth2.token"),
client_id=self.keystore.get("coinbase.oauth2.id"),
client_secret=self.keystore.get("coinbase.oauth2.secret"),
)
self._response = requests.post(uri, data=self._params)

Expand Down
12 changes: 9 additions & 3 deletions pyexch/pyexch.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def main():
with open(args.params, "r") as pj:
params = load(pj)

internals = ["my_ipv4", "my_ipv6", "new_uuid"]
internals = ["my_ipv4", "my_ipv6", "new_uuid", "dbg", "tick"]

ex = Exchange.create(args.keystore, args.auth)

Expand Down Expand Up @@ -125,8 +125,14 @@ def main():
if resp:
print(dumps(resp, indent=2))
elif ex._response is not None:
print("[ERROR] Last Response:", ex._response, file=stderr)
print("Last Response:", ex._response, file=stderr)


if __name__ == "__main__":
main()
# main()
# Debug check
try:
main()
except Exception as e:
ex = e
breakpoint()
29 changes: 29 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# migrate to pyproject.toml
# black config in .black, run with `black --config .black .`
#
[black]
line-length = 88
target-version = ['py38']

# Run with `flake8`
#
[flake8]
max-line-length = 88
filename =
./examples/*.py,
./pyexch/*.py,
ignore =
# E501: line too long
E501,
# W503 line break before binary operator
W503,
# E203 whitespace before ':'
E203,

# Run with `isort .`
#
[isort]
py_version = 38
profile = black
src_paths = pyexch, examples

3 changes: 2 additions & 1 deletion todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
- [x] Paginate fills on get_fills in DCA results
- [ ] BugRpt that max_size has dropped to 0.00000001
- [ ] BugRpt that retries are req to pull committed orders (see try in DCA)
- [ ] Test Send Transaction with [CB-2FA-TOKEN][m] console prompting
- [ ] Convert from setup.py to pyproject.toml
- [ ] Add [AES encryption][h], or port samples to [CryptoDomeX][i]
- [ ] Cleaner update of UID across all my GPG keys
Expand Down Expand Up @@ -67,7 +68,7 @@
[j]: #publish-to-github
[k]: https://docs.readthedocs.io/en/stable/tutorial/index.html (RTD Tutorial)
[l]: #publish-to-pypi
[m]:
[m]: https://docs.cloud.coinbase.com/sign-in-with-coinbase/docs/sign-in-with-coinbase-2fa
[n]:
[o]:
[p]:
Expand Down

0 comments on commit b52d941

Please sign in to comment.