diff --git a/public/lang/en.json b/public/lang/en.json index 8839cf40..b373fd53 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -1446,6 +1446,13 @@ "tooltip": "Minimum profit to consider for trading", "link": "https://quantframe.app/features/live-trading/settings/item/wts#min-profit" }, + "min_profit_percentage": { + "label": "Minimum Profit Percentage (%)", + "placeholder": "Minimum Profit Percentage", + "error": "Invalid minimum profit percentage", + "tooltip": "Minimum profit as a percentage of the purchase price. Both this and Minimum Profit are enforced. Set to -1 to disable.", + "link": "https://quantframe.app/features/live-trading/settings/item/wts#min-profit-percentage" + }, "max_price_drop": { "label": "Max Price Drop", "placeholder": "Max Price Drop", diff --git a/src-tauri/src/app/types/settings/item_settings.rs b/src-tauri/src/app/types/settings/item_settings.rs index 14b2f2dc..3f7104a4 100644 --- a/src-tauri/src/app/types/settings/item_settings.rs +++ b/src-tauri/src/app/types/settings/item_settings.rs @@ -10,7 +10,7 @@ pub struct ItemSettings { impl ItemSettings { pub fn get_query_id(&self) -> String { format!( - "volume_threshold:{};profit_threshold:{};avg_price_cap:{};trading_tax_cap:{};max_total_price_cap:{};price_shift_threshold:{};buy_quantity:{};min_wtb_profit_margin:{};min_sma:{};min_profit:{}", + "volume_threshold:{};profit_threshold:{};avg_price_cap:{};trading_tax_cap:{};max_total_price_cap:{};price_shift_threshold:{};buy_quantity:{};min_wtb_profit_margin:{};min_sma:{};min_profit:{};min_profit_percentage:{}", self.wtb.volume_threshold, self.wtb.profit_threshold, self.wtb.avg_price_cap, @@ -20,7 +20,8 @@ impl ItemSettings { self.wtb.buy_quantity, self.wtb.min_wtb_profit_margin, self.wts.min_sma, - self.wts.min_profit + self.wts.min_profit, + self.wts.min_profit_percentage ) } } diff --git a/src-tauri/src/app/types/settings/item_wts_settings.rs b/src-tauri/src/app/types/settings/item_wts_settings.rs index 5298cdd7..95ba98c9 100644 --- a/src-tauri/src/app/types/settings/item_wts_settings.rs +++ b/src-tauri/src/app/types/settings/item_wts_settings.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; pub struct ItemWtsSettings { pub min_sma: i64, pub min_profit: i64, + pub min_profit_percentage: i64, pub max_price_drop: i64, pub min_listings_below: i64, } @@ -13,6 +14,7 @@ impl Default for ItemWtsSettings { Self { min_sma: 3, min_profit: 10, + min_profit_percentage: -1, max_price_drop: -1, min_listings_below: -1, } diff --git a/src-tauri/src/live_scraper/modules/item.rs b/src-tauri/src/live_scraper/modules/item.rs index 49436848..f1e63348 100644 --- a/src-tauri/src/live_scraper/modules/item.rs +++ b/src-tauri/src/live_scraper/modules/item.rs @@ -25,6 +25,14 @@ use crate::{ static COMPONENT: &str = "LiveScraper:Item:"; static LOG_FILE: &str = "live_scraper_item.log"; +fn minimum_profit_threshold(bought_price: i64, flat_profit: i64, percentage: i64) -> i64 { + if is_disabled(percentage) { + flat_profit + } else { + flat_profit.max(bought_price.saturating_mul(percentage).saturating_add(99) / 100) + } +} + #[derive(Debug)] pub struct ItemModule { client: Weak, @@ -745,9 +753,14 @@ impl ItemModule { stock_item.locked = true; } - // Ensure profit meets the minimum threshold by raising the price if needed + // Ensure profit meets the flat or percentage threshold by raising the price if needed let mut profit = post_price - bought_price; - let minimum_profit = min_profit.unwrap_or(settings.wts.min_profit); + let flat_minimum_profit = min_profit.unwrap_or(settings.wts.min_profit); + let minimum_profit = minimum_profit_threshold( + bought_price, + flat_minimum_profit, + settings.wts.min_profit_percentage, + ); if !is_disabled(minimum_profit) && profit < minimum_profit { let adjustment = minimum_profit - profit; @@ -1128,3 +1141,17 @@ impl ItemModule { fn comp(suffix: &str) -> String { return format!("{}{}", COMPONENT, suffix); } + +#[cfg(test)] +mod tests { + use super::minimum_profit_threshold; + + #[test] + fn combines_flat_and_percentage_profit_thresholds() { + assert_eq!(minimum_profit_threshold(20, -1, 25), 5); + assert_eq!(minimum_profit_threshold(200, -1, 25), 50); + assert_eq!(minimum_profit_threshold(1, -1, 1), 1); + assert_eq!(minimum_profit_threshold(20, 10, 25), 10); + assert_eq!(minimum_profit_threshold(20, 10, -1), 10); + } +} diff --git a/src/components/Forms/Settings/Tabs/LiveTrading/Tabs/Item/Accordion/WTS/index.tsx b/src/components/Forms/Settings/Tabs/LiveTrading/Tabs/Item/Accordion/WTS/index.tsx index a791dc63..187641e2 100644 --- a/src/components/Forms/Settings/Tabs/LiveTrading/Tabs/Item/Accordion/WTS/index.tsx +++ b/src/components/Forms/Settings/Tabs/LiveTrading/Tabs/Item/Accordion/WTS/index.tsx @@ -28,6 +28,15 @@ export const WTSItemAccordion = ({ form }: WTSItemAccordionProps) => { radius="md" {...form.getInputProps(getFieldPath("min_profit"))} /> + } + radius="md" + {...form.getInputProps(getFieldPath("min_profit_percentage"))} + />