Skip to content

Commit

Permalink
Working on #469
Browse files Browse the repository at this point in the history
Still need to fix the stock message + worth values
  • Loading branch information
StephanAkkerman committed Dec 23, 2023
1 parent 11ff79c commit 65164c9
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 111 deletions.
159 changes: 93 additions & 66 deletions src/cogs/loops/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,9 @@ class Assets(commands.Cog):
You can enabled / disable it in config under ["LOOPS"]["ASSETS"].
"""

def __init__(
self, bot: commands.Bot, db: pd.DataFrame = util.vars.portfolio_db
) -> None:
def __init__(self, bot: commands.Bot) -> None:
self.bot = bot

# Refresh assets
asyncio.create_task(self.assets(db))
self.assets.start()

async def usd_value(self, asset: str, exchange: str) -> tuple[float, str]:
"""
Expand Down Expand Up @@ -67,7 +63,8 @@ async def usd_value(self, asset: str, exchange: str) -> tuple[float, str]:

return usd_val, change

async def assets(self, portfolio_db: pd.DataFrame) -> None:
@loop(hours=1)
async def assets(self) -> None:
"""
Only do this function at startup and if a new portfolio has been added.
Checks the account balances of accounts saved in portfolio db, then updates the assets db.
Expand All @@ -81,45 +78,74 @@ async def assets(self, portfolio_db: pd.DataFrame) -> None:
-------
None
"""

if portfolio_db.equals(util.vars.portfolio_db):
# Drop all crypto assets
if not util.vars.assets_db.empty:
crypto_rows = util.vars.assets_db.index[
util.vars.assets_db["exchange"] != "stock"
].tolist()
assets_db = util.vars.assets_db.drop(index=crypto_rows)
else:
assets_db = pd.DataFrame(
columns=["asset", "buying_price", "owned", "exchange", "id", "user"]
)
assets_db_columns = {
"asset": str,
"buying_price": float,
"owned": float,
"exchange": str,
"id": np.int64,
"user": str,
"worth": float,
"price": float,
"change": float,
}

if util.vars.portfolio_db.empty:
print("No portfolios in the database.")
return

# Drop all crypto assets, so we can update them
if not util.vars.assets_db.empty:
crypto_rows = util.vars.assets_db.index[
util.vars.assets_db["exchange"] != "stock"
].tolist()
assets_db = util.vars.assets_db.drop(index=crypto_rows)
else:
# Add it to the old assets db, since this call is for a specific person
assets_db = util.vars.assets_db
# Create a new database
assets_db = pd.DataFrame(columns=list(assets_db_columns.keys()))

if not portfolio_db.empty:
for _, row in portfolio_db.iterrows():
# Add this data to the assets.db database
exch_data = await get_data(row)
assets_db = pd.concat([assets_db, exch_data], ignore_index=True)
# Get the assets of each user
for _, row in util.vars.portfolio_db.iterrows():
# Add this data to the assets db
exch_data = await get_data(row)
assets_db = pd.concat([assets_db, exch_data], ignore_index=True)

# Ensure that the db knows the right types
assets_db = assets_db.astype(
{
"asset": str,
"buying_price": float,
"owned": float,
"exchange": str,
"id": np.int64,
"user": str,
}
)
assets_db = assets_db.astype(assets_db_columns)

# Update the assets db
update_db(assets_db, "assets")
util.vars.assets_db = assets_db

self.post_assets.start()
# Post the assets
await self.post_assets()

async def update_prices_and_changes(self, new_df):
# Filter DataFrame to only include rows where exchange is "Stock"
print(new_df)
stock_df = new_df[new_df["exchange"] == "Stock"]

# Asynchronously get price and change for each asset
async def get_price_change(row):
price, change = await self.usd_value(row["asset"], row["exchange"])
return {
"price": 0 if price is None else round(price, 2),
"change": 0 if change is None else change,
}

# Using asyncio.gather to run all async operations concurrently
results = await asyncio.gather(
*(get_price_change(row) for _, row in stock_df.iterrows())
)
print(stock_df)
print(results)

# Update the DataFrame with the results
for i, (index, row) in enumerate(stock_df.iterrows()):
new_df.at[index, "price"] = results[i]["price"]
new_df.at[index, "change"] = results[i]["change"]

return new_df

