public static class Builder implements StrategyLauncher {
private final Parameters parameters;
private final Security.ByDate securityByDate;
public Builder(
int orderSize, long maxDollars,
Security.ByDate sbd) {
this.securityByDate = sbd;
req(maxDollars > Price.fromDouble(50));
req(maxDollars <= Price.fromDouble(1000 * 1000));
parameters = new Parameters();
parameters.orderSize = orderSize;
parameters.maxDollars = maxDollars;
}
public Builder(Builder other) {
securityByDate = other.securityByDate;
parameters = new Parameters(other.parameters);
}
private StrategyGreen build(OrderManager om) {
Parameters parametersCopy = new Parameters(parameters);
parametersCopy.sec = securityByDate.get(om.getCurrentTime());
return new StrategyGreen(om, parametersCopy);
}
...
}
Here both StrategyGreen.Builder and StrategyGreen.Parameters were static nested classes; Parameters contained the dozen or more parameters for this trading strategy, all of which were public, but the .parameters field of the strategy object was private: public Distance profitTarget = Distance.bps(7);
public Distance trailingCloseDistance = Distance.percentage(15);
public Duration trailingCloseDelay = Duration.minutes(80);
public Duration tau = Duration.seconds(1);
That saved me some duplication, but unfortunately even Lombok couldn't save me from writing this kind of bullshit: public Builder setStartTime(Moment startTime) {
parameters.startTime = startTime;
return this;
}
In Rust, though, I'd think you could easily define macros for that kind of thing if Builder is what you're into? I'm a super novice at Rust, so maybe I'm overlooking something important here.Also, in many cases, couldn't you just use struct update syntax instead of using mutability? In this case that's not very appealing; instead of writing
let greyblake = User::builder("13", "greyblake@example.com")
.first_name("Sergey")
.build();
I think you'd end up writing let greyblake = User {
first_name: Some("Sergey".into()),
..User::with("13", "greyblake@example.com")
};
which doesn't look like an improvement to me and maybe is actually worse. It does have the advantage that you don't have to write the builder class, with macros or otherwise. In other cases this kind of thing might be more reasonable: let foo = Foo { baz: 8, ..DEFAULTFOO };
Even in cases where struct update syntax isn't a good user experience, maybe struct update syntax would provide a less mutability-centric way to implement the Builder pattern, instead using linearity: fn with_bar(self, bar: impl Into<String>) -> Self {
Self { bar: bar.into(), ..self }
}
— ⁂ —Plot twist! In the case of my StrategyGreen above, the actual builder invocation was done from Jython:
builder = (o.StrategyGreen.builder(5,Price.fromDouble(500*1000), sec)
.setStartTime(o.Moment.at(japan, 2014,04,01, 12,55))
.setProfitTarget(o.Distance.bps(8))
.setTau(o.Duration.minutes(5))
...
That kind of combination of static-language implementation scripted in a dynamic language can be a very powerful combination (C and sh, C and elisp, C++ and JS, C++ and Lua), but I feel like it didn't really pay off for us in that project; we would have been better off writing everything in Python, in which case the whole Parameters object would have surely just been a dict. (Of course, Python has optional named function parameters, but in this case we really did want to reify a Builder or Parameters kind of object so that we could create a whole sequence of StrategyGreen objects on different trading days.)I think it didn't pay off for a few different reasons:
· small project size: we were never more than three people, and when we shut down the project, we had only written about 27000 lines of code, roughly half-and-half split between Blub and Python. Java scales better to larger projects—the well-defined interfaces reduce the amount of code spelunking you have to do—but 27 klocs is well within Python's comfort zone.
· constant factor verbosity: Python isn't an order of magnitude better here than Java (my recent dismaying epiphany: https://news.ycombinator.com/item?id=28660097) but it is better by a factor of, like, four, or something. If those 14000 lines of Blub had instead been 3500 lines of Python, that would have been a significant advantage.
· performance: my main reason for choosing Java was that, in some preliminary simulations, CPython was painfully slow, to the point that I thought it would slow down our strategy refinement feedback loop a lot. But maybe you can see from the above code that I was using Java in a not particularly efficient way: although we did use longs for our prices, we had full-fledged boxed objects for Duration, Moment, Distance, Security, Security.ByDate, and so on. (A thing you can't see is that we ran all the strategies on a single event-driven thread, so we weren't drawing on Java's strength in high-performance concurrency, either.) Fortunately (?), the performance requirements didn't turn out to be as stringent as I thought, so my inefficient use of Java was okay. But this also relates to...
· team composition: I was learning Java on the project and simultaneously trying to teach it to everyone else on the team, so we really didn't make optimal use of the Java ecosystem or Java's advantages. Also, teaching Java turned out to be harder than I expected; nobody else on the team got really comfortable with Java, preferring Python, so anything written in Java became a sort of bottleneck. Partly this may be that I'm just really bad at teaching.
· experimental nature: the advantages of dynamic languages are greater for experimental things where you don't know what you're doing and figure it out as you go along. Though I think automated refactoring narrows the gap somewhat, it doesn't eliminate it. Static languages like Java are less costly when you know what you're doing. But everything in this project was experimental. Which relates to...
· extreme testing: because what we were most interested in was whether our strategies would make or lose money if we ran them in production, not some kind of logical or type-theoretic property, we ran them in simulation before running them in production. Like, a lot. We also had JUnit tests for the kinds of properties you can test with JUnit, but almost all of our code got exercised orders of magnitude harder in simulation than it ever did in production. Which means that the kinds of bugs that static type checking catches were less likely to go uncaught in this project than in most others I've worked on.
· Finally, Java itself is kind of a mediocre language, especially in 02014 when we started the project.
As usual, though, technical issues like language choice weren't crucial to the success or failure of the project; what mattered most was how well we did at prioritizing, collaborating effectively, and responding to those problems. (Maybe you can see, we didn't do well at those.)