mirror of
https://github.com/aljazceru/hummingbot-dashboard.git
synced 2026-01-06 06:54:25 +01:00
(feat) add config generator page
This commit is contained in:
0
pages/config_generator/README.md
Normal file
0
pages/config_generator/README.md
Normal file
244
pages/position_builder/app.py
Normal file
244
pages/position_builder/app.py
Normal file
@@ -0,0 +1,244 @@
|
||||
from math import exp
|
||||
import streamlit as st
|
||||
from plotly.subplots import make_subplots
|
||||
import plotly.graph_objects as go
|
||||
from decimal import Decimal
|
||||
|
||||
from utils.st_utils import initialize_st_page
|
||||
from hummingbot.smart_components.utils.distributions import Distributions
|
||||
|
||||
# Initialize the Streamlit page
|
||||
initialize_st_page(title="Position Generator", icon="🔭", initial_sidebar_state="collapsed")
|
||||
|
||||
# Page content
|
||||
st.text("This tool will help you analyze and generate a position config.")
|
||||
st.write("---")
|
||||
|
||||
# Layout in columns
|
||||
col_quote, col_tp_sl, col_levels, col_spread_dist, col_amount_dist = st.columns([1, 1, 1, 2, 2])
|
||||
|
||||
|
||||
def normalize(values):
|
||||
total = sum(values)
|
||||
return [val / total for val in values]
|
||||
|
||||
|
||||
with col_quote:
|
||||
total_amount_quote = st.number_input("Total amount of quote", value=1000)
|
||||
|
||||
with col_tp_sl:
|
||||
tp = st.number_input("Take Profit (%)", min_value=0.0, max_value=100.0, value=2.0, step=0.1)
|
||||
sl = st.number_input("Stop Loss (%)", min_value=0.0, max_value=100.0, value=8.0, step=0.1)
|
||||
|
||||
with col_levels:
|
||||
n_levels = st.number_input("Number of Levels", min_value=1, value=5)
|
||||
|
||||
|
||||
def distribution_inputs(column, dist_type_name):
|
||||
if dist_type_name == "Spread":
|
||||
dist_type = column.selectbox(
|
||||
f"Type of {dist_type_name} Distribution",
|
||||
("GeoCustom", "Geometric", "Fibonacci", "Manual", "Logarithmic", "Arithmetic"),
|
||||
key=f"{dist_type_name.lower()}_dist_type",
|
||||
# Set the default value
|
||||
)
|
||||
else:
|
||||
dist_type = column.selectbox(
|
||||
f"Type of {dist_type_name} Distribution",
|
||||
("Geometric", "Fibonacci", "Manual", "Logarithmic", "Arithmetic"),
|
||||
key=f"{dist_type_name.lower()}_dist_type",
|
||||
# Set the default value
|
||||
)
|
||||
base, scaling_factor, step, ratio, manual_values = None, None, None, None, None
|
||||
|
||||
if dist_type != "Manual":
|
||||
start = column.number_input(f"{dist_type_name} Start Value", value=1.0, key=f"{dist_type_name.lower()}_start")
|
||||
if dist_type == "Logarithmic":
|
||||
base = column.number_input(f"{dist_type_name} Log Base", value=exp(1), key=f"{dist_type_name.lower()}_base")
|
||||
scaling_factor = column.number_input(f"{dist_type_name} Scaling Factor", value=2.0, key=f"{dist_type_name.lower()}_scaling")
|
||||
elif dist_type == "Arithmetic":
|
||||
step = column.number_input(f"{dist_type_name} Step", value=0.1, key=f"{dist_type_name.lower()}_step")
|
||||
elif dist_type == "Geometric":
|
||||
ratio = column.number_input(f"{dist_type_name} Ratio", value=2.0, key=f"{dist_type_name.lower()}_ratio")
|
||||
elif dist_type == "GeoCustom":
|
||||
ratio = column.number_input(f"{dist_type_name} Ratio", value=2.0, key=f"{dist_type_name.lower()}_ratio")
|
||||
else:
|
||||
manual_values = [column.number_input(f"{dist_type_name} for level {i+1}", value=1.0, key=f"{dist_type_name.lower()}_{i}") for i in range(n_levels)]
|
||||
start = None # As start is not relevant for Manual type
|
||||
|
||||
return dist_type, start, base, scaling_factor, step, ratio, manual_values
|
||||
|
||||
|
||||
# Spread and Amount Distributions
|
||||
spread_dist_type, spread_start, spread_base, spread_scaling, spread_step, spread_ratio, manual_spreads = distribution_inputs(col_spread_dist, "Spread")
|
||||
amount_dist_type, amount_start, amount_base, amount_scaling, amount_step, amount_ratio, manual_amounts = distribution_inputs(col_amount_dist, "Amount")
|
||||
|
||||
|
||||
def get_distribution(dist_type, n_levels, start, base=None, scaling_factor=None, step=None, ratio=None, manual_values=None):
|
||||
if dist_type == "Manual":
|
||||
return manual_values
|
||||
elif dist_type == "Linear":
|
||||
return Distributions.linear(n_levels, start, start + tp)
|
||||
elif dist_type == "Fibonacci":
|
||||
return Distributions.fibonacci(n_levels, start)
|
||||
elif dist_type == "Logarithmic":
|
||||
return Distributions.logarithmic(n_levels, base, scaling_factor, start)
|
||||
elif dist_type == "Arithmetic":
|
||||
return Distributions.arithmetic(n_levels, start, step)
|
||||
elif dist_type == "Geometric":
|
||||
return Distributions.geometric(n_levels, start, ratio)
|
||||
elif dist_type == "GeoCustom":
|
||||
return [Decimal("0")] + Distributions.geometric(n_levels - 1, start, ratio)
|
||||
|
||||
spread_distribution = get_distribution(spread_dist_type, n_levels, spread_start, spread_base, spread_scaling, spread_step, spread_ratio, manual_spreads)
|
||||
amount_distribution = normalize(get_distribution(amount_dist_type, n_levels, amount_start, amount_base, amount_scaling, amount_step, amount_ratio, manual_amounts))
|
||||
order_amounts = [Decimal(amount_dist * total_amount_quote) for amount_dist in amount_distribution]
|
||||
spreads = [spread - spread_distribution[0] for spread in spread_distribution]
|
||||
break_even_values = []
|
||||
take_profit_values = []
|
||||
for level in range(n_levels):
|
||||
spreads_normalized = [Decimal(spread) + Decimal(0.01) for spread in spreads[:level+1]]
|
||||
amounts = order_amounts[:level+1]
|
||||
break_even = (sum([spread * amount for spread, amount in zip(spreads_normalized, amounts)]) / sum(amounts)) - Decimal(0.01)
|
||||
break_even_values.append(break_even)
|
||||
take_profit_values.append(break_even - Decimal(tp))
|
||||
|
||||
accumulated_amount = [sum(order_amounts[:i+1]) for i in range(len(order_amounts))]
|
||||
|
||||
def calculate_unrealized_pnl(spreads, break_even_values, accumulated_amount):
|
||||
unrealized_pnl = []
|
||||
for i in range(len(spreads)):
|
||||
distance = abs(spreads[i] - break_even_values[i])
|
||||
pnl = accumulated_amount[i] * distance / 100 # PNL calculation
|
||||
unrealized_pnl.append(pnl)
|
||||
return unrealized_pnl
|
||||
|
||||
# Calculate unrealized PNL
|
||||
cum_unrealized_pnl = calculate_unrealized_pnl(spreads, break_even_values, accumulated_amount)
|
||||
|
||||
|
||||
tech_colors = {
|
||||
'spread': '#00BFFF', # Deep Sky Blue
|
||||
'break_even': '#FFD700', # Gold
|
||||
'take_profit': '#32CD32', # Green
|
||||
'order_amount': '#1E90FF', # Dodger Blue
|
||||
'cum_amount': '#4682B4', # Steel Blue
|
||||
'stop_loss': '#FF0000', # Red
|
||||
}
|
||||
|
||||
# Create Plotly figure with secondary y-axis and a dark theme
|
||||
fig = make_subplots(specs=[[{"secondary_y": True}]])
|
||||
fig.update_layout(template="plotly_dark")
|
||||
|
||||
# Update the Scatter Plots and Horizontal Lines
|
||||
fig.add_trace(go.Scatter(x=list(range(len(spreads))), y=spreads, name='Spread (%)', mode='lines+markers', line=dict(width=3, color=tech_colors['spread'])), secondary_y=False)
|
||||
fig.add_trace(go.Scatter(x=list(range(len(break_even_values))), y=break_even_values, name='Break Even (%)', mode='lines+markers', line=dict(width=3, color=tech_colors['break_even'])), secondary_y=False)
|
||||
fig.add_trace(go.Scatter(x=list(range(len(take_profit_values))), y=take_profit_values, name='Take Profit (%)', mode='lines+markers', line=dict(width=3, color=tech_colors['take_profit'])), secondary_y=False)
|
||||
|
||||
# Add the new Bar Plot for Cumulative Unrealized PNL
|
||||
fig.add_trace(go.Bar(
|
||||
x=list(range(len(cum_unrealized_pnl))),
|
||||
y=cum_unrealized_pnl,
|
||||
text=[f"{pnl:.2f}" for pnl in cum_unrealized_pnl],
|
||||
textposition='auto',
|
||||
textfont=dict(color='white', size=12),
|
||||
name='Cum Unrealized PNL',
|
||||
marker=dict(color='#FFA07A', opacity=0.6) # Light Salmon color, adjust as needed
|
||||
), secondary_y=True)
|
||||
|
||||
fig.add_trace(go.Bar(
|
||||
x=list(range(len(order_amounts))),
|
||||
y=order_amounts,
|
||||
text=[f"{amt:.2f}" for amt in order_amounts], # List comprehension to format text labels
|
||||
textposition='auto',
|
||||
textfont=dict(
|
||||
color='white',
|
||||
size=12
|
||||
),
|
||||
name='Order Amount',
|
||||
marker=dict(color=tech_colors['order_amount'], opacity=0.5),
|
||||
), secondary_y=True)
|
||||
|
||||
# Modify the Bar Plot for Accumulated Amount
|
||||
fig.add_trace(go.Bar(
|
||||
x=list(range(len(accumulated_amount))),
|
||||
y=accumulated_amount,
|
||||
text=[f"{amt:.2f}" for amt in accumulated_amount], # List comprehension to format text labels
|
||||
textposition='auto',
|
||||
textfont=dict(
|
||||
color='white',
|
||||
size=12
|
||||
),
|
||||
name='Cum Amount',
|
||||
marker=dict(color=tech_colors['cum_amount'], opacity=0.5),
|
||||
), secondary_y=True)
|
||||
|
||||
|
||||
# Add Horizontal Lines for Last Breakeven Price and Stop Loss Level
|
||||
last_break_even = break_even_values[-1]
|
||||
stop_loss_value = last_break_even + Decimal(sl)
|
||||
# Horizontal Lines for Last Breakeven and Stop Loss
|
||||
fig.add_hline(y=last_break_even, line_dash="dash", annotation_text=f"Global Break Even: {last_break_even:.2f} (%)", annotation_position="top left", line_color=tech_colors['break_even'])
|
||||
fig.add_hline(y=stop_loss_value, line_dash="dash", annotation_text=f"Stop Loss: {stop_loss_value:.2f} (%)", annotation_position="bottom right", line_color=tech_colors['stop_loss'])
|
||||
|
||||
# Update Annotations for Spread and Break Even
|
||||
for i, (spread, be_value, tp_value) in enumerate(zip(spreads, break_even_values, take_profit_values)):
|
||||
fig.add_annotation(x=i, y=spread, text=f"{spread:.2f}%", showarrow=True, arrowhead=1, yshift=10, xshift=-2, font=dict(color=tech_colors['spread']))
|
||||
fig.add_annotation(x=i, y=be_value, text=f"{be_value:.2f}%", showarrow=True, arrowhead=1, yshift=5, xshift=-2, font=dict(color=tech_colors['break_even']))
|
||||
fig.add_annotation(x=i, y=tp_value, text=f"{tp_value:.2f}%", showarrow=True, arrowhead=1, yshift=10, xshift=-2, font=dict(color=tech_colors['take_profit']))
|
||||
# Update Layout with a Dark Theme
|
||||
fig.update_layout(
|
||||
title="Spread, Accumulated Amount, Break Even, and Take Profit by Order Level",
|
||||
xaxis_title="Order Level",
|
||||
yaxis_title="Spread (%)",
|
||||
yaxis2_title="Amount (Quote)",
|
||||
height=800,
|
||||
width=1800,
|
||||
plot_bgcolor='rgba(0, 0, 0, 0)', # Transparent background
|
||||
paper_bgcolor='rgba(0, 0, 0, 0.1)', # Lighter shade for the paper
|
||||
font=dict(color='white') # Font color
|
||||
)
|
||||
|
||||
# Calculate metrics
|
||||
max_loss = total_amount_quote * Decimal(sl / 100)
|
||||
profit_per_level = [cum_amount * Decimal(tp / 100) for cum_amount in accumulated_amount]
|
||||
loots_to_recover = [max_loss / profit for profit in profit_per_level]
|
||||
|
||||
# Define a consistent annotation size and maximum value for the secondary y-axis
|
||||
circle_text = "●" # Unicode character for a circle
|
||||
max_secondary_value = max(max(accumulated_amount), max(order_amounts), max(cum_unrealized_pnl)) # Adjust based on your secondary y-axis data
|
||||
|
||||
# Determine an appropriate y-offset for annotations
|
||||
y_offset_secondary = max_secondary_value * Decimal(0.1) # Adjusts the height relative to the maximum value on the secondary y-axis
|
||||
|
||||
# Add annotations to the Plotly figure for the secondary y-axis
|
||||
for i, loot in enumerate(loots_to_recover):
|
||||
fig.add_annotation(
|
||||
x=i,
|
||||
y=max_secondary_value + y_offset_secondary, # Position above the maximum value using the offset
|
||||
text=f"{circle_text}<br>LTR: {round(loot, 2)}", # Circle symbol and loot value in separate lines
|
||||
showarrow=False,
|
||||
font=dict(size=16, color='purple'),
|
||||
xanchor="center", # Centers the text above the x coordinate
|
||||
yanchor="bottom", # Anchors the text at its bottom to avoid overlapping
|
||||
align="center",
|
||||
yref="y2" # Reference the secondary y-axis
|
||||
)
|
||||
# Add Max Loss Metric as an Annotation
|
||||
max_loss_annotation_text = f"Max Loss (Quote): {max_loss:.2f}"
|
||||
fig.add_annotation(
|
||||
x=max(len(spreads), len(break_even_values)) - 1, # Positioning the annotation to the right
|
||||
text=max_loss_annotation_text,
|
||||
showarrow=False,
|
||||
font=dict(size=20, color='white'),
|
||||
bgcolor='red', # Red background for emphasis
|
||||
xanchor="left",
|
||||
yanchor="top",
|
||||
yref="y2" # Reference the secondary y-axis
|
||||
)
|
||||
|
||||
st.write("---")
|
||||
|
||||
# Display in Streamlit
|
||||
st.plotly_chart(fig)
|
||||
|
||||
Reference in New Issue
Block a user