diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7bf5de208..b9c116885 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.5.7' + rev: 'v0.6.1' hooks: - id: ruff diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 22828b899..9f37b2975 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -13,19 +13,22 @@ Please follow the [documentation](https://www.freqtrade.io/en/stable/data-downlo import os from pathlib import Path + # Change directory # Modify this cell to insure that the output shows the correct path. # Define all paths relative to the project root shown in the cell output project_root = "somedir/freqtrade" -i=0 +i = 0 try: os.chdir(project_root) - assert Path('LICENSE').is_file() -except: - while i<4 and (not Path('LICENSE').is_file()): - os.chdir(Path(Path.cwd(), '../')) - i+=1 - project_root = Path.cwd() + if not Path("LICENSE").is_file(): + i = 0 + while i < 4 and (not Path("LICENSE").is_file()): + os.chdir(Path(Path.cwd(), "../")) + i += 1 + project_root = Path.cwd() +except FileNotFoundError: + print("Please define the project root relative to the current directory") print(Path.cwd()) ``` @@ -35,6 +38,7 @@ print(Path.cwd()) ```python from freqtrade.configuration import Configuration + # Customize these according to your needs. # Initialize empty configuration object @@ -58,12 +62,14 @@ pair = "BTC/USDT" from freqtrade.data.history import load_pair_history from freqtrade.enums import CandleType -candles = load_pair_history(datadir=data_location, - timeframe=config["timeframe"], - pair=pair, - data_format = "json", # Make sure to update this to your data - candle_type=CandleType.SPOT, - ) + +candles = load_pair_history( + datadir=data_location, + timeframe=config["timeframe"], + pair=pair, + data_format="json", # Make sure to update this to your data + candle_type=CandleType.SPOT, +) # Confirm success print(f"Loaded {len(candles)} rows of data for {pair} from {data_location}") @@ -76,14 +82,16 @@ candles.head() ```python # Load strategy using values set above -from freqtrade.resolvers import StrategyResolver from freqtrade.data.dataprovider import DataProvider +from freqtrade.resolvers import StrategyResolver + + strategy = StrategyResolver.load_strategy(config) strategy.dp = DataProvider(config, None, None) strategy.ft_bot_start() # Generate buy/sell signals using strategy -df = strategy.analyze_ticker(candles, {'pair': pair}) +df = strategy.analyze_ticker(candles, {"pair": pair}) df.tail() ``` @@ -102,7 +110,7 @@ df.tail() ```python # Report results print(f"Generated {df['enter_long'].sum()} entry signals") -data = df.set_index('date', drop=False) +data = df.set_index("date", drop=False) data.tail() ``` @@ -119,10 +127,13 @@ Analyze a trades dataframe (also used below for plotting) ```python from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats + # if backtest_dir points to a directory, it'll automatically load the last backtest file. backtest_dir = config["user_data_dir"] / "backtest_results" -# backtest_dir can also point to a specific file -# backtest_dir = config["user_data_dir"] / "backtest_results/backtest-result-2020-07-01_20-04-22.json" +# backtest_dir can also point to a specific file +# backtest_dir = ( +# config["user_data_dir"] / "backtest_results/backtest-result-2020-07-01_20-04-22.json" +# ) ``` @@ -131,24 +142,24 @@ backtest_dir = config["user_data_dir"] / "backtest_results" # This contains all information used to generate the backtest result. stats = load_backtest_stats(backtest_dir) -strategy = 'SampleStrategy' -# All statistics are available per strategy, so if `--strategy-list` was used during backtest, this will be reflected here as well. +strategy = "SampleStrategy" +# All statistics are available per strategy, so if `--strategy-list` was used during backtest, +# this will be reflected here as well. # Example usages: -print(stats['strategy'][strategy]['results_per_pair']) +print(stats["strategy"][strategy]["results_per_pair"]) # Get pairlist used for this backtest -print(stats['strategy'][strategy]['pairlist']) +print(stats["strategy"][strategy]["pairlist"]) # Get market change (average change of all pairs from start to end of the backtest period) -print(stats['strategy'][strategy]['market_change']) +print(stats["strategy"][strategy]["market_change"]) # Maximum drawdown () -print(stats['strategy'][strategy]['max_drawdown']) +print(stats["strategy"][strategy]["max_drawdown"]) # Maximum drawdown start and end -print(stats['strategy'][strategy]['drawdown_start']) -print(stats['strategy'][strategy]['drawdown_end']) +print(stats["strategy"][strategy]["drawdown_start"]) +print(stats["strategy"][strategy]["drawdown_end"]) # Get strategy comparison (only relevant if multiple strategies were compared) -print(stats['strategy_comparison']) - +print(stats["strategy_comparison"]) ``` @@ -166,24 +177,25 @@ trades.groupby("pair")["exit_reason"].value_counts() ```python # Plotting equity line (starting with 0 on day 1 and adding daily profit for each backtested day) +import pandas as pd +import plotly.express as px + from freqtrade.configuration import Configuration from freqtrade.data.btanalysis import load_backtest_stats -import plotly.express as px -import pandas as pd + # strategy = 'SampleStrategy' # config = Configuration.from_files(["user_data/config.json"]) # backtest_dir = config["user_data_dir"] / "backtest_results" stats = load_backtest_stats(backtest_dir) -strategy_stats = stats['strategy'][strategy] +strategy_stats = stats["strategy"][strategy] -df = pd.DataFrame(columns=['dates','equity'], data=strategy_stats['daily_profit']) -df['equity_daily'] = df['equity'].cumsum() +df = pd.DataFrame(columns=["dates", "equity"], data=strategy_stats["daily_profit"]) +df["equity_daily"] = df["equity"].cumsum() fig = px.line(df, x="dates", y="equity_daily") fig.show() - ``` ### Load live trading results into a pandas dataframe @@ -194,6 +206,7 @@ In case you did already some trading and want to analyze your performance ```python from freqtrade.data.btanalysis import load_trades_from_db + # Fetch trades from database trades = load_trades_from_db("sqlite:///tradesv3.sqlite") @@ -210,8 +223,9 @@ This can be useful to find the best `max_open_trades` parameter, when used with ```python from freqtrade.data.btanalysis import analyze_trade_parallelism + # Analyze the above -parallel_trades = analyze_trade_parallelism(trades, '5m') +parallel_trades = analyze_trade_parallelism(trades, "5m") parallel_trades.plot() ``` @@ -222,23 +236,23 @@ Freqtrade offers interactive plotting capabilities based on plotly. ```python -from freqtrade.plot.plotting import generate_candlestick_graph +from freqtrade.plot.plotting import generate_candlestick_graph + + # Limit graph period to keep plotly quick and reactive # Filter trades to one pair -trades_red = trades.loc[trades['pair'] == pair] +trades_red = trades.loc[trades["pair"] == pair] -data_red = data['2019-06-01':'2019-06-10'] +data_red = data["2019-06-01":"2019-06-10"] # Generate candlestick graph -graph = generate_candlestick_graph(pair=pair, - data=data_red, - trades=trades_red, - indicators1=['sma20', 'ema50', 'ema55'], - indicators2=['rsi', 'macd', 'macdsignal', 'macdhist'] - ) - - - +graph = generate_candlestick_graph( + pair=pair, + data=data_red, + trades=trades_red, + indicators1=["sma20", "ema50", "ema55"], + indicators2=["rsi", "macd", "macdsignal", "macdhist"], +) ``` @@ -248,7 +262,6 @@ graph = generate_candlestick_graph(pair=pair, # Render graph in a separate window graph.show(renderer="browser") - ``` ## Plot average profit per trade as distribution graph @@ -257,12 +270,12 @@ graph.show(renderer="browser") ```python import plotly.figure_factory as ff + hist_data = [trades.profit_ratio] -group_labels = ['profit_ratio'] # name of the dataset +group_labels = ["profit_ratio"] # name of the dataset fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01) fig.show() - ``` Feel free to submit an issue or Pull Request enhancing this document if you would like to share ideas on how to best analyze the data. diff --git a/freqtrade/rpc/api_server/api_ws.py b/freqtrade/rpc/api_server/api_ws.py index 5e2eddc68..ed458165e 100644 --- a/freqtrade/rpc/api_server/api_ws.py +++ b/freqtrade/rpc/api_server/api_ws.py @@ -58,7 +58,7 @@ async def channel_broadcaster(channel: WebSocketChannel, message_stream: Message " consumers." ) - await channel.send(message, timeout=True) + await channel.send(message, use_timeout=True) async def _process_consumer_request(request: Dict[str, Any], channel: WebSocketChannel, rpc: RPC): diff --git a/freqtrade/rpc/api_server/ws/channel.py b/freqtrade/rpc/api_server/ws/channel.py index 0041bb6b2..3c1e0ce2d 100644 --- a/freqtrade/rpc/api_server/ws/channel.py +++ b/freqtrade/rpc/api_server/ws/channel.py @@ -80,7 +80,7 @@ class WebSocketChannel: self._send_high_limit = min(max(self.avg_send_time * 2, 1), 3) async def send( - self, message: Union[WSMessageSchemaType, Dict[str, Any]], timeout: bool = False + self, message: Union[WSMessageSchemaType, Dict[str, Any]], use_timeout: bool = False ): """ Send a message on the wrapped websocket. If the sending @@ -88,7 +88,7 @@ class WebSocketChannel: disconnect the connection. :param message: The message to send - :param timeout: Enforce send high limit, defaults to False + :param use_timeout: Enforce send high limit, defaults to False """ try: _ = time.time() @@ -96,7 +96,8 @@ class WebSocketChannel: # a TimeoutError and bubble up to the # message_endpoint to close the connection await asyncio.wait_for( - self._wrapped_ws.send(message), timeout=self._send_high_limit if timeout else None + self._wrapped_ws.send(message), + timeout=self._send_high_limit if use_timeout else None, ) total_time = time.time() - _ self._send_times.append(total_time) diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb index 8d4459a3c..e81ff72ca 100644 --- a/freqtrade/templates/strategy_analysis_example.ipynb +++ b/freqtrade/templates/strategy_analysis_example.ipynb @@ -29,19 +29,22 @@ "import os\n", "from pathlib import Path\n", "\n", + "\n", "# Change directory\n", "# Modify this cell to insure that the output shows the correct path.\n", "# Define all paths relative to the project root shown in the cell output\n", "project_root = \"somedir/freqtrade\"\n", - "i=0\n", + "i = 0\n", "try:\n", " os.chdir(project_root)\n", - " assert Path('LICENSE').is_file()\n", - "except:\n", - " while i<4 and (not Path('LICENSE').is_file()):\n", - " os.chdir(Path(Path.cwd(), '../'))\n", - " i+=1\n", - " project_root = Path.cwd()\n", + " if not Path(\"LICENSE\").is_file():\n", + " i = 0\n", + " while i < 4 and (not Path(\"LICENSE\").is_file()):\n", + " os.chdir(Path(Path.cwd(), \"../\"))\n", + " i += 1\n", + " project_root = Path.cwd()\n", + "except FileNotFoundError:\n", + " print(\"Please define the project root relative to the current directory\")\n", "print(Path.cwd())" ] }, @@ -60,6 +63,7 @@ "source": [ "from freqtrade.configuration import Configuration\n", "\n", + "\n", "# Customize these according to your needs.\n", "\n", "# Initialize empty configuration object\n", @@ -87,12 +91,14 @@ "from freqtrade.data.history import load_pair_history\n", "from freqtrade.enums import CandleType\n", "\n", - "candles = load_pair_history(datadir=data_location,\n", - " timeframe=config[\"timeframe\"],\n", - " pair=pair,\n", - " data_format = \"json\", # Make sure to update this to your data\n", - " candle_type=CandleType.SPOT,\n", - " )\n", + "\n", + "candles = load_pair_history(\n", + " datadir=data_location,\n", + " timeframe=config[\"timeframe\"],\n", + " pair=pair,\n", + " data_format=\"json\", # Make sure to update this to your data\n", + " candle_type=CandleType.SPOT,\n", + ")\n", "\n", "# Confirm success\n", "print(f\"Loaded {len(candles)} rows of data for {pair} from {data_location}\")\n", @@ -114,14 +120,16 @@ "outputs": [], "source": [ "# Load strategy using values set above\n", - "from freqtrade.resolvers import StrategyResolver\n", "from freqtrade.data.dataprovider import DataProvider\n", + "from freqtrade.resolvers import StrategyResolver\n", + "\n", + "\n", "strategy = StrategyResolver.load_strategy(config)\n", "strategy.dp = DataProvider(config, None, None)\n", "strategy.ft_bot_start()\n", "\n", "# Generate buy/sell signals using strategy\n", - "df = strategy.analyze_ticker(candles, {'pair': pair})\n", + "df = strategy.analyze_ticker(candles, {\"pair\": pair})\n", "df.tail()" ] }, @@ -148,7 +156,7 @@ "source": [ "# Report results\n", "print(f\"Generated {df['enter_long'].sum()} entry signals\")\n", - "data = df.set_index('date', drop=False)\n", + "data = df.set_index(\"date\", drop=False)\n", "data.tail()" ] }, @@ -179,10 +187,13 @@ "source": [ "from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats\n", "\n", + "\n", "# if backtest_dir points to a directory, it'll automatically load the last backtest file.\n", "backtest_dir = config[\"user_data_dir\"] / \"backtest_results\"\n", "# backtest_dir can also point to a specific file\n", - "# backtest_dir = config[\"user_data_dir\"] / \"backtest_results/backtest-result-2020-07-01_20-04-22.json\"" + "# backtest_dir = (\n", + "# config[\"user_data_dir\"] / \"backtest_results/backtest-result-2020-07-01_20-04-22.json\"\n", + "# )" ] }, { @@ -195,23 +206,24 @@ "# This contains all information used to generate the backtest result.\n", "stats = load_backtest_stats(backtest_dir)\n", "\n", - "strategy = 'SampleStrategy'\n", - "# All statistics are available per strategy, so if `--strategy-list` was used during backtest, this will be reflected here as well.\n", + "strategy = \"SampleStrategy\"\n", + "# All statistics are available per strategy, so if `--strategy-list` was used during backtest,\n", + "# this will be reflected here as well.\n", "# Example usages:\n", - "print(stats['strategy'][strategy]['results_per_pair'])\n", + "print(stats[\"strategy\"][strategy][\"results_per_pair\"])\n", "# Get pairlist used for this backtest\n", - "print(stats['strategy'][strategy]['pairlist'])\n", + "print(stats[\"strategy\"][strategy][\"pairlist\"])\n", "# Get market change (average change of all pairs from start to end of the backtest period)\n", - "print(stats['strategy'][strategy]['market_change'])\n", + "print(stats[\"strategy\"][strategy][\"market_change\"])\n", "# Maximum drawdown ()\n", - "print(stats['strategy'][strategy]['max_drawdown'])\n", + "print(stats[\"strategy\"][strategy][\"max_drawdown\"])\n", "# Maximum drawdown start and end\n", - "print(stats['strategy'][strategy]['drawdown_start'])\n", - "print(stats['strategy'][strategy]['drawdown_end'])\n", + "print(stats[\"strategy\"][strategy][\"drawdown_start\"])\n", + "print(stats[\"strategy\"][strategy][\"drawdown_end\"])\n", "\n", "\n", "# Get strategy comparison (only relevant if multiple strategies were compared)\n", - "print(stats['strategy_comparison'])\n" + "print(stats[\"strategy_comparison\"])" ] }, { @@ -242,23 +254,25 @@ "source": [ "# Plotting equity line (starting with 0 on day 1 and adding daily profit for each backtested day)\n", "\n", + "import pandas as pd\n", + "import plotly.express as px\n", + "\n", "from freqtrade.configuration import Configuration\n", "from freqtrade.data.btanalysis import load_backtest_stats\n", - "import plotly.express as px\n", - "import pandas as pd\n", + "\n", "\n", "# strategy = 'SampleStrategy'\n", "# config = Configuration.from_files([\"user_data/config.json\"])\n", "# backtest_dir = config[\"user_data_dir\"] / \"backtest_results\"\n", "\n", "stats = load_backtest_stats(backtest_dir)\n", - "strategy_stats = stats['strategy'][strategy]\n", + "strategy_stats = stats[\"strategy\"][strategy]\n", "\n", - "df = pd.DataFrame(columns=['dates','equity'], data=strategy_stats['daily_profit'])\n", - "df['equity_daily'] = df['equity'].cumsum()\n", + "df = pd.DataFrame(columns=[\"dates\", \"equity\"], data=strategy_stats[\"daily_profit\"])\n", + "df[\"equity_daily\"] = df[\"equity\"].cumsum()\n", "\n", "fig = px.line(df, x=\"dates\", y=\"equity_daily\")\n", - "fig.show()\n" + "fig.show()" ] }, { @@ -278,6 +292,7 @@ "source": [ "from freqtrade.data.btanalysis import load_trades_from_db\n", "\n", + "\n", "# Fetch trades from database\n", "trades = load_trades_from_db(\"sqlite:///tradesv3.sqlite\")\n", "\n", @@ -303,8 +318,9 @@ "source": [ "from freqtrade.data.btanalysis import analyze_trade_parallelism\n", "\n", + "\n", "# Analyze the above\n", - "parallel_trades = analyze_trade_parallelism(trades, '5m')\n", + "parallel_trades = analyze_trade_parallelism(trades, \"5m\")\n", "\n", "parallel_trades.plot()" ] @@ -324,22 +340,23 @@ "metadata": {}, "outputs": [], "source": [ - "from freqtrade.plot.plotting import generate_candlestick_graph\n", + "from freqtrade.plot.plotting import generate_candlestick_graph\n", + "\n", + "\n", "# Limit graph period to keep plotly quick and reactive\n", "\n", "# Filter trades to one pair\n", - "trades_red = trades.loc[trades['pair'] == pair]\n", + "trades_red = trades.loc[trades[\"pair\"] == pair]\n", "\n", - "data_red = data['2019-06-01':'2019-06-10']\n", + "data_red = data[\"2019-06-01\":\"2019-06-10\"]\n", "# Generate candlestick graph\n", - "graph = generate_candlestick_graph(pair=pair,\n", - " data=data_red,\n", - " trades=trades_red,\n", - " indicators1=['sma20', 'ema50', 'ema55'],\n", - " indicators2=['rsi', 'macd', 'macdsignal', 'macdhist']\n", - " )\n", - "\n", - "\n" + "graph = generate_candlestick_graph(\n", + " pair=pair,\n", + " data=data_red,\n", + " trades=trades_red,\n", + " indicators1=[\"sma20\", \"ema50\", \"ema55\"],\n", + " indicators2=[\"rsi\", \"macd\", \"macdsignal\", \"macdhist\"],\n", + ")" ] }, { @@ -352,7 +369,7 @@ "# graph.show()\n", "\n", "# Render graph in a separate window\n", - "graph.show(renderer=\"browser\")\n" + "graph.show(renderer=\"browser\")" ] }, { @@ -370,11 +387,12 @@ "source": [ "import plotly.figure_factory as ff\n", "\n", + "\n", "hist_data = [trades.profit_ratio]\n", - "group_labels = ['profit_ratio'] # name of the dataset\n", + "group_labels = [\"profit_ratio\"] # name of the dataset\n", "\n", "fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01)\n", - "fig.show()\n" + "fig.show()" ] }, { diff --git a/requirements-dev.txt b/requirements-dev.txt index 0a725f660..e5191d48d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt coveralls==4.0.1 -ruff==0.5.7 +ruff==0.6.1 mypy==1.11.1 pre-commit==3.8.0 pytest==8.3.2