diff --git a/Cargo.lock b/Cargo.lock index 23df32a..ffcf564 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -237,6 +237,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" +[[package]] +name = "ears" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e741c53078ec208f07c837fca26cd2624f959140c52268ee0677ad3b8e9b2112" +dependencies = [ + "lazy_static", + "libc", + "pkg-config", +] + [[package]] name = "encoding_rs" version = "0.8.26" @@ -1168,6 +1179,7 @@ dependencies = [ "chrono", "dotenv", "dyn-clone", + "ears", "fern", "float-cmp", "futures-retry", diff --git a/Cargo.toml b/Cargo.toml index 29c4b4c..9702d35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,5 @@ merge = "0.1" futures-retry = "0.6" tungstenite = "0.12" tokio-tungstenite = "0.13" -dotenv = "0.15" \ No newline at end of file +dotenv = "0.15" +ears = "0.8.0" \ No newline at end of file diff --git a/sounds/smas-smb3_goal.wav b/sounds/smas-smb3_goal.wav new file mode 100644 index 0000000..15144bb Binary files /dev/null and b/sounds/smas-smb3_goal.wav differ diff --git a/sounds/smw2_boing.wav b/sounds/smw2_boing.wav new file mode 100644 index 0000000..2dc2845 Binary files /dev/null and b/sounds/smw2_boing.wav differ diff --git a/src/main.rs b/src/main.rs index a6da5ca..76f7dd7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ mod models; mod strategy; mod ticker; mod tests; +mod sounds; pub type BoxError = Box; diff --git a/src/managers.rs b/src/managers.rs index c50d06f..a4f737c 100644 --- a/src/managers.rs +++ b/src/managers.rs @@ -16,6 +16,7 @@ use crate::currency::SymbolPair; use crate::events::{ActionMessage, ActorMessage, Event}; use crate::models::{ActiveOrder, OrderBook, OrderForm, OrderKind, OrderMetadata, Position, PriceTicker}; use crate::strategy::{TrailingStop, MarketEnforce, PositionStrategy}; +use crate::sounds::{play_sound, MARKET_ORDER_PLACED_PATH}; pub type OptionUpdate = (Option>, Option>); @@ -519,6 +520,11 @@ impl OrderManager { } }; + // play sound if Market order is placed + if let OrderKind::Market = order_form.kind() { + play_sound(MARKET_ORDER_PLACED_PATH); + } + // TODO: return valid messages and events!111!!!1! Ok((None, None)) } diff --git a/src/sounds.rs b/src/sounds.rs new file mode 100644 index 0000000..e00dea1 --- /dev/null +++ b/src/sounds.rs @@ -0,0 +1,21 @@ +use ears::{AudioController, Sound}; +use log::error; + + + +pub const MARKET_ORDER_PLACED_PATH: &str = "sounds/smas-smb3_goal.wav"; +pub const LOSS_TO_BREAK_EVEN_PATH: &str = "sounds/smw2_boing.wav"; +pub const MIN_PROFIT_SOUND_PATH: &str = "sounds/smw_1-up.wav"; +pub const GOOD_PROFIT_SOUND_PATH: &str = "sounds/smw_power-up.wav"; + +pub fn play_sound(sound_path: &'static str) { + std::thread::spawn(move || { + match Sound::new(sound_path) { + Ok(mut sound) => { + sound.play(); + while sound.is_playing() {} + } + Err(e) => { error!("Could not play {}: {}", sound_path, e); } + } + }); +} \ No newline at end of file diff --git a/src/strategy.rs b/src/strategy.rs index 4faa02a..541e0ed 100644 --- a/src/strategy.rs +++ b/src/strategy.rs @@ -9,6 +9,8 @@ use crate::BoxError; use crate::events::{ActionMessage, Event, EventKind, EventMetadata}; use crate::managers::OptionUpdate; use crate::models::{ActiveOrder, OrderBook, OrderForm, OrderKind, OrderMetadata, Position, PositionProfitState, TradingFees}; +use crate::models::PositionProfitState::{Critical, Loss}; +use crate::sounds::{GOOD_PROFIT_SOUND_PATH, LOSS_TO_BREAK_EVEN_PATH, MIN_PROFIT_SOUND_PATH, play_sound}; /*************** * DEFINITIONS @@ -92,6 +94,29 @@ pub struct TrailingStop { impl TrailingStop { + fn play_sound_on_state(prev_position: &Position, current_position: &Position) { + if prev_position.profit_state().is_none() { + return; + } + + if current_position.profit_state().is_none() { + return; + } + + let prev_state = prev_position.profit_state().unwrap(); + let current_state = current_position.profit_state().unwrap(); + + // negative to positive + if let Loss | Critical = prev_state { + match current_state { + PositionProfitState::BreakEven => { play_sound(LOSS_TO_BREAK_EVEN_PATH); } + PositionProfitState::MinimumProfit => { play_sound(MIN_PROFIT_SOUND_PATH); } + PositionProfitState::Profit => { play_sound(GOOD_PROFIT_SOUND_PATH); } + _ => {} + } + } + } + fn print_status(&self, position: &Position) { match self.stop_percentages.get(&position.id()) { None => { @@ -181,7 +206,7 @@ impl Default for TrailingStop { impl PositionStrategy for TrailingStop { fn name(&self) -> String { - "Hidden Trailing Stop".into() + "Trailing Stop".into() } /// Sets the profit state of an open position @@ -220,6 +245,8 @@ impl PositionStrategy for TrailingStop { if prev.profit_state() == Some(state) { return (new_position, None, None); } + + TrailingStop::play_sound_on_state(&prev, &new_position); } None => return (new_position, None, None), }; @@ -320,6 +347,8 @@ impl PositionStrategy for TrailingStop { return (position, None, None); } + // if we get here we are with a profit/loss ration > 0.0 + let mut messages = vec![]; // if a stop loss order was previously set,