async def format_exchange(
self,
Expand Down Expand Up @@ -152,40 +178,38 @@ async def format_exchange(
# Necessary to prevent panda warnings
new_df = exchange_df.copy()

# Get the price of the assets
prices = []
changes = []
for _, row in new_df.iterrows():
price, change = await self.usd_value(row["asset"], exchange)
# Usage
new_df = await self.update_prices_and_changes(new_df)

if price is None:
price = 0
if change is None:
change = 0

prices.append(round(price, 2))
# Add without emoji
changes.append(change)
# Set the types (again)
new_df = new_df.astype(
{
"asset": str,
"buying_price": float,
"owned": float,
"exchange": str,
"id": np.int64,
"user": str,
"worth": float,
"price": float,
"change": float,
}
)

new_df["price"] = prices
new_df["change"] = changes
# Format the price change
new_df["change"] = new_df["change"].apply(lambda x: format_change(x))

# Format price and change
new_df["price_change"] = (
"$" + new_df["price"].astype(str) + " (" + new_df["change"] + ")"
"$"
+ new_df["price"].astype(str)
+ " ("
+ new_df["change"].astype(str)
+ ")"
)

# Ensure that 'owned' column is of a numeric type
new_df["owned"] = pd.to_numeric(new_df["owned"], errors="coerce")

# Calculate the most recent worth
new_df["worth"] = pd.Series(prices) * new_df["owned"]

# Round it to 2 decimals
new_df = new_df.round({"worth": 2})

# Drop it if it's worth less than 1$
new_df = new_df.drop(new_df[new_df.worth < 1].index)
# Fill NaN values of worth
new_df["worth"] = new_df["worth"].fillna(0)

# Set buying price to float
new_df["buying_price"] = new_df["buying_price"].astype(float)
Expand All @@ -207,7 +231,11 @@ async def format_exchange(
new_df = new_df.sort_values(by=["worth"], ascending=False)

new_df["worth"] = (
"$" + new_df["worth"].astype(str) + " (" + new_df["worth_change"] + ")"
"$"
+ new_df["worth"].astype(str)
+ " ("
+ new_df["worth_change"].astype(str)
+ ")"
)

# Create the list of string values
Expand All @@ -229,7 +257,6 @@ async def format_exchange(

return e

@loop(hours=1)
async def post_assets(self) -> None:
"""
Posts the assets of the users that added their portfolio.
Expand Down
1 change: 1 addition & 0 deletions src/cogs/loops/listings.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ async def new_listings(self) -> None:
new_symbols = await self.get_symbols(exchange)

new_listings = []
delistings = []

if self.old_symbols[exchange] == []:
await self.set_old_symbols()
Expand Down
7 changes: 5 additions & 2 deletions src/util/cg_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,17 @@ def get_crypto_info(ids):
best_vol = volume
id = symbol
coin_dict = coin_info
except Exception:
except Exception as e:
print("Error getting coin info for", symbol, "Error:", e)
pass

else:
id = ids.values[0]
# Try in case the CoinGecko API does not work
try:
coin_dict = cg.get_coin_by_id(id)
except Exception:
except Exception as e:
print("Error getting coin info for", id, "Error:", e)
return None, None

return coin_dict, id
Expand Down Expand Up @@ -151,6 +153,7 @@ async def get_coin_info(
coin_dict = None
if ticker in util.vars.cg_db["symbol"].values:
# Check coin by symbol, i.e. "BTC"
print(ticker)
coin_dict, id = get_crypto_info(
util.vars.cg_db[util.vars.cg_db["symbol"] == ticker]["id"]
)
Expand Down
30 changes: 24 additions & 6 deletions src/util/exchange_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ async def get_data(row) -> pd.DataFrame:
owned = []

for symbol, amount in balances.items():
usd_val = await get_usd_price(exchange, symbol)
usd_val, percentage = await get_usd_price(exchange, symbol)
worth = amount * usd_val

# Add price change

if worth < 5:
continue

Expand All @@ -45,11 +47,15 @@ async def get_data(row) -> pd.DataFrame:
"exchange": exchange.id,
"id": row["id"],
"user": row["user"],
"worth": round(worth, 2),
"price": usd_val,
"change": percentage,
}
)

df = pd.DataFrame(owned)

# Se tthe types
if not df.empty:
df = df.astype(
{
Expand All @@ -59,6 +65,9 @@ async def get_data(row) -> pd.DataFrame:
"exchange": str,
"id": np.int64,
"user": str,
"worth": float,
"price": float,
"change": float,
}
)

Expand All @@ -81,17 +90,23 @@ async def get_balance(exchange) -> dict:
return {}


async def get_usd_price(exchange, symbol) -> float:
async def get_usd_price(exchange, symbol: str) -> tuple[float, float]:
"""
Returns the price of the symbol in USD
Symbol must be in the format 'BTC/USDT'
"""
exchange_price = 0
exchange_change = 0

if symbol not in stables:
for usd in stables:
try:
price = await exchange.fetchTicker(f"{symbol}/{usd}")
if price != 0:
return float(price["last"])
if "last" in price:
exchange_price = float(price["last"])
if "percentage" in price:
exchange_change = float(price["percentage"])
except ccxt.BadSymbol:
continue
except ccxt.ExchangeError as e:
Expand All @@ -101,11 +116,14 @@ async def get_usd_price(exchange, symbol) -> float:
else:
try:
price = await exchange.fetchTicker(symbol + "/DAI")
return float(price["last"])
if "last" in price:
exchange_price = float(price["last"])
if "percentage" in price:
exchange_change = float(price["percentage"])
except ccxt.BadSymbol:
return 1
return 1, 0

return 0
return exchange_price, exchange_change


async def get_buying_price(exchange, symbol, full_sym: bool = False) -> float:
Expand Down
Loading

0 comments on commit 65164c9

Please sign in to comment.