From 951c95d13f43f01cda9e7b9a12c4910135af4f66 Mon Sep 17 00:00:00 2001 From: Teppy Date: Mon, 5 Aug 2024 15:01:03 -0400 Subject: [PATCH] changes --- src/#main.rs# | 1056 +++++++++++++++++++++++++++++++++++++++++++++++++ src/.#main.rs | 1 + 2 files changed, 1057 insertions(+) create mode 100644 src/#main.rs# create mode 100644 src/.#main.rs diff --git a/src/#main.rs# b/src/#main.rs# new file mode 100644 index 0000000..1b4af66 --- /dev/null +++ b/src/#main.rs# @@ -0,0 +1,1056 @@ +// +// 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"), + } + } diff --git a/src/.#main.rs b/src/.#main.rs new file mode 100644 index 0000000..f999404 --- /dev/null +++ b/src/.#main.rs @@ -0,0 +1 @@ +teppy@HAWAII.11316:1721112783 \ No newline at end of file