diff --git a/backend/services/backend_api_client.py b/backend/services/backend_api_client.py index 4fefb68d..606cbce0 100644 --- a/backend/services/backend_api_client.py +++ b/backend/services/backend_api_client.py @@ -175,6 +175,21 @@ def delete_controller_config(self, controller_name: str): url = "delete-controller-config" return self.post(url, params={"config_name": controller_name}) + def delete_script_config(self, script_name: str): + """Delete a script configuration.""" + url = "delete-script-config" + return self.post(url, params={"script_name": script_name}) + + def delete_all_controller_configs(self): + """Delete all controller configurations.""" + endpoint = "delete-all-controller-configs" + return self.post(endpoint) + + def delete_all_script_configs(self): + """Delete all script configurations.""" + endpoint = "delete-all-script-configs" + return self.post(endpoint) + def get_real_time_candles(self, connector: str, trading_pair: str, interval: str, max_records: int): """Get candles data.""" endpoint = "real-time-candles" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..84a9cc49 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +services: + dashboard: + container_name: dashboard + image: hummingbot/dashboard:latest + ports: + - "8501:8501" + environment: + - AUTH_SYSTEM_ENABLED=False + - BACKEND_API_HOST=backend-api + - BACKEND_API_PORT=8000 + - BACKEND_API_USERNAME=admin + - BACKEND_API_PASSWORD=admin + volumes: + - .:/home/dashboard \ No newline at end of file diff --git a/frontend/components/bot_performance_card.py b/frontend/components/bot_performance_card.py index b97f3ba6..a903e788 100644 --- a/frontend/components/bot_performance_card.py +++ b/frontend/components/bot_performance_card.py @@ -68,6 +68,7 @@ def start_controllers(self, bot_name): def __call__(self, bot_name: str): try: controller_configs = backend_api_client.get_all_configs_from_bot(bot_name) + controller_configs = controller_configs if controller_configs else [] bot_status = backend_api_client.get_bot_status(bot_name) # Controllers Table active_controllers_list = [] @@ -78,6 +79,9 @@ def __call__(self, bot_name: str): total_open_order_volume = 0 total_imbalance = 0 total_unrealized_pnl_quote = 0 + bot_data = bot_status.get("data") + error_logs = bot_data.get("error_logs", []) + general_logs = bot_data.get("general_logs", []) if bot_status.get("status") == "error": with mui.Card(key=self._key, sx={"display": "flex", "flexDirection": "column", "borderRadius": 2, "overflow": "auto"}, @@ -91,11 +95,8 @@ def __call__(self, bot_name: str): f"An error occurred while fetching bot status of the bot {bot_name}. Please check the bot client.", severity="error") else: - bot_data = bot_status.get("data") is_running = bot_data.get("status") == "running" performance = bot_data.get("performance") - error_logs = bot_data.get("error_logs") - general_logs = bot_data.get("general_logs") if is_running: for controller, inner_dict in performance.items(): controller_status = inner_dict.get("status") @@ -115,14 +116,15 @@ def __call__(self, bot_name: str): global_pnl_quote = controller_performance.get("global_pnl_quote", 0) volume_traded = controller_performance.get("volume_traded", 0) open_order_volume = controller_performance.get("open_order_volume", 0) - imbalance = controller_performance.get("imbalance", 0) + imbalance = controller_performance.get("inventory_imbalance", 0) close_types = controller_performance.get("close_type_counts", {}) tp = close_types.get("CloseType.TAKE_PROFIT", 0) sl = close_types.get("CloseType.STOP_LOSS", 0) time_limit = close_types.get("CloseType.TIME_LIMIT", 0) ts = close_types.get("CloseType.TRAILING_STOP", 0) refreshed = close_types.get("CloseType.EARLY_STOP", 0) - close_types_str = f"TP: {tp} | SL: {sl} | TS: {ts} | TL: {time_limit} | RS: {refreshed}" + failed = close_types.get("CloseType.FAILED", 0) + close_types_str = f"TP: {tp} | SL: {sl} | TS: {ts} | TL: {time_limit} | ES: {refreshed} | F: {failed}" controller_info = { "id": controller, "controller": controller_name, diff --git a/frontend/components/launch_strategy_v2.py b/frontend/components/launch_strategy_v2.py index 354db3bb..2669269f 100644 --- a/frontend/components/launch_strategy_v2.py +++ b/frontend/components/launch_strategy_v2.py @@ -32,6 +32,10 @@ def __init__(self, *args, **kwargs): self._bot_name = None self._image_name = "hummingbot/hummingbot:latest" self._credentials = "master_account" + self._max_global_drawdown = None + self._max_controller_drawdown = None + self._rebalance_interval = None + self._asset_to_rebalance = "USDT" def _set_bot_name(self, event): self._bot_name = event.target.value @@ -48,6 +52,18 @@ def _set_controller(self, event): def _handle_row_selection(self, params, _): self._controller_config_selected = [param + ".yml" for param in params] + def _set_max_global_drawdown(self, event): + self._max_global_drawdown = event.target.value + + def _set_max_controller_drawdown(self, event): + self._max_controller_drawdown = event.target.value + + def _set_rebalance_interval(self, event): + self._rebalance_interval = event.target.value + + def _set_asset_to_rebalance(self, event): + self._asset_to_rebalance = event.target.value + def launch_new_bot(self): if not self._bot_name: st.warning("You need to define the bot name.") @@ -72,7 +88,19 @@ def launch_new_bot(self): "time_to_cash_out": None, } } - + if self._max_global_drawdown: + script_config["content"]["max_global_drawdown"] = self._max_global_drawdown + if self._max_controller_drawdown: + script_config["content"]["max_controller_drawdown"] = self._max_controller_drawdown + if self._rebalance_interval: + script_config["content"]["rebalance_interval"] = self._rebalance_interval + if self._asset_to_rebalance and "USD" in self._asset_to_rebalance: + script_config["content"]["asset_to_rebalance"] = self._asset_to_rebalance + else: + st.error("You need to define the asset to rebalance in USD like token.") + return + + self._backend_api_client.delete_all_script_configs() self._backend_api_client.add_script_config(script_config) deploy_config = { "instance_name": bot_name, @@ -102,10 +130,10 @@ def __call__(self): mui.Typography("🎛️ Bot Configuration", variant="h5") with mui.Grid(container=True, spacing=2, sx={"padding": "10px 15px 10px 15px"}): - with mui.Grid(item=True, xs=4): + with mui.Grid(item=True, xs=3): mui.TextField(label="Instance Name", variant="outlined", onChange=lazy(self._set_bot_name), sx={"width": "100%"}) - with mui.Grid(item=True, xs=4): + with mui.Grid(item=True, xs=3): available_images = self._backend_api_client.get_available_images("hummingbot") with mui.FormControl(variant="standard", sx={"width": "100%"}): mui.FormHelperText("Available Images") @@ -113,7 +141,6 @@ def __call__(self): variant="standard", onChange=lazy(self._set_image_name)): for image in available_images: mui.MenuItem(image, value=image) - with mui.Grid(item=True, xs=4): available_credentials = self._backend_api_client.get_accounts() with mui.FormControl(variant="standard", sx={"width": "100%"}): mui.FormHelperText("Credentials") @@ -121,6 +148,22 @@ def __call__(self): variant="standard", onChange=lazy(self._set_credentials)): for master_config in available_credentials: mui.MenuItem(master_config, value=master_config) + with mui.Grid(item=True, xs=3): + with mui.FormControl(variant="standard", sx={"width": "100%"}): + mui.FormHelperText("Risk Management") + mui.TextField(label="Max Global Drawdown (%)", variant="outlined", type="number", + onChange=lazy(self._set_max_global_drawdown), sx={"width": "100%"}) + mui.TextField(label="Max Controller Drawdown (%)", variant="outlined", type="number", + onChange=lazy(self._set_max_controller_drawdown), sx={"width": "100%"}) + + with mui.Grid(item=True, xs=3): + with mui.FormControl(variant="standard", sx={"width": "100%"}): + mui.FormHelperText("Rebalance Configuration") + mui.TextField(label="Rebalance Interval (minutes)", variant="outlined", type="number", + onChange=lazy(self._set_rebalance_interval), sx={"width": "100%"}) + mui.TextField(label="Asset to Rebalance", variant="outlined", + onChange=lazy(self._set_asset_to_rebalance), + sx={"width": "100%"}, default="USDT") all_controllers_config = self._backend_api_client.get_all_controllers_config() data = [] for config in all_controllers_config: @@ -141,7 +184,8 @@ def __call__(self): ts_text = str(trailing_stop["activation_price"]) + " / " + str(trailing_stop["trailing_delta"]) data.append({ "id": config["id"], "config_base": config_base, "version": version, - "controller_name": config["controller_name"], "controller_type": config["controller_type"], + "controller_name": config["controller_name"], + "controller_type": config.get("controller_type", "generic"), "connector_name": connector_name, "trading_pair": trading_pair, "total_amount_quote": total_amount_quote, "max_loss_quote": total_amount_quote * stop_loss / 2, "stop_loss": stop_loss, "take_profit": take_profit, diff --git a/frontend/pages/config/grid_strike/README.md b/frontend/pages/config/grid_strike/README.md new file mode 100644 index 00000000..a9696878 --- /dev/null +++ b/frontend/pages/config/grid_strike/README.md @@ -0,0 +1,94 @@ +# Grid Strike Configuration Tool + +Welcome to the Grid Strike Configuration Tool! This tool allows you to create, modify, visualize, and save configurations for the Grid Strike trading strategy. Here's how you can make the most out of it. + +## Features + +- **Start from Default Configurations**: Begin with a default configuration or use the values from an existing configuration. +- **Dynamic Price Range Defaults**: Automatically sets grid ranges based on current market conditions. +- **Visual Grid Configuration**: See your grid ranges directly on the price chart. +- **Multiple Grid Ranges**: Configure up to 5 different grid ranges with different sides (BUY/SELL). +- **Save and Deploy**: Once satisfied, save the configuration to deploy it later. + +## How to Use + +### 1. Basic Configuration + +Start by configuring the basic parameters: +- **Connector Name**: Select the trading platform or exchange (e.g., "binance"). +- **Trading Pair**: Choose the cryptocurrency trading pair (e.g., "BTC-USDT"). +- **Leverage**: Set the leverage ratio (use 1 for spot trading). + +### 2. Chart Configuration + +Configure how you want to visualize the market data: +- **Candles Connector**: Select the data source for candlestick data. +- **Interval**: Choose the timeframe for the candlesticks (1m to 1d). +- **Days to Display**: Select how many days of historical data to show. + +### 3. Grid Ranges + +Configure up to 5 grid ranges with different parameters: +- **Number of Grid Ranges**: Select how many ranges you want to configure (1-5). +- **Side**: Choose BUY or SELL for each range. +- **Start Price**: The price where the range begins. +- **End Price**: The price where the range ends. +- **Amount %**: Percentage of total amount allocated to this range. + +### 4. Advanced Configuration + +Fine-tune your strategy with advanced parameters: +- **Position Mode**: Choose between HEDGE or ONE-WAY. +- **Time Limit**: Maximum duration for orders (in hours). +- **Activation Bounds**: Price deviation to trigger updates. +- **Min Spread Between Orders**: Minimum price difference between orders. +- **Min Order Amount**: Minimum size for individual orders. +- **Max Open Orders**: Maximum number of active orders per range. +- **Grid Range Update Interval**: How often to update grid ranges (in seconds). + +## Understanding Grid Strike Strategy + +The Grid Strike strategy creates a grid of orders within specified price ranges. Here's how it works: + +### Grid Range Mechanics +- Each grid range defines a price zone where the strategy will place orders +- BUY ranges place buy orders from higher to lower prices +- SELL ranges place sell orders from lower to higher prices +- Multiple ranges can work simultaneously with different configurations + +### Order Placement +- Orders are placed within each range based on the min spread between orders +- The amount per order is calculated based on the range's allocation percentage +- Orders are automatically adjusted when price moves beyond activation bounds + +### Visual Indicators +- Green lines represent BUY ranges +- Red lines represent SELL ranges +- Different dash patterns distinguish multiple ranges of the same side +- Horizontal lines show the start and end prices of each range + +## Best Practices + +1. **Range Placement** + - Place BUY ranges below current price + - Place SELL ranges above current price + - Avoid overlapping ranges of the same side + +2. **Amount Allocation** + - Distribute amounts across ranges based on your risk preference + - Ensure total allocation across all ranges doesn't exceed 100% + - Consider larger allocations for ranges closer to current price + +3. **Spread Configuration** + - Set min spread based on the asset's volatility + - Larger spreads mean fewer, more profitable orders + - Smaller spreads mean more frequent, less profitable orders + +4. **Risk Management** + - Use appropriate leverage (1 for spot) + - Set reasonable time limits for orders + - Monitor and adjust activation bounds based on market conditions + +## Example Configuration + +Here's a sample configuration for a BTC-USDT grid: diff --git a/frontend/pages/config/grid_strike/__init__.py b/frontend/pages/config/grid_strike/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/frontend/pages/config/grid_strike/app.py b/frontend/pages/config/grid_strike/app.py new file mode 100644 index 00000000..e2d450d1 --- /dev/null +++ b/frontend/pages/config/grid_strike/app.py @@ -0,0 +1,148 @@ +import plotly.graph_objects as go +import streamlit as st +from hummingbot.core.data_type.common import TradeType +from plotly.subplots import make_subplots + +from frontend.components.config_loader import get_default_config_loader +from frontend.components.save_config import render_save_config +from frontend.pages.config.grid_strike.user_inputs import user_inputs +from frontend.pages.config.utils import get_candles +from frontend.st_utils import get_backend_api_client, initialize_st_page +from frontend.visualization import theme +from frontend.visualization.candles import get_candlestick_trace +from frontend.visualization.utils import add_traces_to_fig + + +def get_grid_range_traces(grid_ranges): + """Generate horizontal line traces for grid ranges with different colors.""" + dash_styles = ['solid', 'dash', 'dot', 'dashdot', 'longdash'] # 5 different styles + traces = [] + buy_count = 0 + sell_count = 0 + for i, grid_range in enumerate(grid_ranges): + # Set color based on trade type + if grid_range["side"] == TradeType.BUY: + color = 'rgba(0, 255, 0, 1)' # Bright green for buy + dash_style = dash_styles[buy_count % len(dash_styles)] + buy_count += 1 + else: + color = 'rgba(255, 0, 0, 1)' # Bright red for sell + dash_style = dash_styles[sell_count % len(dash_styles)] + sell_count += 1 + # Start price line + traces.append(go.Scatter( + x=[], # Will be set to full range when plotting + y=[float(grid_range["start_price"]), float(grid_range["start_price"])], + mode='lines', + line=dict(color=color, width=1.5, dash=dash_style), + name=f'Range {i} Start: {float(grid_range["start_price"]):,.2f} ({grid_range["side"].name})', + hoverinfo='name' + )) + # End price line + traces.append(go.Scatter( + x=[], # Will be set to full range when plotting + y=[float(grid_range["end_price"]), float(grid_range["end_price"])], + mode='lines', + line=dict(color=color, width=1.5, dash=dash_style), + name=f'Range {i} End: {float(grid_range["end_price"]):,.2f} ({grid_range["side"].name})', + hoverinfo='name' + )) + return traces + + +# Initialize the Streamlit page +initialize_st_page(title="Grid Strike", icon="📊", initial_sidebar_state="expanded") +backend_api_client = get_backend_api_client() + +get_default_config_loader("grid_strike") +# User inputs +inputs = user_inputs() +st.session_state["default_config"].update(inputs) + +# Load candle data +candles = get_candles( + connector_name=inputs["connector_name"], + trading_pair=inputs["trading_pair"], + interval=inputs["interval"], + days=inputs["days_to_visualize"] +) + +# Create a subplot with just 1 row for price action +fig = make_subplots( + rows=1, cols=1, + subplot_titles=(f'Grid Strike - {inputs["trading_pair"]} ({inputs["interval"]})',), +) + +# Add basic candlestick chart +candlestick_trace = get_candlestick_trace(candles) +add_traces_to_fig(fig, [candlestick_trace], row=1, col=1) + +# Add grid range visualization +grid_traces = get_grid_range_traces(inputs["grid_ranges"]) +for trace in grid_traces: + # Set the x-axis range for all grid traces + trace.x = [candles.index[0], candles.index[-1]] + fig.add_trace(trace, row=1, col=1) + +# Update y-axis to make sure all grid ranges and candles are visible +all_prices = [] +# Add candle prices +all_prices.extend(candles['high'].tolist()) +all_prices.extend(candles['low'].tolist()) +# Add grid range prices +for grid_range in inputs["grid_ranges"]: + all_prices.extend([float(grid_range["start_price"]), float(grid_range["end_price"])]) + +y_min, y_max = min(all_prices), max(all_prices) +padding = (y_max - y_min) * 0.1 # Add 10% padding +fig.update_yaxes(range=[y_min - padding, y_max + padding]) + +# Update layout for better visualization +layout_updates = { + "legend": dict( + yanchor="top", + y=0.99, + xanchor="left", + x=0.01, + bgcolor="rgba(0,0,0,0.5)" + ), + "hovermode": 'x unified', + "showlegend": True, + "height": 600, # Make the chart taller + "yaxis": dict( + fixedrange=False, # Allow y-axis zooming + autorange=True, # Enable auto-ranging + ) +} + +# Merge the default theme with our updates +fig.update_layout( + **(theme.get_default_layout() | layout_updates) +) + +# Use Streamlit's functionality to display the plot +st.plotly_chart(fig, use_container_width=True) + + +# Add after user inputs and before saving +def prepare_config_for_save(config): + """Prepare config for JSON serialization.""" + prepared_config = config.copy() + grid_ranges = [] + for grid_range in prepared_config["grid_ranges"]: + grid_range = grid_range.copy() + grid_range["side"] = grid_range["side"].value + grid_range["open_order_type"] = grid_range["open_order_type"].value + grid_range["take_profit_order_type"] = grid_range["take_profit_order_type"].value + grid_ranges.append(grid_range) + prepared_config["grid_ranges"] = grid_ranges + prepared_config["position_mode"] = prepared_config["position_mode"].value + del prepared_config["candles_connector"] + del prepared_config["interval"] + del prepared_config["days_to_visualize"] + return prepared_config + + +# Replace the render_save_config line with: +render_save_config(st.session_state["default_config"]["id"], + prepare_config_for_save(st.session_state["default_config"])) diff --git a/frontend/pages/config/grid_strike/user_inputs.py b/frontend/pages/config/grid_strike/user_inputs.py new file mode 100644 index 00000000..73c977f5 --- /dev/null +++ b/frontend/pages/config/grid_strike/user_inputs.py @@ -0,0 +1,234 @@ +from decimal import Decimal + +import plotly.graph_objs as go +import streamlit as st +from hummingbot.core.data_type.common import OrderType, PositionMode, TradeType + +from frontend.pages.config.utils import get_candles + + +def get_price_range_defaults(connector_name: str, trading_pair: str, interval: str, days: int = 7): + """Fetch candles and compute default price range based on recent min/max prices.""" + try: + candles = get_candles( + connector_name=connector_name, + trading_pair=trading_pair, + interval=interval, + days=days + ) + min_price = float(candles['low'].quantile(0.05)) + max_price = float(candles['high'].quantile(0.95)) + return round(min_price, 2), round(max_price, 2) + except Exception as e: + st.warning(f"Could not fetch price data: {str(e)}. Using default values.") + return 40000.0, 60000.0 # Fallback defaults + + +def get_grid_range_traces(grid_ranges): + """Generate horizontal line traces for grid ranges with different colors.""" + dash_styles = ['solid', 'dash', 'dot', 'dashdot', 'longdash'] # 5 different styles + traces = [] + buy_count = 0 + sell_count = 0 + for i, grid_range in enumerate(grid_ranges): + # Set color based on trade type + if grid_range["side"] == TradeType.BUY: + color = 'rgba(0, 255, 0, 1)' # Bright green for buy + dash_style = dash_styles[buy_count % len(dash_styles)] + buy_count += 1 + else: + color = 'rgba(255, 0, 0, 1)' # Bright red for sell + dash_style = dash_styles[sell_count % len(dash_styles)] + sell_count += 1 + # Start price line + traces.append(go.Scatter( + x=[], # Will be set to full range when plotting + y=[float(grid_range["start_price"]), float(grid_range["start_price"])], + mode='lines', + line=dict(color=color, width=1.5, dash=dash_style), + name=f'Range {i} Start: {float(grid_range["start_price"]):,.2f} ({grid_range["side"].name})', + hoverinfo='name' + )) + # End price line + traces.append(go.Scatter( + x=[], # Will be set to full range when plotting + y=[float(grid_range["end_price"]), float(grid_range["end_price"])], + mode='lines', + line=dict(color=color, width=1.5, dash=dash_style), + name=f'Range {i} End: {float(grid_range["end_price"]):,.2f} ({grid_range["side"].name})', + hoverinfo='name' + )) + return traces + + +def user_inputs(): + # Split the page into two columns for the expanders + left_col, right_col = st.columns(2) + with left_col: + # Basic trading parameters + with st.expander("Basic Configuration", expanded=True): + c1, c2, c3 = st.columns(3) + with c1: + connector_name = st.text_input("Connector Name", value="binance") + with c2: + trading_pair = st.text_input("Trading Pair", value="BTC-USDT") + with c3: + leverage = st.number_input("Leverage", min_value=1, value=20) + # Visualization Configuration + with st.expander("Chart Configuration", expanded=True): + c1, c2, c3 = st.columns(3) + with c1: + candles_connector = st.text_input( + "Candles Connector", + value=connector_name, # Use same connector as trading by default + help="Connector to fetch price data from" + ) + with c2: + interval = st.selectbox( + "Interval", + options=["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "12h", "1d"], + index=10, # Default to 30m + help="Candlestick interval" + ) + with c3: + days_to_visualize = st.number_input( + "Days to Display", + min_value=1, + max_value=365, + value=180, + help="Number of days of historical data to display" + ) + + # Get default price ranges based on current market data + default_min, default_max = get_price_range_defaults( + candles_connector, + trading_pair, + interval, + days_to_visualize + ) + # Grid Ranges Configuration + with st.expander("Grid Ranges", expanded=True): + grid_ranges = [] + num_ranges = st.number_input("Number of Grid Ranges", min_value=1, max_value=5, value=1) + for i in range(num_ranges): + st.markdown(f"#### Range {i}") + # Price configuration + c1, c2, c3, c4 = st.columns(4) + with c1: + # Set default start price based on side + side = st.selectbox( + f"Side {i}", + options=[TradeType.BUY.name, TradeType.SELL.name], + key=f"side_{i}" + ) + with c2: + # Set default start price based on side + start_price = st.number_input( + f"Start Price {i}", + value=default_min, + key=f"start_price_{i}" + ) + with c3: + # Set default end price based on side + end_price = st.number_input( + f"End Price {i}", + value=default_max, + key=f"end_price_{i}" + ) + with c4: + total_amount_pct = st.number_input( + f"Amount % {i}", + min_value=0.0, + max_value=100.0, + value=50.0, + key=f"amount_pct_{i}" + ) + st.markdown("---") + grid_ranges.append({ + "id": f"R{i}", + "start_price": Decimal(str(start_price)), + "end_price": Decimal(str(end_price)), + "total_amount_pct": Decimal(str(total_amount_pct/100)), + "side": TradeType[side], + "open_order_type": OrderType.LIMIT_MAKER, + "take_profit_order_type": OrderType.LIMIT + }) + with right_col: + # Amount configuration + with st.expander("Amount Configuration", expanded=True): + total_amount_quote = st.number_input( + "Total Amount (Quote)", + min_value=0.0, + value=1000.0, + help="Total amount in quote currency to use for trading" + ) + min_order_amount = st.number_input( + "Minimum Order Amount", + min_value=1.0, + value=10.0, + help="Minimum amount for each order" + ) + # Advanced Configuration + with st.expander("Advanced Configuration", expanded=True): + position_mode = st.selectbox( + "Position Mode", + options=["HEDGE", "ONEWAY"], + index=0 + ) + c1, c2 = st.columns(2) + with c1: + time_limit = st.number_input( + "Time Limit (hours)", + min_value=1, + value=48, + help="Strategy time limit in hours" + ) + min_spread = st.number_input( + "Min Spread Between Orders", + min_value=0.0000, + value=0.0001, + format="%.4f", # Show 3 decimal places + help="Minimum price difference between orders", + step=0.0001 + ) + activation_bounds = st.number_input( + "Activation Bounds", + min_value=0.0, + value=0.01, + format="%.4f", + help="Price deviation to trigger updates" + ) + with c2: + max_open_orders = st.number_input( + "Maximum Open Orders", + min_value=1, + value=5, + help="Maximum number of open orders" + ) + grid_update_interval = st.number_input( + "Grid Update Interval (s)", + min_value=1, + value=60, + help="How often to update grid ranges" + ) + + return { + "controller_name": "grid_strike", + "controller_type": "generic", + "connector_name": connector_name, + "candles_connector": candles_connector, + "trading_pair": trading_pair, + "interval": interval, + "days_to_visualize": days_to_visualize, + "leverage": leverage, + "total_amount_quote": Decimal(str(total_amount_quote)), + "grid_ranges": grid_ranges, + "position_mode": PositionMode[position_mode], + "time_limit": time_limit * 60 * 60, + "activation_bounds": Decimal(str(activation_bounds)), + "min_spread_between_orders": Decimal(str(min_spread)) if min_spread > 0 else None, + "min_order_amount": Decimal(str(min_order_amount)), + "max_open_orders": max_open_orders, + "grid_range_update_interval": grid_update_interval, + "extra_balance_base_usd": Decimal("10") + } diff --git a/frontend/pages/permissions.py b/frontend/pages/permissions.py index bca3e9e7..383ade4f 100644 --- a/frontend/pages/permissions.py +++ b/frontend/pages/permissions.py @@ -8,6 +8,7 @@ def main_page(): def public_pages(): return [ Section("Config Generator", "🎛️"), + Page("frontend/pages/config/grid_strike/app.py", "Grid Strike", "🎳"), Page("frontend/pages/config/pmm_simple/app.py", "PMM Simple", "👨‍🏫"), Page("frontend/pages/config/pmm_dynamic/app.py", "PMM Dynamic", "👩‍🏫"), Page("frontend/pages/config/dman_maker_v2/app.py", "D-Man Maker V2", "🤖"),