Compare commits
2 Commits
e5f848b9f4
...
0e154f4ced
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e154f4ced | |||
| b917a0297c |
@@ -59,7 +59,7 @@ defmodule Core.Bots.DCABot do
|
|||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@tick_interval 60_000 # 1 minute
|
@tick_interval 60_000 # 1 minute
|
||||||
@drop_threshold 0.04 # 4% price drop triggers auto-buy
|
@drop_threshold 0.03 # 3% price drop triggers auto-buy
|
||||||
@profit_threshold 0.20 # 20% total return triggers auto-sell
|
@profit_threshold 0.20 # 20% total return triggers auto-sell
|
||||||
|
|
||||||
# Public API
|
# Public API
|
||||||
@@ -156,6 +156,10 @@ defmodule Core.Bots.DCABot do
|
|||||||
def init(%{symbol: symbol, restore_state: restore_state}) do
|
def init(%{symbol: symbol, restore_state: restore_state}) do
|
||||||
account_id = Core.Client.account_id()
|
account_id = Core.Client.account_id()
|
||||||
|
|
||||||
|
# If no restore_state was explicitly provided, check if there's saved state for this symbol
|
||||||
|
restore_state =
|
||||||
|
restore_state || find_saved_state_for_symbol(symbol)
|
||||||
|
|
||||||
state =
|
state =
|
||||||
if restore_state do
|
if restore_state do
|
||||||
# Restoring from saved state
|
# Restoring from saved state
|
||||||
@@ -175,6 +179,7 @@ defmodule Core.Bots.DCABot do
|
|||||||
bot_shares: shares,
|
bot_shares: shares,
|
||||||
bot_cost_basis: cost_basis,
|
bot_cost_basis: cost_basis,
|
||||||
current_return_pct: return_pct,
|
current_return_pct: return_pct,
|
||||||
|
current_pl_dollars: 0.0,
|
||||||
sell_and_stop_pending: false
|
sell_and_stop_pending: false
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -190,6 +195,7 @@ defmodule Core.Bots.DCABot do
|
|||||||
bot_shares: 0,
|
bot_shares: 0,
|
||||||
bot_cost_basis: 0.0,
|
bot_cost_basis: 0.0,
|
||||||
current_return_pct: 0.0,
|
current_return_pct: 0.0,
|
||||||
|
current_pl_dollars: 0.0,
|
||||||
sell_and_stop_pending: false
|
sell_and_stop_pending: false
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -265,13 +271,18 @@ defmodule Core.Bots.DCABot do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_call(:position, _from, %{symbol: symbol, bot_shares: bot_shares, bot_cost_basis: bot_cost_basis, current_return_pct: current_return_pct, last_purchase_price: last_purchase_price, sell_and_stop_pending: sell_and_stop_pending} = state) do
|
def handle_call(:position, _from, %{symbol: symbol, bot_shares: bot_shares, bot_cost_basis: bot_cost_basis, current_return_pct: current_return_pct, current_pl_dollars: current_pl_dollars, last_price: last_price, last_purchase_price: last_purchase_price, sell_and_stop_pending: sell_and_stop_pending} = state) do
|
||||||
|
next_buy_price = if last_purchase_price, do: last_purchase_price * (1 - @drop_threshold), else: nil
|
||||||
|
|
||||||
position_info = %{
|
position_info = %{
|
||||||
symbol: symbol,
|
symbol: symbol,
|
||||||
shares: bot_shares,
|
shares: bot_shares,
|
||||||
cost_basis: bot_cost_basis,
|
cost_basis: bot_cost_basis,
|
||||||
avg_price: if(bot_shares > 0, do: bot_cost_basis / bot_shares, else: 0.0),
|
avg_price: if(bot_shares > 0, do: bot_cost_basis / bot_shares, else: 0.0),
|
||||||
|
current_price: last_price,
|
||||||
|
next_buy_price: next_buy_price,
|
||||||
return_pct: current_return_pct,
|
return_pct: current_return_pct,
|
||||||
|
pl_dollars: current_pl_dollars,
|
||||||
last_purchase_price: last_purchase_price,
|
last_purchase_price: last_purchase_price,
|
||||||
sell_and_stop_pending: sell_and_stop_pending
|
sell_and_stop_pending: sell_and_stop_pending
|
||||||
}
|
}
|
||||||
@@ -319,12 +330,14 @@ defmodule Core.Bots.DCABot do
|
|||||||
price = quote.last
|
price = quote.last
|
||||||
|
|
||||||
# Calculate current return
|
# Calculate current return
|
||||||
return_pct =
|
{return_pct, pl_dollars} =
|
||||||
if bot_shares > 0 && bot_cost_basis > 0 do
|
if bot_shares > 0 && bot_cost_basis > 0 do
|
||||||
current_value = bot_shares * price
|
current_value = bot_shares * price
|
||||||
Float.round((current_value - bot_cost_basis) / bot_cost_basis * 100, 2)
|
pct = Float.round((current_value - bot_cost_basis) / bot_cost_basis * 100, 2)
|
||||||
|
dollars = Float.round(current_value - bot_cost_basis, 2)
|
||||||
|
{pct, dollars}
|
||||||
else
|
else
|
||||||
0.0
|
{0.0, 0.0}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Log price and bot position status
|
# Log price and bot position status
|
||||||
@@ -335,7 +348,7 @@ defmodule Core.Bots.DCABot do
|
|||||||
Logger.info("#{symbol} current price: $#{price} | Bot: no position")
|
Logger.info("#{symbol} current price: $#{price} | Bot: no position")
|
||||||
end
|
end
|
||||||
|
|
||||||
state = %{state | last_price: price, current_return_pct: return_pct}
|
state = %{state | last_price: price, current_return_pct: return_pct, current_pl_dollars: pl_dollars}
|
||||||
|
|
||||||
# Check if we should auto-sell (position up 20%)
|
# Check if we should auto-sell (position up 20%)
|
||||||
state = maybe_auto_sell(state, price)
|
state = maybe_auto_sell(state, price)
|
||||||
@@ -452,7 +465,8 @@ defmodule Core.Bots.DCABot do
|
|||||||
bot_shares: 0,
|
bot_shares: 0,
|
||||||
bot_cost_basis: 0.0,
|
bot_cost_basis: 0.0,
|
||||||
last_purchase_price: nil,
|
last_purchase_price: nil,
|
||||||
current_return_pct: 0.0
|
current_return_pct: 0.0,
|
||||||
|
current_pl_dollars: 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
# Save state after reset
|
# Save state after reset
|
||||||
@@ -622,4 +636,12 @@ defmodule Core.Bots.DCABot do
|
|||||||
false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp find_saved_state_for_symbol(symbol) do
|
||||||
|
state_data = Core.Bots.BotPersistence.load_state()
|
||||||
|
|
||||||
|
Enum.find(state_data, fn bot_state ->
|
||||||
|
bot_state["symbol"] == symbol
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -306,13 +306,24 @@ defmodule TraderexWeb.DashboardLive do
|
|||||||
<:col :let={bot} label="Cost Basis">
|
<:col :let={bot} label="Cost Basis">
|
||||||
{format_currency(bot.cost_basis)}
|
{format_currency(bot.cost_basis)}
|
||||||
</:col>
|
</:col>
|
||||||
|
<:col :let={bot} label="Current Price">
|
||||||
|
{format_currency(Map.get(bot, :current_price))}
|
||||||
|
</:col>
|
||||||
<:col :let={bot} label="Avg Price">
|
<:col :let={bot} label="Avg Price">
|
||||||
{format_currency(bot.avg_price)}
|
{format_currency(bot.avg_price)}
|
||||||
</:col>
|
</:col>
|
||||||
|
<:col :let={bot} label="Next Buy Price">
|
||||||
|
{format_currency(Map.get(bot, :next_buy_price))}
|
||||||
|
</:col>
|
||||||
<:col :let={bot} label="Return">
|
<:col :let={bot} label="Return">
|
||||||
|
<div class="flex flex-col">
|
||||||
<span class={return_color(bot.return_pct)}>
|
<span class={return_color(bot.return_pct)}>
|
||||||
{format_percent_simple(bot.return_pct)}
|
{format_percent_simple(bot.return_pct)}
|
||||||
</span>
|
</span>
|
||||||
|
<span class={["text-sm", pl_color(Map.get(bot, :pl_dollars, 0))]}>
|
||||||
|
{format_currency(Map.get(bot, :pl_dollars, 0))}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</:col>
|
</:col>
|
||||||
<:col :let={bot} label="Actions">
|
<:col :let={bot} label="Actions">
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user