// Copyright 2015 Ivan Popivanov
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package net.tradelib.core;
import java.time.LocalDateTime;
import com.google.gson.JsonObject;
public class Order {
// Use the position quantity when processing this order
public static final long POSITION_QUANTITY = -1;
private String symbol;
private long quantity;
private double limitPrice;
private double stopPrice;
private String signal;
// One Cancels All identifier
private String oca;
private Type type;
private State state;
private int barsValidFor;
private LocalDateTime lastBar;
private enum Type {
ENTER_LONG, ENTER_LONG_STOP, ENTER_LONG_LIMIT, ENTER_LONG_STOP_LIMIT,
EXIT_LONG, EXIT_LONG_STOP, EXIT_LONG_LIMIT, EXIT_LONG_STOP_LIMIT,
ENTER_SHORT, ENTER_SHORT_STOP, ENTER_SHORT_LIMIT, ENTER_SHORT_STOP_LIMIT,
EXIT_SHORT, EXIT_SHORT_STOP, EXIT_SHORT_LIMIT, EXIT_SHORT_STOP_LIMIT
}
private enum State {
ACTIVE, CANCELLED, FILLED
}
private boolean stopWasTriggered_;
public static Order enterLong(String s, long q) { return new Order(s, q, Double.NaN, Double.NaN, Type.ENTER_LONG); }
public static Order enterLong(String s, long q, String sig) { return new Order(s, q, Double.NaN, Double.NaN, Type.ENTER_LONG, sig); }
public static Order enterLongLimit(String s, long q, double lp) { return new Order(s, q, lp, Double.NaN, Type.ENTER_LONG_LIMIT); }
public static Order enterLongLimit(String s, long q, double lp, String sig) { return new Order(s, q, lp, Double.NaN, Type.ENTER_LONG_LIMIT, sig); }
public static Order enterLongStop(String s, long q, double sp) { return new Order(s, q, Double.NaN, sp, Type.ENTER_LONG_STOP); }
public static Order enterLongStop(String s, long q, double sp, String sig) { return new Order(s, q, Double.NaN, sp, Type.ENTER_LONG_STOP, sig); }
public static Order enterLongStopLimit(String s, long q, double sp, double lp) { return new Order(s, q, lp, sp, Type.ENTER_LONG_STOP_LIMIT); }
public static Order enterLongStopLimit(String s, long q, double sp, double lp, String sig) { return new Order(s, q, lp, sp, Type.ENTER_LONG_STOP_LIMIT, sig); }
public static Order enterShort(String s, long q) { return new Order(s, q, Double.NaN, Double.NaN, Type.ENTER_SHORT); }
public static Order enterShort(String s, long q, String sig) { return new Order(s, q, Double.NaN, Double.NaN, Type.ENTER_SHORT, sig); }
public static Order enterShortLimit(String s, long q, double lp) { return new Order(s, q, lp, Double.NaN, Type.ENTER_SHORT_LIMIT); }
public static Order enterShortLimit(String s, long q, double lp, String sig) { return new Order(s, q, lp, Double.NaN, Type.ENTER_SHORT_LIMIT, sig); }
public static Order enterShortStop(String s, long q, double sp) { return new Order(s, q, Double.NaN, sp, Type.ENTER_SHORT_STOP); }
public static Order enterShortStop(String s, long q, double sp, String sig) { return new Order(s, q, Double.NaN, sp, Type.ENTER_SHORT_STOP, sig); }
public static Order enterShortStopLimit(String s, long q, double sp, double lp) { return new Order(s, q, lp, sp, Type.ENTER_SHORT_STOP_LIMIT); }
public static Order enterShortStopLimit(String s, long q, double sp, double lp, String sig) { return new Order(s, q, lp, sp, Type.ENTER_SHORT_STOP_LIMIT, sig); }
public static Order exitLong(String s, long q) { return new Order(s, q, Double.NaN, Double.NaN, Type.EXIT_LONG); }
public static Order exitLong(String s, long q, String sig) { return new Order(s, q, Double.NaN, Double.NaN, Type.EXIT_LONG, sig); }
public static Order exitLongLimit(String s, long q, double lp) { return new Order(s, q, lp, Double.NaN, Type.EXIT_LONG_LIMIT); }
public static Order exitLongLimit(String s, long q, double lp, String sig) { return new Order(s, q, lp, Double.NaN, Type.EXIT_LONG_LIMIT, sig); }
public static Order exitLongStop(String s, long q, double sp) { return new Order(s, q, Double.NaN, sp, Type.EXIT_LONG_STOP); }
public static Order exitLongStop(String s, long q, double sp, String sig) { return new Order(s, q, Double.NaN, sp, Type.EXIT_LONG_STOP, sig); }
public static Order exitLongStopLimit(String s, long q, double sp, double lp) { return new Order(s, q, lp, sp, Type.EXIT_LONG_STOP_LIMIT); }
public static Order exitLongStopLimit(String s, long q, double sp, double lp, String sig) { return new Order(s, q, lp, sp, Type.EXIT_LONG_STOP_LIMIT, sig); }
public static Order exitShort(String s, long q) { return new Order(s, q, Double.NaN, Double.NaN, Type.EXIT_SHORT); }
public static Order exitShort(String s, long q, String sig) { return new Order(s, q, Double.NaN, Double.NaN, Type.EXIT_SHORT, sig); }
public static Order exitShortLimit(String s, long q, double lp) { return new Order(s, q, lp, Double.NaN, Type.EXIT_SHORT_LIMIT); }
public static Order exitShortLimit(String s, long q, double lp, String sig) { return new Order(s, q, lp, Double.NaN, Type.EXIT_SHORT_LIMIT, sig); }
public static Order exitShortStop(String s, long q, double sp) { return new Order(s, q, Double.NaN, sp, Type.EXIT_SHORT_STOP); }
public static Order exitShortStop(String s, long q, double sp, String sig) { return new Order(s, q, Double.NaN, sp, Type.EXIT_SHORT_STOP, sig); }
public static Order exitShortStopLimit(String s, long q, double sp, double lp) { return new Order(s, q, lp, sp, Type.EXIT_SHORT_STOP_LIMIT); }
public static Order exitShortStopLimit(String s, long q, double sp, double lp, String sig) { return new Order(s, q, lp, sp, Type.EXIT_SHORT_STOP_LIMIT, sig); }
public Order(String ss, long qq, double lp, double sp, Type tt) {
this.symbol = ss;
this.quantity = qq;
this.limitPrice = lp; this.stopPrice = sp;
this.type = tt;
this.oca = "";
this.state = State.ACTIVE;
this.stopWasTriggered_ = false;
this.barsValidFor = -1;
}
public Order(String ss, long qq, double lp, double sp, Type tt, String sig) {
this.symbol = ss;
this.quantity = qq;
this.limitPrice = lp; stopPrice = sp;
this.type = tt;
this.oca = "";
this.signal = sig;
this.state = State.ACTIVE;
this.stopWasTriggered_ = false;
this.barsValidFor = -1;
}
public Order(Order oo) {
this.symbol = oo.symbol;
this.quantity = oo.quantity;
this.limitPrice = oo.limitPrice;
this.stopPrice = oo.stopPrice;
this.type = oo.type;
this.oca = oo.oca;
this.signal = oo.signal;
this.barsValidFor = oo.barsValidFor;
}
public Order clone() {
return new Order(this);
}
public String getSymbol() { return symbol; }
public void setSymbol(String symbol) { this.symbol = symbol; }
public double getLimit() { return limitPrice; }
public void setLimit(double price) { this.limitPrice = price; }
public double getStop() { return stopPrice; }
public void setStop(double price) { this.stopPrice = price; }
public long getQuantity() { return quantity; }
public void setQuantity(long quantity) { this.quantity = quantity; }
public String getSignal() { return signal; }
public void setSignal(String signal) { this.signal = signal; }
public void activate() { state = State.ACTIVE; }
public void fill() { state = State.FILLED; }
public void cancel() { state = State.CANCELLED; }
public boolean isActive() { return state == State.ACTIVE; }
public boolean isFilled() { return state == State.FILLED; }
public boolean isCancelled() { return state == State.CANCELLED; }
public boolean isStopped() { return stopWasTriggered_; }
public void makeStopped() { stopWasTriggered_ = true; }
public boolean isBuy() {
switch(type) {
case ENTER_LONG:
case ENTER_LONG_LIMIT:
case ENTER_LONG_STOP:
case ENTER_LONG_STOP_LIMIT:
case EXIT_SHORT:
case EXIT_SHORT_LIMIT:
case EXIT_SHORT_STOP:
case EXIT_SHORT_STOP_LIMIT:
return true;
default:
return false;
}
}
public boolean isLongEntry() {
switch(type) {
case ENTER_LONG:
case ENTER_LONG_LIMIT:
case ENTER_LONG_STOP:
case ENTER_LONG_STOP_LIMIT:
return true;
default:
return false;
}
}
public boolean isLongExit() {
switch(type) {
case EXIT_LONG:
case EXIT_LONG_LIMIT:
case EXIT_LONG_STOP:
case EXIT_LONG_STOP_LIMIT:
return true;
default:
return false;
}
}
public boolean isShortEntry() {
switch(type) {
case ENTER_SHORT:
case ENTER_SHORT_LIMIT:
case ENTER_SHORT_STOP:
case ENTER_SHORT_STOP_LIMIT:
return true;
default:
return false;
}
}
public boolean isShortExit() {
switch(type) {
case EXIT_SHORT:
case EXIT_SHORT_LIMIT:
case EXIT_SHORT_STOP:
case EXIT_SHORT_STOP_LIMIT:
return true;
default:
return false;
}
}
public boolean isLimit() {
switch(type) {
case ENTER_LONG_LIMIT:
case ENTER_LONG_STOP_LIMIT:
case EXIT_LONG_LIMIT:
case EXIT_LONG_STOP_LIMIT:
case ENTER_SHORT_LIMIT:
case ENTER_SHORT_STOP_LIMIT:
case EXIT_SHORT_LIMIT:
case EXIT_SHORT_STOP_LIMIT:
return true;
default:
return false;
}
}
public boolean isStop() {
switch(type) {
case ENTER_LONG_STOP:
case ENTER_LONG_STOP_LIMIT:
case EXIT_LONG_STOP:
case EXIT_LONG_STOP_LIMIT:
case ENTER_SHORT_STOP:
case ENTER_SHORT_STOP_LIMIT:
case EXIT_SHORT_STOP:
case EXIT_SHORT_STOP_LIMIT:
return true;
default:
return false;
}
}
public boolean isSell() { return !isBuy(); }
public boolean isEntry() { return isLongEntry() || isShortEntry(); }
public boolean isExit() { return isLongExit() || isShortExit(); }
// Is this a One-Cancels-All order
public boolean isOca() { return !oca.isEmpty(); }
public void updateState(Bar bar) {
// Check whether the order requires processing (bar expiration is set and is active)
if(!isActive() || barsValidFor < 0) return;
assert barsValidFor > 0 || isCancelled();
if(bar.getDateTime() != lastBar) {
--barsValidFor;
if(barsValidFor == 0) {
cancel();
} else {
lastBar = bar.getDateTime();
}
}
}
/**
* @brief Make the order valid for numBars including the bar on which it is submitted.
*
* To account for the bar on which the order is submitted (and to be independent of the
* current bar), "lastBar_" is initialized to LocalDateTime.MIN. Thus, at the end of that
* bar, the code below will perform the first decrement and consider for canceling.
*
* @param numBars The number of bars this order is valid for
*/
public void setExpiration(int numBars) {
barsValidFor = numBars;
lastBar = LocalDateTime.MIN;
}
private long computeFilledQuantity(long position) {
long result = Long.MIN_VALUE;
assert getQuantity() > 0 || getQuantity() == POSITION_QUANTITY;
if(getQuantity() > 0) {
result = Math.min(getQuantity(), Math.abs(position));
} else if(getQuantity() == POSITION_QUANTITY) {
result = Math.abs(position);
}
return result;
}
public OrderFill tryFill(Tick tick, long position, boolean executeOnLimitOrStop)
{
long filledQuantity = Long.MIN_VALUE;
OrderFill orderFill = null;
// Only active orders are filled
if(isActive()) {
switch(type) {
// Market orders
case ENTER_LONG:
if(position == 0) {
orderFill = new OrderFill(tick.getPrice(), getQuantity(), getQuantity(), getQuantity());
}
break;
case ENTER_SHORT:
if(position == 0) {
orderFill = new OrderFill(tick.getPrice(), getQuantity(), -getQuantity(), -getQuantity());
}
break;
case EXIT_LONG:
if(position > 0) {
filledQuantity = computeFilledQuantity(position);
orderFill = new OrderFill(tick.getPrice(), filledQuantity, -filledQuantity, 0);
}
break;
case EXIT_SHORT:
filledQuantity = computeFilledQuantity(position);
orderFill = new OrderFill(tick.getPrice(), filledQuantity, filledQuantity, 0);
break;
// limit orders
case ENTER_LONG_LIMIT:
if(position == 0) {
if(tick.getPrice() <= limitPrice) {
double fillPrice = executeOnLimitOrStop ? limitPrice : tick.getPrice();
orderFill = new OrderFill(fillPrice, getQuantity(), getQuantity(), getQuantity());
}
}
break;
case EXIT_SHORT_LIMIT:
if(position < 0) {
if(tick.getPrice() <= limitPrice) {
double fillPrice = executeOnLimitOrStop ? limitPrice : tick.getPrice();
filledQuantity = computeFilledQuantity(position);
orderFill = new OrderFill(fillPrice, filledQuantity, filledQuantity, 0);
}
}
break;
// Limit orders
case ENTER_SHORT_LIMIT:
if(position == 0) {
if(limitPrice <= tick.getPrice()) {
double fillPrice = executeOnLimitOrStop ? limitPrice : tick.getPrice();
orderFill = new OrderFill(fillPrice, getQuantity(), -getQuantity(), -getQuantity());
}
}
break;
case EXIT_LONG_LIMIT:
if(position > 0) {
if(limitPrice <= tick.getPrice()) {
double fillPrice = executeOnLimitOrStop ? limitPrice : tick.getPrice();
filledQuantity = computeFilledQuantity(position);
orderFill = new OrderFill(fillPrice, filledQuantity, -filledQuantity, 0);
}
}
break;
// Stop orders
case ENTER_LONG_STOP:
if(position == 0) {
if(stopPrice <= tick.getPrice()) {
double fillPrice = executeOnLimitOrStop ? stopPrice : tick.getPrice();
orderFill = new OrderFill(fillPrice, getQuantity(), getQuantity(), getQuantity());
}
}
break;
case EXIT_SHORT_STOP:
if(position < 0) {
if(stopPrice <= tick.getPrice()) {
double fillPrice = executeOnLimitOrStop ? stopPrice : tick.getPrice();
filledQuantity = computeFilledQuantity(position);
orderFill = new OrderFill(fillPrice, filledQuantity, filledQuantity, 0);
}
}
break;
case EXIT_LONG_STOP:
if(position > 0) {
if(stopPrice >= tick.getPrice()) {
double fillPrice = executeOnLimitOrStop ? stopPrice : tick.getPrice();
filledQuantity = computeFilledQuantity(position);
orderFill = new OrderFill(fillPrice, filledQuantity, -filledQuantity, 0);
}
}
break;
case ENTER_SHORT_STOP:
if(position == 0) {
if(stopPrice >= tick.getPrice()) {
double fillPrice = executeOnLimitOrStop ? stopPrice : tick.getPrice();
orderFill = new OrderFill(fillPrice, getQuantity(), -getQuantity(), -getQuantity());
}
}
break;
case ENTER_LONG_STOP_LIMIT:
if(position == 0) {
if(isStopped()) {
if(limitPrice >= tick.getPrice()) {
double fillPrice = executeOnLimitOrStop ? limitPrice : tick.getPrice();
orderFill = new OrderFill(fillPrice, getQuantity(), getQuantity(), getQuantity());
}
} else if(stopPrice <= tick.getPrice()) {
if((limitPrice >= tick.getPrice()) || (executeOnLimitOrStop && stopPrice <= limitPrice)) {
double fillPrice = executeOnLimitOrStop ? stopPrice : tick.getPrice();
orderFill = new OrderFill(fillPrice, getQuantity(), getQuantity(), getQuantity());
} else {
makeStopped();
}
}
}
break;
case EXIT_LONG_STOP_LIMIT:
if(isStopped()) {
if(limitPrice <= tick.getPrice()) {
double fillPrice = executeOnLimitOrStop ? limitPrice : tick.getPrice();
filledQuantity = computeFilledQuantity(position);
orderFill = new OrderFill(fillPrice, filledQuantity, -filledQuantity, 0);
}
}
else if(stopPrice >= tick.getPrice()) {
if((limitPrice <= tick.getPrice()) || (executeOnLimitOrStop && limitPrice <= stopPrice)) {
double fillPrice = executeOnLimitOrStop ? stopPrice : tick.getPrice();
filledQuantity = computeFilledQuantity(position);
orderFill = new OrderFill(fillPrice, filledQuantity, -filledQuantity, 0);
} else {
makeStopped();
}
}
break;
case ENTER_SHORT_STOP_LIMIT:
if(position == 0) {
if(isStopped()) {
if(limitPrice <= tick.getPrice()) {
double fillPrice = executeOnLimitOrStop ? limitPrice : tick.getPrice();
orderFill = new OrderFill(fillPrice, getQuantity(), -getQuantity(), -getQuantity());
}
} else if(stopPrice >= tick.getPrice()) {
if((limitPrice <= tick.getPrice()) || (executeOnLimitOrStop && stopPrice >= limitPrice)) {
double fillPrice = executeOnLimitOrStop ? stopPrice : tick.getPrice();
orderFill = new OrderFill(fillPrice, getQuantity(), -getQuantity(), -getQuantity());
} else {
makeStopped();
}
}
}
break;
case EXIT_SHORT_STOP_LIMIT:
if(position < 0) {
if(isStopped()) {
if(limitPrice >= tick.getPrice()) {
double fillPrice = executeOnLimitOrStop ? limitPrice : tick.getPrice();
filledQuantity = computeFilledQuantity(position);
orderFill = new OrderFill(fillPrice, filledQuantity, -filledQuantity, 0);
}
}
else if(stopPrice <= tick.getPrice()) {
if((limitPrice >= tick.getPrice()) || (executeOnLimitOrStop && stopPrice <= limitPrice)) {
double fillPrice = executeOnLimitOrStop ? stopPrice : tick.getPrice();
filledQuantity = computeFilledQuantity(position);
orderFill = new OrderFill(fillPrice, filledQuantity, -filledQuantity, 0);
} else {
makeStopped();
}
}
}
break;
}
}
return orderFill;
}
public JsonObject toJsonString() {
JsonObject jo = new JsonObject();
jo.addProperty("type", type.name());
if(getQuantity() != POSITION_QUANTITY) {
jo.addProperty("quantity", getQuantity());
}
if(!Double.isNaN(getLimit())) {
jo.addProperty("limit_price", getLimit());
}
if(!Double.isNaN(getStop())) {
jo.addProperty("stop_price", getStop());
}
return jo;
}
public Order addLimitToStop(double offset) {
if(this.type == Type.ENTER_LONG_STOP) {
this.limitPrice = this.stopPrice;
this.stopPrice -= offset;
this.type = Type.ENTER_LONG_STOP_LIMIT;
} else if(this.type == Type.ENTER_SHORT_STOP) {
this.limitPrice = this.stopPrice;
this.stopPrice += offset;
this.type = Type.ENTER_SHORT_STOP_LIMIT;
} else if(this.type == Type.EXIT_SHORT_STOP) {
this.limitPrice = this.stopPrice;
this.stopPrice -= offset;
this.type = Type.EXIT_SHORT_STOP_LIMIT;
} else if(this.type == Type.EXIT_LONG_STOP) {
this.limitPrice = this.stopPrice;
this.stopPrice += offset;
this.type = Type.EXIT_LONG_STOP_LIMIT;
}
return this;
}
}