diff --git a/Cargo.lock b/Cargo.lock index 464b89e..8a4d612 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,18 +1,30 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] -name = "az" -version = "1.2.1" +name = "ahash" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] [[package]] -name = "bytemuck" -version = "1.15.0" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" @@ -21,42 +33,149 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "crunchy" -version = "0.2.2" +name = "getrandom" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "fixed" -version = "1.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc715d38bea7b5bf487fcd79bcf8c209f0b58014f3018a7a19c2b855f472048" -dependencies = [ - "az", - "bytemuck", - "half", - "typenum", -] - -[[package]] -name = "half" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", - "crunchy", + "libc", + "wasi", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + [[package]] name = "lzf1" version = "0.1.0" dependencies = [ - "fixed", + "hashbrown", + "rand", ] [[package]] -name = "typenum" -version = "1.17.0" +name = "once_cell" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "syn" +version = "2.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 90afe88..676625f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,10 +7,12 @@ debug = true [profile.dev] debug=2 +opt-level=0 + +[profile.release] +debug = true [dependencies] rand="0.8.5" -hashbrown="0.14" -finum={ path="../finum" } hashbrown="0.14.5" diff --git a/src/#main.rs# b/src/#main.rs# deleted file mode 100644 index 1b4af66..0000000 --- a/src/#main.rs# +++ /dev/null @@ -1,1056 +0,0 @@ -// -// When inserting a new Order into the royalties tree, we must make sure the existing -// royalties don't get shared with that new node. To do this, we must "shave" down -// the tree, pushing royalties from the root down to all nodes to the left of the -// new node. -// -// Case to think about: -// Selling 140000 USD to buy 2 BTC. Weight is ===140k USD -// Selling 50000 GBP to buy 1 BTC. Weight is === 50k GBP -// - -// Quick & Dirty Documentation -// -// Running lzf1 from a command line: -// lzf1 # --Interactive is the default mode, no need to -// lzf1 --log tuesday.log # Same as above, but log all commands to file tuesday.log -// lzf1 --replay tuesday.log # Replay tuesday.log, then go into interactive mode -// lzf1 --replay tuesday.log --log wednesday.log # Replay tuesday.log, then go interactive logging additional commands to wednesday.log -// lzf1 --replay tuesday.log --log tuesday.log # Replay tuesday.log, then go interactive, then append additional commands to tuesday.log -// Commands in Interactive Mode: -// addasset USD # Case sensitive, no spaces -// setroyalty USD 0.01 0.00 0.03 0.02 # Sets 1% royalty when order selling USD is placed, but no commission. -// # Then additional 3% royalty when order executes, plus 2% commission -// addtrader Teppy # Case sensitive, no spaces -// addfunds Teppy 20000 USD # Create funds from thin air. Do this when we receive a wire transfer, for instance. -// subfunds Teppy 0.5 BTC # Remove funds. Do this when we process a withdrawal. -// balances Teppy # Show all of Teppy's funds (but not what has been moved to the market) -// login Teppy # Some commands take an implicit Trader parameter. This sets that parameter -// whoami # Shows logged in name for this Interactive session -// wallet # Show all of the logged-in trader's funds (but not what is on the market) -// order 0.5 BTC 30000 USD # Create an order selling 0.5 BTC to buy 30000 USD. Uses logged in Trader's balance. -// orderbatch 0.5 BTC 30000 USD # Enter an order but don't execute it (allow it to contribute to a crossed market) -// quit # Clean exit -// -// - - -#![allow(unsafe_code)] -#![allow(unused_variables)] -#![allow(dead_code)] - -use std::fs::File; -use std::fs::OpenOptions; -use std::path::Path; -use std::io::{self, BufRead, Write}; -use std::env; -//use std::collections::HashMap; -use std::cmp::Ordering; -use std::rc::Rc; -use std::cell::RefCell; -use rand::prelude::*; -use rand::rngs::StdRng; -use hashbrown::HashMap; -mod finum; -use finum::FiNum; - -#[derive(Debug, Clone)] -struct Trader { - name: String, - password: String, // hash(name..salt..password) - id: usize, - balances: HashMap, // Maps Currency to Amount - } - -#[derive(Clone)] -struct Asset { - name: String, - royalty0_rate: FiNum, // Traders get this when entered - royalty1_rate: FiNum, // Traders get this when executed - commission0_rate: FiNum, // House gets this when entered - commission1_rate: FiNum, // House gets this when executed - } - -impl Asset { - fn new(name: &str) -> Self { - Asset { - name: String::from(name), - royalty0_rate:FiNum::zero(), - royalty1_rate:FiNum::zero(), - commission0_rate:FiNum::zero(), - commission1_rate:FiNum::zero(), - } - } - } - -impl Trader { - fn new(name:&str,id:usize) -> Self { - Trader { - name: String::from(name), - password: String::from(""), - id: id, - balances: HashMap::new() - } - } - fn add_balance(&mut self, cur:usize, delta:FiNum) { - self.balances.entry(cur).and_modify(|ent| *ent+=delta ).or_insert_with(|| delta); - } - fn sub_balance(&mut self, cur:usize, delta:FiNum) { - self.balances.entry(cur).and_modify(|ent| *ent-=delta ); - } - fn get_balance(&self, cur:usize) -> FiNum { - *self.balances.get(&cur).map(|bal| bal).unwrap_or(&FiNum::new(0u64)) - } - } - -#[derive(Debug, Clone)] -struct Order { - sell_qty: FiNum, - sell_remain: FiNum, - buy_qty: FiNum, - royalty_remain: FiNum, // Based on royalty1 - commission_remain: FiNum, // Based on commission1 - owner: usize, - rt_loc: usize, // Location in the Royalty Tree - pq_loc: usize, // Location in th Priority Queue - id: usize, - } - -struct RoyaltyTree { - tree: Vec, - next_entry: usize, - spare_change: FiNum, // Waiting to be distributed - } - -struct Royalty { - weight: FiNum, // Here and below - lazy: FiNum, // To be distributed to here and to below based on weights - acc: FiNum, // Accumulated here - } - -impl Royalty { - fn new() -> Self { - Royalty { weight:FiNum::zero(), lazy:FiNum::zero(), acc:FiNum::zero() } - } - } - -impl RoyaltyTree { - fn new() -> Self { - RoyaltyTree { tree:Vec::new(), next_entry:0, spare_change:FiNum::zero() } - } - fn acc_total(&self) -> FiNum { - let mut rval=FiNum::zero(); - for r in &self.tree { rval+= r.acc+r.lazy; } - rval - } - fn weight_here_below(&self, index:usize) -> FiNum { - self.tree[index].weight - } - fn weight_below(&self, index: usize) -> FiNum { - if index&1==0 { - FiNum::zero() - } else { - let w0=self.weight_here_below(wt_left (index).unwrap()); - let w1=self.weight_here_below(wt_right(index).unwrap()); - w0+w1 - } - } - fn weight_here(&self, index:usize) -> FiNum { - if index&1==0 { - self.weight_here_below(index) - } else { - let w0=self.weight_here_below(index); - let w1=self.weight_below(index); - w0-w1 - } - } - fn expand_to(&mut self, index: usize) -> &mut Self { - for _ in self.tree.len()..=index { self.tree.push(Royalty::new()) } - self - } - fn capture0(&mut self, index: usize) -> &mut Self { - if index&1==0 { - let lazy=self.tree[index].lazy; - self.tree[index].acc+=lazy; - self.tree[index].lazy=FiNum::zero(); - } else { - let lazy=self.tree[index].lazy; - let d1=if lazy>0.into() { lazy*self.weight_here(index)/self.weight_here_below(index) } else { FiNum::zero() }; - let d02=lazy-d1; - let index_left =wt_left (index).unwrap(); - let index_right=wt_right(index).unwrap(); - let d0=if d02>0.into() { d02*self.weight_here_below(index_left)/self.weight_below(index) } else { FiNum::zero() }; - let d2=d02-d0; - assert!(lazy==d0+d1+d2,"Distributing amounts that don't add up."); - self.tree[index_left ].lazy+=d0; - self.tree[index_right].lazy+=d2; - self.tree[index].acc+=d1; - self.tree[index].lazy=FiNum::zero(); - } - self - } - fn get_royalty(&mut self, index: usize) -> FiNum { - let mut f=self.forefather(); - while f!=index { - self.capture0(f); - f=if f>index { wt_left(f).unwrap() } else { wt_right(f).unwrap() } - } - self.capture0(f); - self.tree[index].acc - } - fn add_royalty(&mut self, amount: FiNum) { - if self.next_entry==0 { self.spare_change+=amount } - else { - let ff=self.forefather(); - if self.weight_here_below(ff).is_zero() { self.spare_change+=amount } else { self.tree[ff].lazy+=amount }; - } - } - fn add_weight(&mut self, index: usize, weight: FiNum) { - if self.tree.len()>0 { - let mut ff0=self.forefather(); - let ff1=wt_forefather(index); - let w=self.tree[ff0].weight; - self.expand_to(ff1*2); - while ff0 usize { - wt_forefather(self.tree.len()-1) - } - fn dump(&mut self) { - for index in 0..self.tree.len() { - let roy=self.get_royalty(index); - println!("Index {} Weight {} Lazy {} Acc {} Royalty {}",index,self.tree[index].weight,self.tree[index].lazy,self.tree[index].acc,roy); - } - } - } - - -trait Dumpable { - fn dump(&self); - } - -impl Dumpable for usize { - fn dump(&self) { - println!("Dump Integer: {}",self); - } - } - - -impl Dumpable for Rc> { - fn dump(&self) { - } - } - - -impl Dumpable for Order { - fn dump(&self) { - println!("Giving {}/{} to get {}",self.sell_remain,self.sell_qty,self.buy_qty); - } - } - -struct OrderLocs { - asset0: usize, - asset1: usize, - pq_loc: usize, - rt_loc: usize, - } - -struct Market { - asset_name2num: HashMap, - assets: Vec, - money_supply: HashMap, - traders: Vec, - trader_name2num: HashMap, - orders: HashMap<(usize,usize),OrderQueue>, - royalties: HashMap, // Active orders that are accepting asset X. They receive royalties when someone makes an order to sell X - royalty_rate: HashMap, - order_finder: HashMap, // To find an order, look in Orders.get(usize,usize) at position usize - next_order_id: usize, - } - -impl Market { - fn new() -> Self { - let mut rval=Market { - asset_name2num: HashMap::new(), - assets: Vec::new(), - money_supply: HashMap::new(), - traders: Vec::new(), - trader_name2num: HashMap::new(), - orders: HashMap::new(), - royalties: HashMap::new(), - royalty_rate: HashMap::new(), - order_finder: HashMap::new(), - next_order_id: 1, - }; - rval.register_trader("*HOUSE*"); - rval - } - fn sanity_check(&self) { - println!("Sanity Checking Market..."); - for (cur,amt) in self.money_supply.iter() { - println!("Money Supply {}: {}",self.number_to_name(*cur),*amt); - let mut acc_orders=FiNum::new(0); - for (ac,pq) in &self.orders { if ac.0==*cur { - for off in &*pq.v { acc_orders+=off.sell_remain; } - } } - let mut acc_traders=FiNum::new(0); - for t in &self.traders { acc_traders+=t.get_balance(*cur); } - let acc=acc_orders+acc_traders; - println!(" {}: Orders {} Traders {} Total {} Should Be {}",self.number_to_name(*cur),acc_orders,acc_traders,acc,*amt); - } - } - fn register_trader(&mut self, name:&str) -> usize { // Add error checking for inserting a trader twice - let rval=self.traders.len(); - self.trader_name2num.insert(String::from(name),self.traders.len()); - self.traders.push(Trader::new(name,rval)); - rval - } - // These are the only ways to get money into or out of the market. - fn get_trader_balance(&self, who:usize, cur:usize) -> FiNum { - self.traders[who].get_balance(cur) - } - fn add_trader_balance(&mut self, who:usize, cur:usize, delta: FiNum) { - self.traders[who].add_balance(cur,delta); - *self.money_supply.get_mut(&cur).unwrap()+=delta; - } - fn sub_trader_balance(&mut self, who:usize, cur:usize, delta: FiNum) { - self.traders[who].sub_balance(cur,delta); - *self.money_supply.get_mut(&cur).unwrap()-=delta; - } - fn register_asset(&mut self, name:&str) -> Option { - if self.asset_name2num.contains_key(name) { return None } - self.assets.push(Asset::new(name)); - self.asset_name2num.insert(String::from(name),self.assets.len()-1); - self.money_supply.insert(self.assets.len()-1,FiNum::new(0)); - let rval=self.assets.len()-1; - Some(rval) - } - fn set_royalty(&mut self, n: usize, roy0: FiNum, com0: FiNum, roy1: FiNum, com1: FiNum) { - self.assets[n].royalty0_rate =roy0; - self.assets[n].commission0_rate=com0; - self.assets[n].royalty1_rate =roy1; - self.assets[n].commission1_rate=com1; - } - fn number_to_asset(&self, n: usize) -> Asset { - self.assets[n].clone() - } - fn number_to_name(&self, n: usize) -> String { - self.assets[n].name.clone() - } - fn name_to_number(&self, name:&str) -> Option<&usize> { - self.asset_name2num.get(name) - } - fn dump(&mut self) { - println!("Dumping Market:"); - println!("Money Supply:"); - for index in 0..self.assets.len() { - println!(" {}: {}",self.number_to_asset(index).name,self.money_supply[&index]); - } - for (ap,pq) in &self.orders { - println!("Orders selling {} to buy {}:",self.number_to_name(ap.0),self.number_to_name(ap.1)); - let mut sorted=pq.v.clone(); - sorted.sort_by(|a,b| { (a.sell_qty/a.buy_qty).cmp(&(b.sell_qty/b.buy_qty)) }); - for off in sorted.iter() { - println!(" {} @ {} ({}) OrderID: {} PQLoc: {} RTLoc: {} Royalties: {}", - off.sell_remain, // off.buy_qty*off.sell_remain/off.sell_qty, - off.buy_qty/off.sell_qty, - self.traders[off.owner as usize].name, - off.id,off.pq_loc,off.rt_loc, - self.royalties.get_mut(&ap.1).unwrap().get_royalty(off.rt_loc)); - //,self.royalties.get(&ap.1).unwrap().tree[off.rt_loc].acc); - } - } - println!("Royalties accumulated but not captured:"); - for index in 0..self.assets.len() { - let tot=self.royalties.get(&index).unwrap().acc_total(); - println!(" For {}: {}",self.number_to_asset(index).name,tot); - } - println!("Trader Balances:"); - for t in &self.traders { - println!(" Trader {}: {}",t.id,t.name); - for (cur,bal) in t.balances.iter() { - println!(" {}: {}",self.number_to_name(*cur),*bal) - } - } - } - fn replay_file(&mut self, fname:&str) { - println!("Replaying {}",fname); - let f=File::open(Path::new(fname)); - let reader=io::BufReader::new(f.unwrap()); - for line in reader.lines() { - if let Ok(line) = line { - let cmd=Command::deserialize(line); - if let Command::NOP(comment)=cmd { println!("{}",comment); } - else { - let res=self.execute(&cmd); - println!("{}",res.describe()); - } - } - } - } - fn execute_batch(&mut self, asset_type0: usize, strike0: FiNum, asset_type1: usize, strike1: FiNum) -> Result { - let mut log:Vec=Vec::new(); - let asset0=self.number_to_asset(asset_type0); - let asset1=self.number_to_asset(asset_type1); - log.push(format!("Executing batch {} {} <-> {} {}",strike0,asset0.name,strike1,asset1.name)); - let ap0=(asset_type0,asset_type1); - let ap1=(asset_type1,asset_type0); - let [bids0,bids1]=self.orders.get_many_mut([&ap0, &ap1]).unwrap(); - while !bids0.empty() && !bids1.empty() - && bids0.peek().sell_qty/bids0.peek().buy_qty>=strike0/strike1 - && bids1.peek().sell_qty/bids1.peek().buy_qty>=strike1/strike0 { - let asset0_paying =std::cmp::min(bids0.peek().sell_remain,strike0*bids1.peek().sell_remain/bids1.peek().sell_qty); - let asset1_paying =std::cmp::min(bids1.peek().sell_remain,strike1*bids0.peek().sell_remain/bids0.peek().sell_qty); - bids0.peek().sell_remain-=asset0_paying; - bids1.peek().sell_remain-=asset1_paying; - self.traders[bids0.peek().owner].add_balance(asset_type1,asset1_paying); - self.traders[bids1.peek().owner].add_balance(asset_type0,asset0_paying); - log.push(format!(" {} got {} {}, {} got {} {}",self.traders[bids0.peek().owner].name,asset1_paying,asset1.name, - self.traders[bids1.peek().owner].name,asset0_paying,asset0.name)); - if bids0.peek().sell_remain==FiNum::zero() { bids0.pop(); } - if bids1.peek().sell_remain==FiNum::zero() { bids1.pop(); } - } - Result::ExecutedBatch(log) - } - fn make_order(&mut self, owner:usize, sell_type:usize, buy_type:usize, sell_qty_initial:FiNum, buy_qty_initial:FiNum, execute_if_possible:bool) -> Result { - let mut log:Vec=Vec::new(); - let initial_balance=self.traders[owner].get_balance(sell_type); - let asset=self.number_to_asset(sell_type); - let mut royalty0_qty=asset.royalty0_rate*sell_qty_initial; - let mut royalty1_qty=asset.royalty1_rate*sell_qty_initial; - let mut commission0_qty=asset.commission0_rate*sell_qty_initial; - let mut commission1_qty=asset.commission1_rate*sell_qty_initial; - let sell_qty_plus=sell_qty_initial+royalty0_qty+royalty1_qty+commission0_qty+commission1_qty; // This is the maximum amount we are going to sell - let sell_qty=sell_qty_initial; - if initial_balanceFiNum::new(0) && self.orders.contains_key(&ap) && self.orders.get(&ap).unwrap().v.len()>0 { - let mut elt=self.orders.get(&ap).unwrap().v[0].clone(); - if sell_qty/buy_qty_initial>=elt.buy_qty/elt.sell_qty { // Transact at ask_rate - let qty=std::cmp::min(elt.sell_remain,buy_qty); - elt.sell_remain-=qty; - buy_qty-=qty; - let pay_qty=qty*elt.buy_qty/elt.sell_qty; - self.traders[owner ].sub_balance(sell_type,pay_qty); - self.traders[owner ].add_balance(buy_type ,qty); - self.traders[elt.owner].add_balance(sell_type,pay_qty); - log.push(format!("Sold {} {} ({}) to buy {} {} ({})",pay_qty,self.number_to_name(sell_type),self.traders[owner ].name, - qty ,self.number_to_name(buy_type ),self.traders[elt.owner].name)); - let rq0=royalty0_qty *pay_qty/sell_qty_initial; - let cq0=commission0_qty*pay_qty/sell_qty_initial; - let rq1=royalty1_qty *pay_qty/sell_qty_initial; - let cq1=commission1_qty*pay_qty/sell_qty_initial; - println!("Transact at ask rate. Qty={} elt.sell_remain={} elt.royalty_remain={} elt.commission_remain={}",qty,elt.sell_remain,elt.royalty_remain,elt.commission_remain); - let rq2=if !elt.sell_remain.is_zero() { elt.royalty_remain *qty/elt.sell_remain } else { FiNum::zero() }; - let cq2=if !elt.sell_remain.is_zero() { elt.commission_remain*qty/elt.sell_remain } else { FiNum::zero() }; - royalty_acc+=rq0+rq1; - commission_acc+=cq0+cq1; - royalty0_qty-=rq0; - royalty1_qty-=rq1; - commission0_qty-=cq0; - commission1_qty-=cq1; - self.royalties.entry(sell_type).or_insert(RoyaltyTree::new()).add_royalty(rq0+rq1); - self.royalties.entry(buy_type) .or_insert(RoyaltyTree::new()).add_royalty(rq2); - let top_order=self.orders.get_mut(&ap).unwrap().peek(); - top_order.royalty_remain-=rq2; - top_order.commission_remain-=cq2; - self.traders[0].add_balance(buy_type,cq2); - self.royalties.entry(buy_type).or_insert(RoyaltyTree::new()).add_royalty(rq2); - if elt.sell_remain==0.into() { // deal with pennies stored in royalty_remain and commission_remain - self.traders[0].add_balance(buy_type,top_order.commission_remain); - self.royalties.entry(buy_type).or_insert(RoyaltyTree::new()).add_royalty(top_order.royalty_remain); - self.orders.get_mut(&ap).unwrap().pop(); - } else { - top_order.sell_remain-=qty; - } - } else { break; } - } - if buy_qty>0.into() { - let ap=(sell_type,buy_type); - if let None=self.orders.get_mut(&ap) { self.orders.insert(ap,OrderQueue::new()); } - let bids=self.orders.get_mut(&ap).unwrap(); - let sell_qty_remain=sell_qty*buy_qty/buy_qty_initial; - if sell_qty_remain>0.into() { - self.royalties.entry(sell_type).or_insert(RoyaltyTree::new()).add_royalty(royalty0_qty); - let rt=self.royalties.entry(buy_type).or_insert(RoyaltyTree::new()); - let rt_loc=rt.next_entry; - rt.add_weight(rt_loc,buy_qty); // Weight should really be the quantity you would be able to immediately transact rather than how much you wish for. - let neworder=Order { owner:owner, sell_qty:sell_qty_remain, sell_remain:sell_qty_remain, buy_qty:buy_qty, rt_loc: rt_loc, pq_loc:0, royalty_remain:royalty1_qty, commission_remain:commission1_qty, id:self.next_order_id }; - new_order_id=self.next_order_id; - self.next_order_id+=1; - rt.next_entry+=1; - let pq_loc=bids.insert(neworder); - bids.v[pq_loc].pq_loc=pq_loc; - bids.v[pq_loc].rt_loc=rt_loc; - self.traders[owner].sub_balance(sell_type,sell_qty_remain); - self.traders[0].add_balance(sell_type,commission0_qty); - log.push(format!("Moved {} {} ({}) to market",sell_qty_remain,self.number_to_name(sell_type),self.traders[owner].name)); - } else { new_order_id=0; } - } else { new_order_id=0; } - self.traders[0].add_balance(sell_type,commission_acc); - self.traders[owner].sub_balance(sell_type,royalty_acc+commission_acc); - log.push(format!("Paid {} {} in royalties and {} {} ({}) in commissions",royalty_acc ,self.number_to_name(sell_type), - commission_acc,self.number_to_name(sell_type),self.traders[owner].name)); - Result::PlacedOrder(new_order_id,log) - } - } - -impl PartialOrd for Order { - fn partial_cmp(&self, other:&Self) -> Option { - let ord0=self .sell_qty/self .buy_qty; - let ord1=other.sell_qty/other.buy_qty; - if ord0ord1 { Some(Ordering::Greater) } - else { Some(Ordering::Equal) } - } - } - -impl PartialEq for Order { - fn eq(&self, other:&Self) -> bool { - let ord0=self .sell_qty/self .buy_qty; - let ord1=other.sell_qty/other.buy_qty; - ord0==ord1 - } - } - - -struct OrderQueue { - v: Vec, - } - -impl OrderQueue { - fn new()->Self { - OrderQueue { - v: Vec::new() - } - } - fn peek(&mut self) -> &mut Order { - self.v.first_mut().unwrap() - } - fn empty(&self) -> bool { - self.v.len()==0 - } - fn bubble_up(&mut self, pos: usize) -> usize { - if pos>0 { - let parent=(pos-1)/2; - if self.v[parent] usize { - item.pq_loc=self.v.len(); - self.v.push(item); - self.bubble_up(self.v.len()-1) - } - fn pop(&mut self) -> Option { - if self.v.len()==0 { None } - else { - let end=self.v.len()-1; - self.v.swap(0,end); - let rval=self.v.pop(); - self.trickle_down(0); - rval - } - } - fn dump(&self) { - for index in 0..self.v.len() { - self.v[index].dump(); - } - } - } - -impl Market { - fn exercise(&mut self) { - let mut rng: StdRng=StdRng::seed_from_u64(13u64); - let teppy=self.register_trader("Teppy"); - let luni =self.register_trader("Luni"); - let usd =self.register_asset("USD").unwrap(); - let btc =self.register_asset("BTC").unwrap(); - self.add_trader_balance(teppy,btc,100000.into()); - self.add_trader_balance(luni ,usd,650000000.into()); - let mut count=0; - let mut tries=0; - for _i in 1..=1000000 { - let seller=if rng.gen_bool(0.5) { teppy } else { luni }; - let (buy_type,sell_type,buy_qty,sell_qty):(usize,usize,FiNum,FiNum); - if rng.gen_bool(0.5) { - sell_type=btc; - buy_type=usd; - sell_qty=rng.gen_range(1..=5).into(); - buy_qty=sell_qty*rng.gen_range(60..=70).into(); - } else { - sell_type=usd; - buy_type=btc; - buy_qty=rng.gen_range(1..=5).into(); - sell_qty=buy_qty*rng.gen_range(60..=70).into(); - } - let mut _success=false; - if let Result::Ok=self.make_order(seller,sell_type,buy_type,sell_qty,buy_qty,true) { count+=1; _success=true; }; - if count>=1000000 { break; } - tries+=1; - } - println!("Tries: {} Trades: {}",tries,count); - self.sanity_check(); - } - } - -fn wt_level(index:usize) -> usize { - (index^(index+1)).trailing_ones() as usize-1 - } - -fn wt_leaf(index:usize) -> bool { - index&1==0 - } - -fn wt_left(index:usize) -> Option { - let level=wt_level(index); - if level>0 { Some(index-(1<<(wt_level(index)-1))) } else { None } - } - -fn wt_right_edge(index: usize, edge: usize) -> Option { // Less than edge - if index&1==0 { None } - else { - let mut r=wt_right(index).unwrap(); - if r Option { - let level=wt_level(index); - if level>0 { Some(index+(1<<(wt_level(index)-1))) } else { None } - } - -fn wt_parent(index:usize) -> usize { - let lev=wt_level(index); - let first_in_row=index%(1<>1; - first_in_parent_row+nth_in_parent_row*skip_in_parent_row - } - -fn wt_forefather(max_index:usize) -> usize { - let mut rval=max_index; - rval=rval|(rval>>1); - rval=rval|(rval>>2); - rval=rval|(rval>>4); - rval=rval|(rval>>8); - rval=rval|(rval>>16); - rval=rval|(rval>>32); - if rval>max_index { wt_left(rval).unwrap() } else { rval } - } - - -fn royalty_stuff() { - let mut rt=RoyaltyTree::new(); - for index in 0..10 { - rt.add_weight(index,FiNum::new_i32(10)); - rt.add_royalty(FiNum::new_i32(24)) - } - for index in 0..rt.tree.len() { - println!("Index: {} Weight_here {} Weight_below {} Weight_here_below {}",index,rt.weight_here(index),rt.weight_below(index),rt.weight_here_below(index)); - } - } - - -enum Result { - AddedTrader(usize,String), - AddedAsset(usize,String), - PlacedOrder(usize, Vec), - FundsRemaining(FiNum), - ExecutedBatch(Vec), - Error(String), - Ok - } - -enum Command { - AddTrader { user: String }, - AddAsset { asset: String }, - SetRoyalty { asset_id: usize, roy0: FiNum, com0: FiNum, roy1: FiNum, com1: FiNum }, - AddFunds { user_id: usize, asset_id: usize, amt: FiNum }, - SubFunds { user_id: usize, asset_id: usize, amt: FiNum }, - Order { user_id: usize, sell_type: usize, sell_qty: FiNum, buy_type: usize, buy_qty: FiNum }, - OrderBatch { user_id: usize, sell_type: usize, sell_qty: FiNum, buy_type: usize, buy_qty: FiNum }, - ExecuteBatch { asset_type0: usize, strike0: FiNum, asset_type1: usize, strike1: FiNum }, - Retract { order_id: usize }, - Error(String), - NOP(String), - None, - } - -fn clean(s: &str) -> String { s.to_string() } - -impl Command { - fn serialize(&self) -> String { - match self { - Self::AddTrader { user: name } => format!("AT {}",name), - Self::AddAsset { asset: name } => format!("AA {}",name), - Self::SetRoyalty { asset_id, roy0, com0 , roy1 , com1 } - => format!("SR {} {} {} {} {}",asset_id,roy0.serialize(),com0.serialize(),roy1.serialize(),com1.serialize()), - Self::AddFunds { user_id, asset_id, amt } - => format!("AF {} {} {}",user_id,asset_id,amt.serialize()), - Self::SubFunds { user_id, asset_id , amt } - => format!("SF {} {} {}",user_id,asset_id,amt.serialize()), - Self::Order { user_id, sell_type, sell_qty, buy_type, buy_qty } - => format!("OR {} {} {} {} {}",user_id,sell_type,sell_qty.serialize(),buy_type,buy_qty.serialize()), - Self::OrderBatch { user_id, sell_type, sell_qty, buy_type, buy_qty } - => format!("ORB {} {} {} {} {}",user_id,sell_type,sell_qty.serialize(),buy_type,buy_qty.serialize()), - Self::ExecuteBatch { asset_type0, strike0, asset_type1, strike1 } - => format!("EXE {} {} {} {}",asset_type0,strike0.serialize(),asset_type1,strike1.serialize()), - Self::Retract { order_id } => format!("RE {}",order_id), - Self::Error(str) => format!("NOP Error: {}",str), - Self::NOP(str) => format!("NOP {}",clean(str)), - _ => format!("NOP (This should never happen)"), - } - } - fn deserialize(line: String) -> Self { - let tokens: Vec<&str> = line.split_whitespace().collect(); - match tokens.as_slice() { - ["AT",name] => Self::AddTrader { user: name.to_string() }, - ["AA",name] => Self::AddAsset { asset: name.to_string() }, - ["SR",asset_id,roy0,com0,roy1,com1] => - Self::SetRoyalty { asset_id: asset_id.parse::().unwrap(), roy0: FiNum::new_deserialize(roy0), com0: FiNum::new_deserialize(com0), - roy1: FiNum::new_deserialize(roy1), com1: FiNum::new_deserialize(com1) }, - ["AF",user_id,asset_id,amt] => - Self::AddFunds { user_id: user_id.parse::().unwrap(), asset_id: asset_id.parse::().unwrap(), amt: FiNum::new_deserialize(amt) }, - ["SF",user_id,asset_id,amt] => - Self::SubFunds { user_id: user_id.parse::().unwrap(), asset_id: asset_id.parse::().unwrap(), amt: FiNum::new_deserialize(amt) }, - ["OR",user_id,sell_type,sell_qty,buy_type,buy_qty] => - Self::Order { user_id: user_id.parse::().unwrap(), sell_type: sell_type.parse::().unwrap(), sell_qty: FiNum::new_deserialize(sell_qty), - buy_type: buy_type.parse::().unwrap(), buy_qty: FiNum::new_deserialize(buy_qty) }, - ["ORB",user_id,sell_type,sell_qty,buy_type,buy_qty] => - Self::OrderBatch { user_id: user_id.parse::().unwrap(), sell_type: sell_type.parse::().unwrap(), sell_qty: FiNum::new_deserialize(sell_qty), - buy_type: buy_type.parse::().unwrap(), buy_qty: FiNum::new_deserialize(buy_qty) }, - ["EXE",asset_type0,strike0,asset_type1,strike1] => - Self::ExecuteBatch { asset_type0: asset_type0.parse::().unwrap(),strike0: FiNum::new_deserialize(strike0), asset_type1: asset_type1.parse::().unwrap(), strike1: FiNum::new_deserialize(strike1) }, - ["NOP", many_things @ ..] => Self::NOP(clean(&line)), - _ => Self::Error("Unimplemented Parse".to_string()), - } - } - } - -impl Result { - fn describe(&self) -> String { - match self { - Self::PlacedOrder(order_id,vec) => format!("Placed order {}:\n {}",order_id,vec.join("\n ")), - Self::AddedTrader(id,name) => format!("Added Trader Id {}: {}",id,name), - Self::AddedAsset(id,name) => format!("Added Asset Id {}: {}",id,name), - Self::ExecutedBatch(vec) => format!("{}",vec.join("\n")), - Self::FundsRemaining(amt) => format!("Funds Remaining: {}",amt), - Self::Ok => format!("Ok"), - Self::Error(str) => format!("Error: {}",str), -// _ => "Some other result".to_string(), - } - } - fn print(&self) -> &Self { - println!("{}",self.describe()); - self - } - } - -impl Market { - fn execute(&mut self, cmd: &Command) -> Result { - match cmd { - Command::AddTrader { user: user_name } => { - let id=self.register_trader(user_name); - Result::AddedTrader(id,user_name.to_string()) - } - Command::AddAsset { asset: asset_name } => { - if let Some(cur)=self.register_asset(asset_name) { - Result::AddedAsset(cur,asset_name.to_string()) - } else { Result::Error(format!("Asset {} already exists.",asset_name)) } - } - Command::SetRoyalty { asset_id,roy0,com0,roy1,com1 } => { - if *roy0+*com0+*roy1+*com1 { - self.add_trader_balance(*user_id,*asset_id,*amt); - println!("Added {} {} to {}",*amt,self.number_to_asset(*asset_id).name,self.traders[*user_id].name); - Result::FundsRemaining(self.get_trader_balance(*user_id,*asset_id)) - } - Command::SubFunds { user_id,asset_id,amt } => { - if *amt>self.get_trader_balance(*user_id,*asset_id) { Result::Error(format!("Not enough {} in {}",asset_id,user_id)) } - else { - self.sub_trader_balance(*user_id,*asset_id,*amt); - println!("Subtracted {} {} from {}",*amt,self.number_to_asset(*asset_id).name,self.traders[*user_id].name); - Result::FundsRemaining(self.get_trader_balance(*user_id,*asset_id)) - } - } - Command::Order { user_id, sell_type, sell_qty, buy_type, buy_qty } => { - self.make_order(*user_id,*sell_type,*buy_type,*sell_qty,*buy_qty,true) - } - Command::OrderBatch { user_id, sell_type, sell_qty, buy_type, buy_qty } => { - self.make_order(*user_id,*sell_type,*buy_type,*sell_qty,*buy_qty,false) - } - Command::ExecuteBatch { asset_type0, strike0, asset_type1, strike1 } => { - self.execute_batch(*asset_type0, *strike0, *asset_type1, *strike1) - } - Command::Error(str) => Result::Error(format!("Command Error")), - Command::NOP(str) => Result::Ok, - _ => Result::Error(format!("Tried to execute an unimplemented Command. This is a bug because all Commands should be implemented, even NOPs and Errors.")), - } - } - } - - - -fn erase(filename: &str) { - // Stub function for erasing a file - println!("Erasing file: {}", filename); -} - -fn copy(source: &str, destination: &str) { - // Stub function for copying a file - println!("Copying file from {} to {}", source, destination); -} - - -fn tokens_to_command(m: &Market, logged_in: usize, tokens: Vec<&str>,line: &str) -> Command { - let cmd:Command=match &tokens[..] { - ["addtrader", username] => Command::AddTrader { user: username.to_string() }, - ["addasset", assetname] => Command::AddAsset { asset: assetname.to_string() }, - ["setroyalty", assetname, roy0, com0, roy1, com1] => { - if let Some(cur)=m.name_to_number(assetname) { - let roy0=FiNum::new_str(roy0); - let com0=FiNum::new_str(com0); - let roy1=FiNum::new_str(roy1); - let com1=FiNum::new_str(com1); - if roy0+com0+roy1+com1 { - let user=m.trader_name2num.get(*username); - let qty=FiNum::new_str(qty0); - let cur=m.name_to_number(cur0); - if user.is_none() { Command::Error(format!("Could not find trader {}",username)) } - else if qty.is_zero() { Command::Error(format!("Could not parse quantity {}",qty0)) } - else if cur.is_none() { Command::Error(format!("Could not find asset {}",cur0)) } - else { - let user=*user.unwrap(); - let cur=*cur.unwrap(); - Command::AddFunds { user_id: user, asset_id: cur, amt: qty } - } - } - ["subfunds", username, qty0, cur0 ] => { - let user=m.trader_name2num.get(*username); - let qty=FiNum::new_str(qty0); - let cur=m.name_to_number(cur0); - if user.is_none() { Command::Error(format!("Could not find trader {}",username)) } - else if qty.is_zero() { Command::Error(format!("Could not parse quantity {}",qty0)) } - else if cur.is_none() { Command::Error(format!("Could not find asset {}",cur0)) } - else { - let user=*user.unwrap(); - let cur=*cur.unwrap(); - Command::SubFunds { user_id: user, asset_id: cur, amt: qty } - } - } - ["order", qty0, cur0, qty1, cur1 ] => { - let qty0=FiNum::new_str(qty0); - let cur0=m.name_to_number(cur0); - let qty1=FiNum::new_str(qty1); - let cur1=m.name_to_number(cur1); - if !cur0.is_some() { Command::Error("Count not find currency".to_string()) } - else if !cur1.is_some() { Command::Error("Count not find currency".to_string()) } - else if qty0.is_zero() { Command::Error("Qty0 is must be > 0".to_string()) } - else if qty1.is_zero() { Command::Error("Qty1 is must be > 0".to_string()) } - else { Command::Order { user_id: logged_in, sell_type: *cur0.unwrap(), sell_qty: qty0, buy_type: *cur1.unwrap(), buy_qty: qty1 } } - } - ["orderbatch", qty0, cur0, qty1, cur1 ] => { - let qty0=FiNum::new_str(qty0); - let cur0=m.name_to_number(cur0); - let qty1=FiNum::new_str(qty1); - let cur1=m.name_to_number(cur1); - if !cur0.is_some() { Command::Error("Count not find currency".to_string()) } - else if !cur1.is_some() { Command::Error("Count not find currency".to_string()) } - else if qty0.is_zero() { Command::Error("Qty0 is must be > 0".to_string()) } - else if qty1.is_zero() { Command::Error("Qty1 is must be > 0".to_string()) } - else { Command::OrderBatch { user_id: logged_in, sell_type: *cur0.unwrap(), sell_qty: qty0, buy_type: *cur1.unwrap(), buy_qty: qty1 } } - } - ["execute", strike0, asset_type0, strike1, asset_type1 ] => { - let strike0=FiNum::new_str(strike0); - let asset_type0=m.name_to_number(asset_type0); - let strike1=FiNum::new_str(strike1); - let asset_type1=m.name_to_number(asset_type1); - if !asset_type0.is_some() { Command::Error("Count not find currency".to_string()) } - else if !asset_type1.is_some() { Command::Error("Count not find currency".to_string()) } - else if strike0.is_zero() { Command::Error("Strike0 is must be > 0".to_string()) } - else if strike1.is_zero() { Command::Error("Strike1 is must be > 0".to_string()) } - else { Command::ExecuteBatch { asset_type0: *asset_type0.unwrap(), strike0:strike0, asset_type1: *asset_type1.unwrap(), strike1: strike1 } } - } - _ => { Command::Error(line.to_string()) }, - }; - cmd - } - - -fn interactive(m: &mut Market, mut out: Option) { - println!("Trading interactively in Tuesday Markets (demo)"); - let stdin = io::stdin(); - let mut trader:usize=0; - for line in stdin.lock().lines() { - match line { - Ok(input) => { - let tokens: Vec<&str> = input.split_whitespace().collect(); - let cmd:Command=match tokens.as_slice() { - ["dump"] => { m.dump(); Command::None }, - ["login", username] => { - if let Some(t)=m.trader_name2num.get_mut(*username) { trader=*t; println!("Logged in as {}",m.traders[trader].name) } else { println!("Trader {} not found.",username) } - Command::None - }, - ["whoami" ] => { println!("Logged in as {}, id {}",m.traders[trader].name,trader ); Command::None } - ["wallet"] => { - for (key,value) in &m.traders[trader].balances { println!(" {} {}",m.number_to_name(*key),value); } - Command::None - } - ["balances", username] => { - if let Some(user)=m.trader_name2num.get(*username) { - println!("Balances for trader {}",m.traders[*user].name); - for (key,value) in &m.traders[*user].balances { println!(" {} {}",m.number_to_name(*key),value); } - } else { println!("Could not find trader {}",username); } - Command::None - }, - ["quit"] => { return }, - _ => { - let cmd=tokens_to_command(m,trader,tokens,&input); - if let Some(ref mut f)=out { - let ser=cmd.serialize(); - if let Err(e)=writeln!(f,"{}",ser) { eprintln!("An error occurred while writing {}",e); } - else { println!("Wrote to logfile: {}",ser); } - } - let res=m.execute(&cmd); - println!("Result: {}",res.describe()); - Command::None - }, - }; - } - Err(error) => println!("Error reading input: {}", error), - } - } - } - -fn numbers_stuff() { - println!("Numbers_stuff"); - let n=FiNum::new_i32(7)/FiNum::new_i32(2); - let n_s=n.serialize(); - let n_d=FiNum::new_deserialize(&n_s); - println!("N is {}, Serialized to {}, Deserialized to {}",n,n_s,n_d); - } - -fn paths_match(path0: &str, path1: &str) -> bool - { - let path0 = Path::new(path0); - let path1 = Path::new(path1); - path0 == path1 - } - -// -// Use cases: -// Replace logfile -// Replay logfile and then append to it -// Replay one logfile and then replace a different one -// Future additional use cases: -// Replay logfile1+logfile2+... and then replace a different log file -// Replay logfile1+logfile2+logfileN and then append to logfileN -// -fn main() { - let args: Vec = env::args().collect(); - let mut options:HashMap<&str,String>=HashMap::new(); - let mut i=1; - enum Mode { Help, Royalty, Interactive, Exercise, Numbers, None } - let mut mode_count=0; - let mut mode=Mode::None; - while i { mode=Mode::Help; mode_count+=1; i+=1; } - "--interactive" => { mode=Mode::Interactive; mode_count+=1; i+=1; } - "--royalty" => { mode=Mode::Royalty; mode_count+=1; i+=1; } - "--exercise" => { mode=Mode::Exercise; mode_count+=1; i+=1; } - "--numbers" => { mode=Mode::Numbers; mode_count+=1; i+=1; } - "--log" => { - if i+1>=args.len() { println!("No log file specified."); return; } - else { options.insert("logfile",args[i+1].clone()); i+=2; } - } - "--replay" => { - if i+1>=args.len() { println!("No replay file specified."); return; } - else { options.insert("replay",args[i+1].clone()); i+=2; } - } - _ => { println!("Unknown option."); return; } - } - } - if mode_count==0 { mode=Mode::Interactive; mode_count+=1; } - if mode_count!=1 { println!("You may only select one mode to run in."); return; } - let mut m=Market::new(); // USD type is 1, EUR type is 2, BTC type is 10, ETH type is 11, zKN6FBdD SOL type is 12 - if options.contains_key("replay") { m.replay_file(options.get("replay").unwrap()); } - match mode { - Mode::Interactive => { - if options.contains_key("logfile") { - let ap=options.contains_key("replay") && paths_match(options.get("replay").unwrap(),options.get("logfile").unwrap()); - let f=OpenOptions::new().write(true).append(ap).truncate(!ap).create(true).open(options.get("logfile").unwrap()); - if let Ok(f)=f { interactive(&mut m,Some(f)); } - else { println!("Could not open logfile for writing."); } - } else { interactive(&mut m,None); } - } - Mode::Exercise => m.exercise(), - Mode::Numbers => numbers_stuff(), - Mode::Royalty => royalty_stuff(), - _ => println!("Unspecified mode"), - } - }