atr tracking
This commit is contained in:
1
.direnv/flake-inputs/j9250k63yp54q9r2m0xnca8lxjcfadv0-source
Symbolic link
1
.direnv/flake-inputs/j9250k63yp54q9r2m0xnca8lxjcfadv0-source
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/nix/store/j9250k63yp54q9r2m0xnca8lxjcfadv0-source
|
||||||
@@ -1 +0,0 @@
|
|||||||
/nix/store/vanbyn1mbsqmff9in675grd5lqpr69zl-source
|
|
||||||
@@ -41,7 +41,7 @@ NIX_ENFORCE_NO_NATIVE='1'
|
|||||||
export NIX_ENFORCE_NO_NATIVE
|
export NIX_ENFORCE_NO_NATIVE
|
||||||
NIX_HARDENING_ENABLE='bindnow format fortify fortify3 libcxxhardeningextensive libcxxhardeningfast pic relro stackclashprotection stackprotector strictoverflow zerocallusedregs'
|
NIX_HARDENING_ENABLE='bindnow format fortify fortify3 libcxxhardeningextensive libcxxhardeningfast pic relro stackclashprotection stackprotector strictoverflow zerocallusedregs'
|
||||||
export NIX_HARDENING_ENABLE
|
export NIX_HARDENING_ENABLE
|
||||||
NIX_LDFLAGS='-rpath /home/work/Documents/rust/invest-bot/outputs/out/lib -L/nix/store/2w8pppicnsddp3n61ar96cnv3r6iyrdh-rust-mixed/lib -L/nix/store/g7lir5wb7g1a31szgw6n5wa0kbsq04zd-openssl-3.6.0/lib -L/nix/store/2w8pppicnsddp3n61ar96cnv3r6iyrdh-rust-mixed/lib -L/nix/store/g7lir5wb7g1a31szgw6n5wa0kbsq04zd-openssl-3.6.0/lib'
|
NIX_LDFLAGS='-rpath /home/mrfluffy/Documents/projects/rust/vibe-invest/outputs/out/lib -L/nix/store/2w8pppicnsddp3n61ar96cnv3r6iyrdh-rust-mixed/lib -L/nix/store/g7lir5wb7g1a31szgw6n5wa0kbsq04zd-openssl-3.6.0/lib -L/nix/store/2w8pppicnsddp3n61ar96cnv3r6iyrdh-rust-mixed/lib -L/nix/store/g7lir5wb7g1a31szgw6n5wa0kbsq04zd-openssl-3.6.0/lib'
|
||||||
export NIX_LDFLAGS
|
export NIX_LDFLAGS
|
||||||
NIX_NO_SELF_RPATH='1'
|
NIX_NO_SELF_RPATH='1'
|
||||||
NIX_PKG_CONFIG_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu='1'
|
NIX_PKG_CONFIG_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu='1'
|
||||||
@@ -142,7 +142,7 @@ name='nix-shell-env'
|
|||||||
export name
|
export name
|
||||||
nativeBuildInputs='/nix/store/2w8pppicnsddp3n61ar96cnv3r6iyrdh-rust-mixed /nix/store/fgm3pz8486ksh3f94629lpb7xjr2wjp7-openssl-3.6.0-dev /nix/store/rvp7qlpf5jqvdckjy1afjb6aha6j8dxg-pkg-config-wrapper-0.29.2 /nix/store/fl02yv3ax1qf1xkq64ik8qz5bjxyyd71-cargo-deny-0.19.0 /nix/store/7va1z8il76ycxvyvgsbpr4bjk89lzj5a-cargo-edit-0.13.8 /nix/store/zrx7kmcgzax4s6fldam9hf6nmwcw5nks-cargo-watch-8.5.3 /nix/store/b42adwrm8v2lb1889x1zb8dxzf5ljqys-rust-analyzer-2026-02-02'
|
nativeBuildInputs='/nix/store/2w8pppicnsddp3n61ar96cnv3r6iyrdh-rust-mixed /nix/store/fgm3pz8486ksh3f94629lpb7xjr2wjp7-openssl-3.6.0-dev /nix/store/rvp7qlpf5jqvdckjy1afjb6aha6j8dxg-pkg-config-wrapper-0.29.2 /nix/store/fl02yv3ax1qf1xkq64ik8qz5bjxyyd71-cargo-deny-0.19.0 /nix/store/7va1z8il76ycxvyvgsbpr4bjk89lzj5a-cargo-edit-0.13.8 /nix/store/zrx7kmcgzax4s6fldam9hf6nmwcw5nks-cargo-watch-8.5.3 /nix/store/b42adwrm8v2lb1889x1zb8dxzf5ljqys-rust-analyzer-2026-02-02'
|
||||||
export nativeBuildInputs
|
export nativeBuildInputs
|
||||||
out='/home/work/Documents/rust/invest-bot/outputs/out'
|
out='/home/mrfluffy/Documents/projects/rust/vibe-invest/outputs/out'
|
||||||
export out
|
export out
|
||||||
outputBin='out'
|
outputBin='out'
|
||||||
outputDev='out'
|
outputDev='out'
|
||||||
@@ -173,7 +173,7 @@ preConfigurePhases=' updateAutotoolsGnuConfigScriptsPhase'
|
|||||||
declare -a preFixupHooks=('_moveToShare' '_multioutDocs' '_multioutDevs' )
|
declare -a preFixupHooks=('_moveToShare' '_multioutDocs' '_multioutDevs' )
|
||||||
preferLocalBuild='1'
|
preferLocalBuild='1'
|
||||||
export preferLocalBuild
|
export preferLocalBuild
|
||||||
prefix='/home/work/Documents/rust/invest-bot/outputs/out'
|
prefix='/home/mrfluffy/Documents/projects/rust/vibe-invest/outputs/out'
|
||||||
declare -a propagatedBuildDepFiles=('propagated-build-build-deps' 'propagated-native-build-inputs' 'propagated-build-target-deps' )
|
declare -a propagatedBuildDepFiles=('propagated-build-build-deps' 'propagated-native-build-inputs' 'propagated-build-target-deps' )
|
||||||
propagatedBuildInputs=''
|
propagatedBuildInputs=''
|
||||||
export propagatedBuildInputs
|
export propagatedBuildInputs
|
||||||
|
|||||||
@@ -115,6 +115,14 @@ impl TradingBot {
|
|||||||
Ok(bot)
|
Ok(bot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_entry_atrs(&self) -> HashMap<String, f64> {
|
||||||
|
self.strategy.entry_atrs.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_high_water_marks(&self) -> HashMap<String, f64> {
|
||||||
|
self.strategy.high_water_marks.clone()
|
||||||
|
}
|
||||||
|
|
||||||
// ── Persistence helpers ──────────────────────────────────────────
|
// ── Persistence helpers ──────────────────────────────────────────
|
||||||
|
|
||||||
fn load_json_map<V: serde::de::DeserializeOwned>(
|
fn load_json_map<V: serde::de::DeserializeOwned>(
|
||||||
|
|||||||
@@ -12,13 +12,23 @@ use std::path::Path;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tower_http::cors::CorsLayer;
|
use tower_http::cors::CorsLayer;
|
||||||
|
|
||||||
use crate::alpaca::AlpacaClient;
|
use crate::{
|
||||||
use crate::paths::LIVE_EQUITY_FILE;
|
alpaca::AlpacaClient,
|
||||||
use crate::types::EquitySnapshot;
|
config::{ATR_TRAIL_ACTIVATION_MULTIPLIER, ATR_TRAIL_MULTIPLIER},
|
||||||
|
paths::{LIVE_ENTRY_ATRS_FILE, LIVE_EQUITY_FILE, LIVE_HIGH_WATER_MARKS_FILE},
|
||||||
|
types::EquitySnapshot,
|
||||||
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub struct DashboardInitData {
|
||||||
|
pub entry_atrs: HashMap<String, f64>,
|
||||||
|
pub high_water_marks: HashMap<String, f64>,
|
||||||
|
}
|
||||||
|
|
||||||
/// Shared state for the dashboard.
|
/// Shared state for the dashboard.
|
||||||
pub struct DashboardState {
|
pub struct DashboardState {
|
||||||
pub client: AlpacaClient,
|
pub client: AlpacaClient,
|
||||||
|
pub init_data: DashboardInitData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -48,6 +58,8 @@ struct PositionResponse {
|
|||||||
unrealized_pnl: f64,
|
unrealized_pnl: f64,
|
||||||
pnl_pct: f64,
|
pnl_pct: f64,
|
||||||
change_today: f64,
|
change_today: f64,
|
||||||
|
trail_status: String,
|
||||||
|
stop_loss_price: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@@ -363,6 +375,8 @@ const HTML_TEMPLATE: &str = r#"<!DOCTYPE html>
|
|||||||
<div class="position-detail"><div class="position-detail-label">Current</div><div class="position-detail-value">${formatCurrency(pos.current_price)}</div></div>
|
<div class="position-detail"><div class="position-detail-label">Current</div><div class="position-detail-value">${formatCurrency(pos.current_price)}</div></div>
|
||||||
<div class="position-detail"><div class="position-detail-label">P&L</div><div class="position-detail-value ${pnlClass}">${formatCurrency(pos.unrealized_pnl, true)}</div></div>
|
<div class="position-detail"><div class="position-detail-label">P&L</div><div class="position-detail-value ${pnlClass}">${formatCurrency(pos.unrealized_pnl, true)}</div></div>
|
||||||
<div class="position-detail"><div class="position-detail-label">Today</div><div class="position-detail-value ${changeClass}">${changeSign}${pos.change_today.toFixed(2)}%</div></div>
|
<div class="position-detail"><div class="position-detail-label">Today</div><div class="position-detail-value ${changeClass}">${changeSign}${pos.change_today.toFixed(2)}%</div></div>
|
||||||
|
<div class="position-detail"><div class="position-detail-label">Trail Status</div><div class="position-detail-value">${pos.trail_status}</div></div>
|
||||||
|
<div class="position-detail"><div class="position-detail-label">Stop Loss</div><div class="position-detail-value">${formatCurrency(pos.stop_loss_price)}</div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
@@ -548,12 +562,38 @@ async fn api_positions(State(state): State<Arc<DashboardState>>) -> impl IntoRes
|
|||||||
Ok(positions) => {
|
Ok(positions) => {
|
||||||
let mut result: Vec<PositionResponse> = positions
|
let mut result: Vec<PositionResponse> = positions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| PositionResponse {
|
.map(|p| {
|
||||||
|
let entry_price = p.avg_entry_price.parse().unwrap_or(0.0);
|
||||||
|
let current_price = p.current_price.parse().unwrap_or(0.0);
|
||||||
|
let pnl_pct = if entry_price > 0.0 {
|
||||||
|
(current_price - entry_price) / entry_price
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
let entry_atr = state.init_data.entry_atrs.get(&p.symbol).copied().unwrap_or(0.0);
|
||||||
|
let high_water_mark = state.init_data.high_water_marks.get(&p.symbol).copied().unwrap_or(entry_price);
|
||||||
|
|
||||||
|
let activation_gain = if entry_atr > 0.0 {
|
||||||
|
(ATR_TRAIL_ACTIVATION_MULTIPLIER * entry_atr) / entry_price
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
|
||||||
|
let (trail_status, stop_loss_price) = if pnl_pct >= activation_gain && entry_atr > 0.0 {
|
||||||
|
let trail_distance = ATR_TRAIL_MULTIPLIER * entry_atr;
|
||||||
|
let stop_price = high_water_mark - trail_distance;
|
||||||
|
("Active".to_string(), stop_price)
|
||||||
|
} else {
|
||||||
|
("Inactive".to_string(), entry_price - ATR_TRAIL_MULTIPLIER * entry_atr)
|
||||||
|
};
|
||||||
|
|
||||||
|
PositionResponse {
|
||||||
symbol: p.symbol.clone(),
|
symbol: p.symbol.clone(),
|
||||||
qty: p.qty.parse().unwrap_or(0.0),
|
qty: p.qty.parse().unwrap_or(0.0),
|
||||||
market_value: p.market_value.parse().unwrap_or(0.0),
|
market_value: p.market_value.parse().unwrap_or(0.0),
|
||||||
avg_entry_price: p.avg_entry_price.parse().unwrap_or(0.0),
|
avg_entry_price: entry_price,
|
||||||
current_price: p.current_price.parse().unwrap_or(0.0),
|
current_price,
|
||||||
unrealized_pnl: p.unrealized_pl.parse().unwrap_or(0.0),
|
unrealized_pnl: p.unrealized_pl.parse().unwrap_or(0.0),
|
||||||
pnl_pct: p.unrealized_plpc.parse::<f64>().unwrap_or(0.0) * 100.0,
|
pnl_pct: p.unrealized_plpc.parse::<f64>().unwrap_or(0.0) * 100.0,
|
||||||
change_today: p
|
change_today: p
|
||||||
@@ -562,6 +602,9 @@ async fn api_positions(State(state): State<Arc<DashboardState>>) -> impl IntoRes
|
|||||||
.and_then(|s| s.parse::<f64>().ok())
|
.and_then(|s| s.parse::<f64>().ok())
|
||||||
.unwrap_or(0.0)
|
.unwrap_or(0.0)
|
||||||
* 100.0,
|
* 100.0,
|
||||||
|
trail_status,
|
||||||
|
stop_loss_price,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@@ -614,8 +657,12 @@ async fn api_orders(State(state): State<Arc<DashboardState>>) -> impl IntoRespon
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Start the dashboard web server.
|
/// Start the dashboard web server.
|
||||||
pub async fn start_dashboard(client: AlpacaClient, port: u16) -> anyhow::Result<()> {
|
pub async fn start_dashboard(
|
||||||
let state = Arc::new(DashboardState { client });
|
client: AlpacaClient,
|
||||||
|
port: u16,
|
||||||
|
init_data: DashboardInitData,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let state = Arc::new(DashboardState { client, init_data });
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(index))
|
.route("/", get(index))
|
||||||
|
|||||||
16
src/main.rs
16
src/main.rs
@@ -232,17 +232,27 @@ async fn run_live_trading(api_key: String, api_secret: String, args: Args) -> Re
|
|||||||
.parse()
|
.parse()
|
||||||
.unwrap_or(5000);
|
.unwrap_or(5000);
|
||||||
|
|
||||||
|
// Create the bot first to load its state
|
||||||
|
let mut bot = TradingBot::new(api_key.clone(), api_secret.clone(), args.timeframe).await?;
|
||||||
|
|
||||||
// Create a separate client for the dashboard
|
// Create a separate client for the dashboard
|
||||||
let dashboard_client = AlpacaClient::new(api_key.clone(), api_secret.clone())?;
|
let dashboard_client = AlpacaClient::new(api_key.clone(), api_secret.clone())?;
|
||||||
|
|
||||||
|
// Extract data for the dashboard
|
||||||
|
let init_data = dashboard::DashboardInitData {
|
||||||
|
entry_atrs: bot.get_entry_atrs(),
|
||||||
|
high_water_marks: bot.get_high_water_marks(),
|
||||||
|
};
|
||||||
|
|
||||||
// Spawn dashboard in background
|
// Spawn dashboard in background
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = dashboard::start_dashboard(dashboard_client, dashboard_port).await {
|
if let Err(e) =
|
||||||
|
dashboard::start_dashboard(dashboard_client, dashboard_port, init_data).await
|
||||||
|
{
|
||||||
tracing::error!("Dashboard error: {}", e);
|
tracing::error!("Dashboard error: {}", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Run the trading bot
|
// Now run the bot's main loop
|
||||||
let mut bot = TradingBot::new(api_key, api_secret, args.timeframe).await?;
|
|
||||||
bot.run().await
|
bot.run().await
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user