Skip to content

Commit

Permalink
add fee info (#1006)
Browse files Browse the repository at this point in the history
- Have strategy fee labels configurable in the strategy module itself
  • Loading branch information
AlexTheLion123 authored Aug 15, 2024
1 parent 97028ed commit d06a8e4
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 5 deletions.
7 changes: 6 additions & 1 deletion strategies/matic-breakout.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@
from tradingstrategy.utils.groupeduniverse import resample_price_series

trading_strategy_engine_version = "0.5"

management_fee="0.00%"
trading_strategy_protocol_fee="0.02%"
strategy_developer_fee="0.1%"
enzyme_protocol_fee="0.0025%"

class Parameters:
"""Parameteres for this strategy.
Expand Down Expand Up @@ -84,6 +87,8 @@ class Parameters:
backtest_trading_fee = 0.0005 # Switch to QuickSwap 30 BPS free from the default Binance 5 BPS fee
initial_cash = 10_000




def get_strategy_trading_pairs(execution_mode: ExecutionMode) -> list[HumanReadableTradingPairDescription]:
"""Switch between backtest and live trading pairs.
Expand Down
4 changes: 4 additions & 0 deletions strategies/test_only/enzyme_end_to_end.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
trading_strategy_cycle = CycleDuration.cycle_1s
reserve_currency = ReserveCurrency.usdc

management_fee="0.01%"
trading_strategy_protocol_fee="0.01%"
strategy_developer_fee="0.01%"
enzyme_protocol_fee="0.01%"

def decide_trades(
timestamp: pd.Timestamp,
Expand Down
32 changes: 32 additions & 0 deletions tests/test_binance_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,38 @@ def test_create_binance_universe_multipair():
)


@pytest.mark.skipif(os.environ.get("GITHUB_ACTIONS", None) == "true", reason="No mock tests for this one")
def test_create_binance_universe_bigger_date_range():
"""Test reading cached candle data by downloading with a bigger date range."""

downloader = BinanceDownloader()
downloader.purge_all_cached_data()

_ = create_binance_universe(
["ETHUSDT"],
candle_time_bucket = TimeBucket.h4,
start_at = datetime.datetime(2021, 1, 1),
end_at = datetime.datetime(2021, 1, 10),
include_lending=True,
force_download=True,
)

strategy_universe = create_binance_universe(
["ETHUSDT"],
candle_time_bucket = TimeBucket.h4,
start_at = datetime.datetime(2021, 1, 4),
end_at = datetime.datetime(2021, 1, 6),
include_lending=True
)

candles_start, candles_end = strategy_universe.universe.candles.get_timestamp_range()
lending_start = strategy_universe.universe.lending_candles.variable_borrow_apr.df.index[0]
lending_end = strategy_universe.universe.lending_candles.variable_borrow_apr.df.index[-1]

assert candles_start == lending_start == datetime.datetime(2021, 1, 4, 0, 0)
assert candles_end == lending_end == datetime.datetime(2021, 1, 6, 0, 0)


@pytest.mark.skipif(os.environ.get("GITHUB_ACTIONS", None) == "true" or os.environ.get("BINANCE_LENDING_DATA") == "false", reason="Github US servers are blocked by Binance with HTTP 451")
def test_binance_multi_pair():
"""Check multipair resampling works."""
Expand Down
4 changes: 4 additions & 0 deletions tests/units_tests/test_strategy_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ def test_read_strategy_module(
"""Read strategy module and check for some basic information."""
mod = read_strategy_module(strategy_file)
assert mod.sort_priority == 99
assert mod.trading_strategy_protocol_fee == "0.02%"
assert mod.strategy_developer_fee == "0.1%"
assert mod.management_fee == "0.00%"
assert mod.enzyme_protocol_fee == "0.0025%"
16 changes: 14 additions & 2 deletions tests/web/test_webhook_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ def server_url(store):
True,
badges=Metadata.parse_badges_configuration("polygon, metamask, eth, usdc"),
tags={StrategyTag.beta},
fees=dict(
management_fee="0.00%",
trading_strategy_protocol_fee="0.02%",
strategy_developer_fee="0.1%",
enzyme_protocol_fee="0.0025%",
)
)

# Inject some fake files for the backtest content
Expand All @@ -75,7 +81,7 @@ def test_home(logger, server_url):
assert resp.status_code == 200
# Chuck the Trade Executor server, version 0.1.0, our URL is http://127.0.0.1:5000
assert resp.headers["content-type"] == "text/plain; charset=UTF-8"
assert "Trading Strategy" in resp.text
assert "Trade Executor daemon of Trading Strategy" in resp.text


def test_ping(logger,server_url):
Expand All @@ -92,10 +98,16 @@ def test_metadata(logger, server_url):
data = resp.json()
assert data["name"] == "Foobar"
assert data["short_description"] == "Short desc"
assert data["executor_running"] == True
assert data["executor_running"] is True
assert data["crashed_at"] is None
assert data["badges"] == ["polygon", "metamask", "eth", "usdc"]
assert data["tags"] == ["beta"]
assert data["fees"] == dict(
management_fee="0.00%",
trading_strategy_protocol_fee="0.02%",
strategy_developer_fee="0.1%",
enzyme_protocol_fee="0.0025%",
)


def test_cors(logger, server_url):
Expand Down
2 changes: 2 additions & 0 deletions tradeexecutor/cli/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ def create_metadata(
tags: Optional[Set[StrategyTag]] = None,
hot_wallet: Optional[HotWallet] = None,
sort_priority=0,
fees=None,
) -> Metadata:
"""Create metadata object from the configuration variables."""

Expand Down Expand Up @@ -365,6 +366,7 @@ def create_metadata(
badges=Metadata.parse_badges_configuration(badges),
tags=tags or set(), # Always fill empty set
sort_priority=sort_priority,
fees=fees,
)

return metadata
Expand Down
10 changes: 10 additions & 0 deletions tradeexecutor/cli/commands/start.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
from typing import Optional

import typer
import runpy

from eth_defi.gas import GasPriceMethod
from tradeexecutor.strategy.strategy_module import parse_strategy_module
from tradingstrategy.chain import ChainId
from tradingstrategy.client import Client
from tradingstrategy.testing.uniswap_v2_mock_client import UniswapV2MockClient
Expand Down Expand Up @@ -334,6 +336,13 @@ def start(
if not notebook_report and backtest_result:
notebook_report = Path(f"state/{id}-backtest.ipynb")

fees = dict(
management_fee=mod.management_fee,
trading_strategy_protocol_fee=mod.trading_strategy_protocol_fee,
strategy_developer_fee=mod.strategy_developer_fee,
enzyme_protocol_fee=mod.enzyme_protocol_fee,
)

metadata = create_metadata(
name,
short_description,
Expand All @@ -350,6 +359,7 @@ def start(
tags=mod.tags,
hot_wallet=sync_model.get_hot_wallet(),
sort_priority=mod.sort_priority,
fees=fees,
)

# Start the queue that relays info from the web server to the strategy executor
Expand Down
8 changes: 8 additions & 0 deletions tradeexecutor/cli/commands/webapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ def webapi(
if state_file:
store = create_state_store(Path(state_file))

fees = dict(
management_fee=mod.management_fee,
trading_strategy_protocol_fee=mod.trading_strategy_protocol_fee,
strategy_developer_fee=mod.strategy_developer_fee,
enzyme_protocol_fee=mod.enzyme_protocol_fee,
)

metadata = create_metadata(
name,
short_description,
Expand All @@ -109,6 +116,7 @@ def webapi(
AssetManagementMode.dummy,
chain_id=mod.get_default_chain_id(),
vault=None,
fees=fees,
)

# Start the queue that relays info from the web server to the strategy executor
Expand Down
10 changes: 10 additions & 0 deletions tradeexecutor/state/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,16 @@ class Metadata:
#: Higher = the strategy apppears in the frontend first.
sort_priority: int = 0

#: Fees for this strategy
#: In the format
#: {
#: management_fee,
#: trading_strategy_protocol_fee,
#: strategy_developer_fee,
#: enzyme_protocol_fee,
#: }
fees: Dict[str, str] = field(default_factory=dict)

@staticmethod
def create_dummy() -> "Metadata":
return Metadata(
Expand Down
2 changes: 1 addition & 1 deletion tradeexecutor/state/position.py
Original file line number Diff line number Diff line change
Expand Up @@ -1656,7 +1656,7 @@ def get_size_relative_realised_profit_percent(self) -> Percent:
The profit is scaled to the % of the position size relative to the portfolio
to account for max capital allocation for the position.
- TODO: This does not work for positions that have capital added over time
- TODO: This does not work for positions that have capital added over time or strategies that have overlapping positions
See :ref:`profitability` for more details.
Expand Down
17 changes: 16 additions & 1 deletion tradeexecutor/strategy/strategy_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import inspect
import logging
import runpy
from dataclasses import dataclass
from dataclasses import dataclass, field
from pathlib import Path
from types import NoneType
from typing import Callable, Dict, Protocol, List, Optional, Union, Set, Type
Expand Down Expand Up @@ -318,6 +318,8 @@ def __call__(self,
"""




@dataclass
class StrategyModuleInformation:
"""Describe elements that we need to have in a strategy module.
Expand Down Expand Up @@ -448,6 +450,13 @@ class StrategyModuleInformation:
#:
parameters: Optional[Type | StrategyParameters] = None

#: Fees for the strategy.
#:
management_fee: Optional[str] = None
trading_strategy_protocol_fee: Optional[str] = None
strategy_developer_fee: Optional[str] = None
enzyme_protocol_fee: Optional[str] = None

def __repr__(self):
return f"<StrategyModuleInformation {self.path}>"

Expand Down Expand Up @@ -606,6 +615,8 @@ def get_live_trading_history_period(self) -> datetime.timedelta | None:
return None




def parse_strategy_module(
path,
python_module_exports: dict,
Expand Down Expand Up @@ -641,6 +652,10 @@ def parse_strategy_module(
sort_priority=python_module_exports.get("sort_priority"),
create_indicators=python_module_exports.get("create_indicators"),
parameters=python_module_exports.get("parameters"),
management_fee=python_module_exports.get("management_fee"),
trading_strategy_protocol_fee=python_module_exports.get("trading_strategy_protocol_fee"),
strategy_developer_fee=python_module_exports.get("strategy_developer_fee"),
enzyme_protocol_fee=python_module_exports.get("enzyme_protocol_fee"),
)


Expand Down
5 changes: 5 additions & 0 deletions tradeexecutor/strategy/summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,11 @@ class StrategySummary:
#:
tags: Set[StrategyTag] = field(default_factory=set)

#: Fees charged by the strategy
#:
#: See `Metadata.fees` for description.
#:
fees: Dict[str, float] = field(default_factory=dict)

#: Help links for different metrics
_KEY_METRIC_HELP = {
Expand Down
1 change: 1 addition & 0 deletions tradeexecutor/webhook/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def web_metadata(request: Request):
crashed_at=run_state.crashed_at,
badges=metadata.badges,
tags=metadata.tags,
fees=metadata.fees,
)

# Catch NaN's and other data JavaScript cannot eat
Expand Down

0 comments on commit d06a8e4

Please sign in to comment.