Skip to content
This repository has been archived by the owner on Feb 13, 2020. It is now read-only.

Commit

Permalink
Created way to convert HashMaps into SimbrokerSettings
Browse files Browse the repository at this point in the history
 - Created a FromHashmap trait
 - Created a procedural macro to derive it for structs
  • Loading branch information
Ameobea committed Jan 12, 2017
1 parent 83a650b commit 81d26aa
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 79 deletions.
2 changes: 0 additions & 2 deletions backtester/src/sim_broker.rs
Expand Up @@ -6,8 +6,6 @@ use std::collections::hash_map::Entry;
use std::sync::atomic::{Ordering, AtomicUsize};
use std::sync::{mpsc, Arc, Mutex};
use std::thread;
#[allow(unused_imports)]
use test;

use futures::{oneshot, Oneshot};
use futures::stream::{BoxStream, Stream};
Expand Down
13 changes: 9 additions & 4 deletions configurator/src/directory.rs
Expand Up @@ -67,7 +67,7 @@ pub fn show_directory(s: &mut Cursive, settings: Settings, needs_start: bool) {
let last_page = PAGE_LIST[last_page_ix];
let changed = check_changes(s, last_page, settings__.clone());
if changed {
s.add_layer(get_save_dialog(0, 0, settings__.clone(), true));
s.add_layer(get_save_dialog(last_page_ix, last_page_ix, settings__.clone(), true));
} else {
directory_exit(s, settings__.clone());
}
Expand Down Expand Up @@ -170,8 +170,13 @@ fn check_changes(s: &mut Cursive, page: &SettingsPage, settings: Settings) -> bo
for row in page.iter() {
let cur_val = get_by_id(row.id, s)
.expect(&format!("Unable to get {} by id!", row.id));
let last_val = settings.get(String::from(row.id))
.expect(&format!("Unable to get past val in check_changes: {}", row.id));
let last_val_opt = settings.get(String::from(row.id));
let last_val;
if last_val_opt.is_none() {
last_val = String::from(row.default.expect(&format!("No past val for {} and no default!", row.id)));
} else {
last_val = last_val_opt.unwrap();
}
if last_val != *cur_val {
return true
}
Expand All @@ -182,7 +187,7 @@ fn check_changes(s: &mut Cursive, page: &SettingsPage, settings: Settings) -> bo
/// Commits all changes for a page to the internal Settings object and then writes them to all files.
fn save_changes(s: &mut Cursive, page: &SettingsPage, settings: Settings) {
for row in page.iter() {
let cur_val = get_by_id(row.id, s).unwrap();
let cur_val = get_by_id(row.id, s).expect(&format!("Couldn't get value by id for: {}", row.id));
settings.set(row.id, &*cur_val);
}

Expand Down
1 change: 1 addition & 0 deletions configurator/src/main.rs
Expand Up @@ -187,6 +187,7 @@ fn redis_local(s: &mut Cursive, installed: bool, settings: Settings) {
"redis://localhost:{}/",
port
));
settings.set("redis_server_binary_path", &which("redis-server"));
postgres_config(s, settings.clone())
});
port_box.set_content("6379");
Expand Down
23 changes: 15 additions & 8 deletions configurator/src/misc.rs
Expand Up @@ -37,7 +37,7 @@ impl SettingRow {
SettingType::String | SettingType::Usize | SettingType::Boolean => format!("\"{}\"", raw_val),
SettingType::OptionString => match raw_val.as_str() {
"" => String::from("null"),
_ => raw_val,
_ => format!("\"{}\"", raw_val),
},
}
}
Expand All @@ -51,7 +51,7 @@ impl SettingRow {
SettingType::Boolean => raw_val, // Assume it's in the right format.
SettingType::OptionString => match raw_val.as_str() {
"" => String::from("None"),
_ => format!("Some({})", raw_val),
_ => format!("Some(\"{}\")", raw_val),
},
}
}
Expand All @@ -65,7 +65,7 @@ impl SettingRow {
SettingType::Boolean => raw_val, // Assume it's in the right format.
SettingType::OptionString => match raw_val.as_str() {
"" => String::from("null"),
_ => raw_val,
_ => format!("\"{}\"", raw_val),
},
}
}
Expand Down Expand Up @@ -367,7 +367,7 @@ pub const FXCM_SETTINGS: SettingsPage = SettingsPage {
name: "URL",
default: Some("http://www.fxcorporate.com/Hosts.jsp"),
setting_type: SettingType::String,
comment: None,
comment: Some("Path to the `Hosts.jsp` file for the FXCM API."),
},
SettingRow {
id: "fxcm_pin",
Expand Down Expand Up @@ -406,14 +406,14 @@ pub const GENERAL_SETTINGS: SettingsPage = SettingsPage {
name: "Log Channel",
default: Some("log"),
setting_type: SettingType::String,
comment: None,
comment: Some("The redis pub/sub channel on which log messages will be sent."),
},
SettingRow {
id: "data_dir",
name: "Data Directory",
default: None,
setting_type: SettingType::String,
comment: None,
comment: Some("Data directory for the platform where things like historical ticks and settings are stored."),
},
SettingRow {
id: "websocket_port",
Expand All @@ -427,14 +427,21 @@ pub const GENERAL_SETTINGS: SettingsPage = SettingsPage {
name: "MM Port",
default: Some("8002"),
setting_type: SettingType::Usize,
comment: Some("The port the MM will listen on."),
comment: Some("The port the MM web GUI will listen on."),
},
SettingRow {
id: "node_binary_path",
name: "NodeJS Binary Path",
default: None,
setting_type: SettingType::String,
comment: None,
comment: Some("The absolute path to the `node` binary."),
},
SettingRow {
id: "redis_server_binary_path",
name: "Redis Server Path",
default: Some(""),
setting_type: SettingType::OptionString,
comment: Some("The absolute path to the `redis-server` executable. Empty if Redis is installed remotely."),
},
SettingRow {
id: "logger_persistance_table",
Expand Down
7 changes: 7 additions & 0 deletions dev.sh
@@ -1 +1,8 @@
#!/bin/bash

# This script symlinks the mm directory into the dist directory so that changes made to the
# mm directory during development will be reflected when running the platform.
#
# You only need to run this script if you're working on the MM.

make debug && make dev && ./run.sh
4 changes: 4 additions & 0 deletions run.sh
@@ -1,5 +1,9 @@
#!/bin/bash

# This script starts the whole platform and is the only official way to start the platform.
# It initializes a spawner instance which in turn spawns a Logger and a MM instance.
# Once you run this script, you can view the MM web console in your web browser (default port 8002).

LD_LIBRARY_PATH="$(pwd)/dist/lib"
export LD_LIBRARY_PATH
cd dist && ./spawner
2 changes: 2 additions & 0 deletions util/Cargo.toml
Expand Up @@ -2,6 +2,7 @@
name = "algobot-util"
version = "0.1.0"
authors = ["Casey Primozic <me@ameo.link>"]
description = "Code used in all of TickGrinder's modules."

[dependencies]
redis = "0.5.3"
Expand All @@ -12,6 +13,7 @@ serde_json = "0.8.4"
serde_derive = "0.8.21"
uuid = { version = "0.3.1", features = ["serde", "v4",] }
indoc = "^0.1"
from_hashmap = { path = "from_hashmap" }

[lib]
name = "algobot_util"
Expand Down
2 changes: 2 additions & 0 deletions util/from_hashmap/.gitignore
@@ -0,0 +1,2 @@
*.lock
target
12 changes: 12 additions & 0 deletions util/from_hashmap/Cargo.toml
@@ -0,0 +1,12 @@
[package]
name = "from_hashmap"
version = "0.1.0"
authors = ["Casey Primozic <me@ameo.link>"]
description = "Defines a procedural macro to build a struct from a HashMap containing values for its fields"

[dependencies]
syn = "0.10"
quote = "0.3"

[lib]
proc-macro = true
84 changes: 84 additions & 0 deletions util/from_hashmap/src/lib.rs
@@ -0,0 +1,84 @@
//! Used to take a HashMap<String, String> containing field:value pairs of a struct
//! and using it to create a new struct containing that data.

#![recursion_limit = "128"]

extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;
use syn::{Ident, VariantData};

#[proc_macro_derive(FromHashmap)]
pub fn from_hashmap(input: TokenStream) -> TokenStream {
let source = input.to_string();
// Parse the string representation into a syntax tree
let ast = syn::parse_macro_input(&source).unwrap();

// create a vector containing the names of all fields on the struct
let idents: Vec<Ident> = match ast.body {
syn::Body::Struct(vdata) => {
match vdata {
VariantData::Struct(fields) => {
let mut idents = Vec::new();
for ref field in fields.iter() {
match &field.ident {
&Some(ref ident) => idents.push(ident.clone()),
&None => panic!("Your struct is missing a field identity!"),
}
}
idents
},
VariantData::Tuple(_) | VariantData::Unit => {
panic!("You can only derive this for normal structs!");
},
}
},
syn::Body::Enum(_) => panic!("You can only derive this on structs!"),
};

// contains quoted strings containing the struct fields in the same order as
// the vector of idents.
let mut keys = Vec::new();
for ident in idents.iter() {
keys.push(String::from(ident.as_ref()));
}

let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

let tokens = quote! {
/// Attempts to convert the given &str into a T, panicing if it's not successful
fn parse_pair<T>(v: &str) -> T where T : ::std::str::FromStr {
let res = v.parse::<T>();
match res {
Ok(val) => val,
Err(_) => panic!(format!("Unable to convert given input into required type: {}", v)),
}
}

impl #impl_generics FromHashmap<#name> for #name #ty_generics #where_clause {
fn from_hashmap(mut hm: ::std::collections::HashMap<String, String>) -> #name {
// start with the default implementation
let mut settings = #name::default();
#(
match hm.entry(String::from(#keys)) {
::std::collections::hash_map::Entry::Occupied(occ_ent) => {
// set the corresponding struct field to the value in
// the corresponding hashmap if it contains it
settings.#idents = parse_pair(occ_ent.get().as_str());
},
::std::collections::hash_map::Entry::Vacant(_) => (),
}
)*

// return the modified struct
settings
}
}
};

tokens.parse().unwrap()
}
4 changes: 3 additions & 1 deletion util/src/lib.rs
@@ -1,6 +1,6 @@
//! Code shared by all modules of the platform

#![feature(custom_derive, plugin, conservative_impl_trait, test)]
#![feature(rustc_attrs, plugin, conservative_impl_trait, test)]

extern crate redis;
extern crate futures;
Expand All @@ -11,6 +11,8 @@ extern crate serde_json;
extern crate serde_derive;
extern crate postgres;
extern crate test;
#[macro_use]
extern crate from_hashmap;

pub mod transport;
pub mod strategies;
Expand Down
64 changes: 0 additions & 64 deletions util/src/trading/broker/mod.rs
Expand Up @@ -43,67 +43,3 @@ pub type BrokerResult = Result<BrokerMessage, BrokerError>;

/// Utility type for a currently pending broker action
pub type PendingResult = Receiver<BrokerResult>;

// TODO: Move SimbrokerSettings out of here

/// Settings for the simulated broker that determine things like trade fees,
/// estimated slippage, etc.
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct SimBrokerSettings {
pub starting_balance: f64,
/// how many microseconds ahead the broker is to the client.
pub ping_ns: usize,
/// how many us between when the broker receives an order and executes it.
pub execution_delay_us: usize,
/// the minimum size that a trade can be in cents of .
pub lot_size: usize,
pub leverage: usize,
}

impl SimBrokerSettings {
/// Creates a default SimBrokerSettings used for tests
pub fn default() -> SimBrokerSettings {
SimBrokerSettings {
starting_balance: 1f64,
ping_ns: 0,
execution_delay_us: 0usize,
lot_size: 100000,
leverage: 50,
}
}

/// Parses a String:String hashmap into a SimBrokerSettings object.
pub fn from_hashmap(hm: HashMap<String, String>) -> Result<SimBrokerSettings, BrokerError> {
let mut settings = SimBrokerSettings::default();

// TODO: change to use Configurator settings.

for (k, v) in hm.iter() {
match k.as_str() {
"starting_balance" => {
let res = v.parse::<f64>();
if res.is_err() {
return Err(SimBrokerSettings::kv_parse_error(k, v))
}
settings.starting_balance = res.unwrap();
},
"ping_ms" => {
let res = v.parse::<usize>();
if res.is_err() {
return Err(SimBrokerSettings::kv_parse_error(k, v))
};
settings.ping_ns = res.unwrap();
},
_ => (),
}
}

Ok(settings)
}

fn kv_parse_error(k: &String, v: &String) -> BrokerError {
return BrokerError::Message{
message: format!("Unable to parse K:V pair: {}:{}", k, v)
}
}
}
43 changes: 43 additions & 0 deletions util/src/trading/objects.rs
Expand Up @@ -204,3 +204,46 @@ impl Position {
None
}
}

