[go: nahoru, domu]

Skip to content

Commit

Permalink
Merge pull request hummingbot#2831 from CoinAlpha/development
Browse files Browse the repository at this point in the history
(release 2/3) v0.35.0 - merge development to master
  • Loading branch information
fengtality committed Jan 12, 2021
2 parents 069624e + c38ebe4 commit c314a9d
Show file tree
Hide file tree
Showing 25 changed files with 201 additions and 130 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ OKEx is reportedly being investigated by Chinese authorities and has stopped wit
## Supported protocol exchanges
| logo | id | name | ver | doc| status |
|:---:|:---:|:---:|:---:|:---:|:--:|
| <img src="assets/celo_logo.svg" alt="Celo" width="90" /> | celo | [Celo](https://terra.money/) | * | [SDK](https://celo.org/developers) | ![GREEN](https://via.placeholder.com/15/008000/?text=+) |
| <img src="assets/celo_logo.svg" alt="Celo" width="90" /> | celo | [Celo](https://celo.org/) | * | [SDK](https://celo.org/developers) | ![GREEN](https://via.placeholder.com/15/008000/?text=+) |
| <img src="assets/balancer_logo.svg" alt="Balancer" width="90" height="30" /> | balancer | [Balancer](https://balancer.finance/) | * | [SDK](https://docs.balancer.finance/) | ![YELLOW](https://via.placeholder.com/15/ffff00/?text=+) |
| <img src="assets/terra_logo.png" alt="Terra" width="90" /> | terra | [Terra](https://terra.money/) | * | [SDK](https://docs.terra.money/) | ![YELLOW](https://via.placeholder.com/15/ffff00/?text=+) |
| <img src="assets/uniswap_logo.svg" alt="Uniswap" width="90" height="30" /> | uniswap | [Uniswap](https://uniswap.org/) | * | [SDK](https://uniswap.org/docs/v2/) | ![YELLOW](https://via.placeholder.com/15/ffff00/?text=+) |
Expand Down Expand Up @@ -98,4 +98,4 @@ Hummingbot was created and is maintained by CoinAlpha, Inc. We are [a global tea
## Legal

- **License**: Hummingbot is licensed under [Apache 2.0](./LICENSE).
- **Data collection**: read important information regarding [Hummingbot Data Collection](DATA_COLLECTION.md).
- **Data collection**: read important information regarding [Hummingbot Data Collection](DATA_COLLECTION.md).
2 changes: 1 addition & 1 deletion hummingbot/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.34.0
0.35.0
9 changes: 6 additions & 3 deletions hummingbot/client/command/balance_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ def balance(self,
amount = float(args[2])
if exchange not in config_var.value or config_var.value[exchange] is None:
config_var.value[exchange] = {}
config_var.value[exchange][asset] = amount

self._notify(f"Limit for {asset} on {exchange} exchange set to {amount}")
if amount < 0 and asset in config_var.value[exchange].keys():
config_var.value[exchange].pop(asset)
self._notify(f"Limit for {asset} on {exchange} exchange removed.")
elif amount >= 0:
config_var.value[exchange][asset] = amount
self._notify(f"Limit for {asset} on {exchange} exchange set to {amount}")
save_to_yml(file_path, config_map)

elif option == "paper":
Expand Down
1 change: 0 additions & 1 deletion hummingbot/client/command/create_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ async def prompt_for_configuration(self, # type: HummingbotApplication
self.app.hide_input = False
if await self.status_check_all():
self._notify("\nEnter \"start\" to start market making.")
self.app.set_text("start")

async def prompt_a_config(self, # type: HummingbotApplication
config: ConfigVar,
Expand Down
25 changes: 15 additions & 10 deletions hummingbot/client/command/history_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
ConnectorType
)
from hummingbot.model.trade_fill import TradeFill
from hummingbot.core.utils.market_price import get_last_price
from hummingbot.user.user_balances import UserBalances
from hummingbot.core.utils.async_utils import safe_ensure_future
from hummingbot.client.performance import PerformanceMetrics, calculate_performance_metrics, smart_round
Expand Down Expand Up @@ -72,8 +71,7 @@ async def history_report(self, # type: HummingbotApplication
for market, symbol in market_info:
cur_trades = [t for t in trades if t.market == market and t.symbol == symbol]
cur_balances = await self.get_current_balances(market)
cur_price = await get_last_price(market.replace("_PaperTrade", ""), symbol)
perf = calculate_performance_metrics(symbol, cur_trades, cur_balances, cur_price)
perf = await calculate_performance_metrics(market, symbol, cur_trades, cur_balances)
if display_report:
self.report_performance_by_market(market, symbol, perf, precision)
return_pcts.append(perf.return_pct)
Expand All @@ -88,6 +86,8 @@ async def get_current_balances(self, # type: HummingbotApplication
return self.markets[market].get_all_balances()
elif "Paper" in market:
paper_balances = global_config_map["paper_trade_account_balance"].value
if paper_balances is None:
return {}
return {token: Decimal(str(bal)) for token, bal in paper_balances.items()}
else:
gateway_eth_connectors = [cs.name for cs in CONNECTOR_SETTINGS.values() if cs.use_ethereum_wallet and
Expand Down Expand Up @@ -150,9 +150,9 @@ def report_performance_by_market(self, # type: HummingbotApplication
smart_round(perf.cur_quote_bal, precision),
smart_round(perf.tot_vol_quote, precision)],
[f"{trading_pair + ' price':<17}",
perf.start_price,
perf.cur_price,
perf.cur_price - perf.start_price],
smart_round(perf.start_price),
smart_round(perf.cur_price),
smart_round(perf.cur_price - perf.start_price)],
[f"{'Base asset %':<17}",
f"{perf.start_base_ratio_pct:.2%}",
f"{perf.cur_base_ratio_pct:.2%}",
Expand All @@ -164,11 +164,16 @@ def report_performance_by_market(self, # type: HummingbotApplication
perf_data = [
["Hold portfolio value ", f"{smart_round(perf.hold_value, precision)} {quote}"],
["Current portfolio value ", f"{smart_round(perf.cur_value, precision)} {quote}"],
["Trade P&L ", f"{smart_round(perf.trade_pnl, precision)} {quote}"],
["Fees paid ", f"{smart_round(perf.fee_paid, precision)} {perf.fee_token}"],
["Total P&L ", f"{smart_round(perf.total_pnl, precision)} {quote}"],
["Return % ", f"{perf.return_pct:.2%}"],
["Trade P&L ", f"{smart_round(perf.trade_pnl, precision)} {quote}"]
]
perf_data.extend(
["Fees paid ", f"{smart_round(fee_amount, precision)} {fee_token}"]
for fee_token, fee_amount in perf.fees.items()
)
perf_data.extend(
[["Total P&L ", f"{smart_round(perf.total_pnl, precision)} {quote}"],
["Return % ", f"{perf.return_pct:.2%}"]]
)
perf_df: pd.DataFrame = pd.DataFrame(data=perf_data)
lines.extend(["", " Performance:"] +
[" " + line for line in perf_df.to_string(index=False, header=False).split("\n")])
Expand Down
1 change: 0 additions & 1 deletion hummingbot/client/command/import_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ async def import_config_file(self, # type: HummingbotApplication
self.app.change_prompt(prompt=">>> ")
if await self.status_check_all():
self._notify("\nEnter \"start\" to start market making.")
self.app.set_text("start")

async def prompt_a_file_name(self # type: HummingbotApplication
):
Expand Down
9 changes: 3 additions & 6 deletions hummingbot/client/command/pnl_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from hummingbot.core.data_type.trade import Trade
from hummingbot.core.data_type.common import OpenOrder
from hummingbot.client.performance import calculate_performance_metrics
from hummingbot.core.utils.market_price import get_last_price
from hummingbot.client.command.history_command import get_timestamp
from hummingbot.client.config.global_config_map import global_config_map

Expand Down Expand Up @@ -58,8 +57,7 @@ async def pnl_report(self, # type: HummingbotApplication
if market is not None:
market = market.upper()
trades: List[Trade] = await connector.get_my_trades(market, days)
cur_price = await get_last_price(exchange, market)
perf = calculate_performance_metrics(market, trades, cur_balances, cur_price)
perf = await calculate_performance_metrics(exchange, market, trades, cur_balances)
self.report_performance_by_market(exchange, market, perf, precision=None)
return
self._notify(f"Starting: {datetime.fromtimestamp(get_timestamp(days)).strftime('%Y-%m-%d %H:%M:%S')}"
Expand All @@ -78,10 +76,9 @@ async def pnl_report(self, # type: HummingbotApplication
trades: List[Trade] = await connector.get_my_trades(market, days)
if not trades:
continue
cur_price = await get_last_price(exchange, market)
perf = calculate_performance_metrics(market, trades, cur_balances, cur_price)
perf = await calculate_performance_metrics(exchange, market, trades, cur_balances)
volume = await usd_value(quote, abs(perf.b_vol_quote) + abs(perf.s_vol_quote))
fee = await usd_value(perf.fee_token, perf.fee_paid)
fee = await usd_value(quote, perf.fee_in_quote)
pnl = await usd_value(quote, perf.total_pnl)
data.append([market, round(volume, 2), round(fee, 2), round(pnl, 2), f"{perf.return_pct:.2%}"])
if not data:
Expand Down
2 changes: 1 addition & 1 deletion hummingbot/client/hummingbot_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def _initialize_markets(self, market_names: List[Tuple[str, List[str]]]):
conn_setting = CONNECTOR_SETTINGS[connector_name]
if global_config_map.get("paper_trade_enabled").value and conn_setting.type == ConnectorType.Exchange:
try:
connector = create_paper_trade_market(market_name, trading_pairs)
connector = create_paper_trade_market(connector_name, trading_pairs)
except Exception:
raise
paper_trade_account_balance = global_config_map.get("paper_trade_account_balance").value
Expand Down
80 changes: 54 additions & 26 deletions hummingbot/client/performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
from typing import (
Dict,
Optional,
List
List,
Any
)
from hummingbot.model.trade_fill import TradeFill
from hummingbot.core.event.events import TradeFee
from hummingbot.core.utils.market_price import get_last_price

s_decimal_0 = Decimal("0")
s_decimal_nan = Decimal("NaN")


@dataclass
Expand Down Expand Up @@ -41,21 +43,28 @@ class PerformanceMetrics:
hold_value: Decimal = s_decimal_0
cur_value: Decimal = s_decimal_0
trade_pnl: Decimal = s_decimal_0
fee_paid: Decimal = s_decimal_0
fee_token: str = ""

fee_in_quote: Decimal = s_decimal_0
total_pnl: Decimal = s_decimal_0
return_pct: Decimal = s_decimal_0

realised_pnl: Decimal = s_decimal_0
unrealised_pnl: Decimal = s_decimal_0
# An outstanding amount, negative means more sell amounts than buy (short position)
outstanding_amount: Decimal = s_decimal_0
def __init__(self):
# fees is a dictionary of token and total fee amount paid in that token.
self.fees: Dict[str, Decimal] = {}


def calculate_performance_metrics(trading_pair: str,
trades: List[TradeFill],
current_balances: Dict[str, Decimal],
current_price: Optional[Decimal]) -> PerformanceMetrics:
async def calculate_performance_metrics(exchange: str,
trading_pair: str,
trades: List[Any],
current_balances: Dict[str, Decimal]) -> PerformanceMetrics:
"""
Calculates PnL, fees, Return % and etc...
:param exchange: the exchange or connector name
:param trading_pair: the trading market to get performance metrics
:param trades: the list of TradeFill or Trade object
:param current_balances: current user account balance
:return: A PerformanceMetrics object
"""

def divide(value, divisor):
value = Decimal(str(value))
Expand Down Expand Up @@ -93,7 +102,7 @@ def divide(value, divisor):
perf.start_quote_bal = perf.cur_quote_bal - perf.tot_vol_quote

perf.start_price = Decimal(str(trades[0].price))
perf.cur_price = current_price
perf.cur_price = await get_last_price(exchange.replace("_PaperTrade", ""), trading_pair)
if perf.cur_price is None:
perf.cur_price = Decimal(str(trades[-1].price))
perf.start_base_ratio_pct = divide(perf.start_base_bal * perf.start_price,
Expand All @@ -104,25 +113,44 @@ def divide(value, divisor):
perf.hold_value = (perf.start_base_bal * perf.cur_price) + perf.start_quote_bal
perf.cur_value = (perf.cur_base_bal * perf.cur_price) + perf.cur_quote_bal
perf.trade_pnl = perf.cur_value - perf.hold_value
fee_paid = 0
perf.fee_token = quote
if type(trades[0].trade_fee) is TradeFee:
perf.fee_token = trades[0].trade_fee.flat_fees[0][0]
fee_paid = sum(sum(ff[1] for ff in t.trade_fee.flat_fees) for t in trades)
else:
if trades[0].trade_fee.get("percent", None) is not None and trades[0].trade_fee["percent"] > 0:
fee_paid = sum(t.price * t.amount * t.trade_fee["percent"] for t in trades)
elif trades[0].trade_fee.get("flat_fees", []):
perf.fee_token = trades[0].trade_fee["flat_fees"][0]["asset"]
fee_paid = sum(f["amount"] for t in trades for f in t.trade_fee.get("flat_fees", []))
perf.fee_paid = Decimal(str(fee_paid))
perf.total_pnl = perf.trade_pnl - perf.fee_paid if perf.fee_token == quote else perf.trade_pnl

for trade in trades:
if type(trade) is TradeFill:
if trade.trade_fee.get("percent") is not None and trade.trade_fee["percent"] > 0:
if quote not in perf.fees:
perf.fees[quote] = s_decimal_0
perf.fees[quote] += Decimal(trade.price * trade.amount * trade.trade_fee["percent"])
for flat_fee in trade.trade_fee.get("flat_fees", []):
if flat_fee["asset"] not in perf.fees:
perf.fees[flat_fee["asset"]] = s_decimal_0
perf.fees[flat_fee["asset"]] += Decimal(flat_fee["amount"])
else: # assume this is Trade object
if trade.trade_fee.percent > 0:
if quote not in perf.fees:
perf.fees[quote] = s_decimal_0
perf.fees[quote] += (trade.price * trade.order_amount) * trade.trade_fee.percent
for flat_fee in trade.trade_fee.flat_fees:
if flat_fee[0] not in perf.fees:
perf.fees[flat_fee[0]] = s_decimal_0
perf.fees[flat_fee[0]] += flat_fee[1]

for fee_token, fee_amount in perf.fees.items():
if fee_token == quote:
perf.fee_in_quote += fee_amount
else:
last_price = await get_last_price(exchange, f"{fee_token}-{quote}")
if last_price is not None:
perf.fee_in_quote += fee_amount * last_price

perf.total_pnl = perf.trade_pnl - perf.fee_in_quote
perf.return_pct = divide(perf.total_pnl, perf.hold_value)

return perf


def smart_round(value: Decimal, precision: Optional[int] = None) -> Decimal:
if value is None or value.is_nan():
return value
if precision is not None:
precision = 1 / (10 ** precision)
return Decimal(str(value)).quantize(Decimal(str(precision)))
Expand Down
4 changes: 1 addition & 3 deletions hummingbot/client/ui/interface_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import datetime
import asyncio
from hummingbot.model.trade_fill import TradeFill
from hummingbot.core.utils.market_price import get_last_price
from hummingbot.client.performance import calculate_performance_metrics, smart_round


Expand Down Expand Up @@ -66,8 +65,7 @@ async def start_trade_monitor(trade_monitor):
quote_asset = symbol.split("-")[1] # Note that the qiote asset of the last pair is assumed to be the quote asset of P&L for simplicity
cur_trades = [t for t in trades if t.market == market and t.symbol == symbol]
cur_balances = await hb.get_current_balances(market)
cur_price = await get_last_price(market.replace("_PaperTrade", ""), symbol)
perf = calculate_performance_metrics(symbol, cur_trades, cur_balances, cur_price)
perf = await calculate_performance_metrics(market, symbol, cur_trades, cur_balances)
return_pcts.append(perf.return_pct)
pnls.append(perf.total_pnl)
avg_return = sum(return_pcts) / len(return_pcts) if len(return_pcts) > 0 else s_decimal_0
Expand Down
15 changes: 10 additions & 5 deletions hummingbot/connector/connector/balancer/balancer_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,9 @@ async def get_allowances(self) -> Dict[str, Decimal]:
:return: A dictionary of token and its allowance (how much Balancer can spend).
"""
ret_val = {}
resp = await self._api_request("post", "eth/allowances",
{"tokenAddressList": json.dumps(dict(zip(self._token_addresses.values(), self._token_decimals.values()))),
resp = await self._api_request("post", "eth/allowances-2",
{"tokenAddressList": ("".join([tok + "," for tok in self._token_addresses.values()])).rstrip(","),
"tokenDecimalList": ("".join([str(dec) + "," for dec in self._token_decimals.values()])).rstrip(","),
"connector": self.name})
for address, amount in resp["approvals"].items():
ret_val[self.get_token(address)] = Decimal(str(amount))
Expand All @@ -180,6 +181,8 @@ async def get_quote_price(self, trading_pair: str, is_buy: bool, amount: Decimal
{"base": self._token_addresses[base],
"quote": self._token_addresses[quote],
"amount": amount,
"base_decimals": self._token_decimals[base],
"quote_decimals": self._token_decimals[quote],
"maxSwaps": self._max_swaps})
if resp["price"] is not None:
return Decimal(str(resp["price"]))
Expand Down Expand Up @@ -259,6 +262,8 @@ async def _create_order(self,
"maxPrice": str(price),
"maxSwaps": str(self._max_swaps),
"gasPrice": str(gas_price),
"base_decimals": self._token_decimals[base],
"quote_decimals": self._token_decimals[quote],
}
self.start_tracking_order(order_id, None, trading_pair, trade_type, price, amount, gas_price)
try:
Expand Down Expand Up @@ -483,13 +488,13 @@ async def _update_balances(self, on_interval = False):
last_tick = self._last_balance_poll_timestamp
current_tick = self.current_timestamp
if not on_interval or (current_tick - last_tick) > self.UPDATE_BALANCE_INTERVAL:
self.logger().info("Update wallet balance")
self._last_balance_poll_timestamp = current_tick
local_asset_names = set(self._account_balances.keys())
remote_asset_names = set()
resp_json = await self._api_request("post",
"eth/balances",
{"tokenAddressList": json.dumps(dict(zip(self._token_addresses.values(), self._token_decimals.values())))})
"eth/balances-2",
{"tokenAddressList": ("".join([tok + "," for tok in self._token_addresses.values()])).rstrip(","),
"tokenDecimalList": ("".join([str(dec) + "," for dec in self._token_decimals.values()])).rstrip(",")})
for token, bal in resp_json["balances"].items():
if len(token) > 4:
token = self.get_token(token)
Expand Down
Loading

0 comments on commit c314a9d

Please sign in to comment.