/// Returns a struct given the struct's field:value pairs in a HashMap. If
/// the provided HashMap doesn't contain a field, then the default is used.
pub trait FromHashmap<T> : Default {
fn from_hashmap(hm: HashMap<String, String>) -> T;
}

/// Settings for the simulated broker that determine things like trade fees,
/// estimated slippage, etc.
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
// procedural macro is defined in the `from_hashmap` crate found in the util
// directory's root.
#[derive(FromHashmap)]
pub struct SimBrokerSettings {
pub starting_balance: f64,
/// how many microseconds ahead the broker is to the client.
pub ping_ns: usize,
/// how many us between when the broker receives an order and executes it.
pub execution_delay_us: usize,
/// the minimum size that a trade can be in cents of .
pub lot_size: usize,
pub leverage: usize,
}

impl Default for SimBrokerSettings {
fn default() -> SimBrokerSettings {
SimBrokerSettings {
starting_balance: 1f64,
ping_ns: 0,
execution_delay_us: 0usize,
lot_size: 1000,
leverage: 50,
}
}
}

#[test]
fn simbroker_settings_hashmap_population() {
let mut hm = HashMap::new();
hm.insert(String::from("ping_ns"), String::from("2000"));
let settings = SimBrokerSettings::from_hashmap(hm);
assert_eq!(settings.ping_ns, 2000);
}

0 comments on commit 81d26aa

Please sign in to comment.