longPositionsAtOrBelowDigitizedPrice(double openingPrice) {
return filter(openPositions, p -> p.isLong() && p.digitizedOpeningPrice() <= openingPrice);
}
final PriceCells priceCells(){ return cells(); }
PriceCells cells() {
return digitizer == null ? null : digitizer.cells;
}
String formatPriceX(double price) {
if (isNaN(price)) return "-";
String s = formatPrice(price);
if (cells() == null) return s;
double num = cells().priceToCellNumber(price);
return s + " (C" + formatDouble2(num) + ")";
}
final void currentPrice(double price){ price(price); }
abstract void price(double price);
boolean inCooldown() {
if (cooldownMinutes <= 0) return false;
if (nempty(openPositions)) return true;
var lastClosed = last(closedPositions);
if (lastClosed == null) return false;
var minutes = toMinutes(currentTime()-lastClosed.closingTime);
return minutes < cooldownMinutes;
}
void assureCanOpen(Position p) {
if (cooldownMinutes > 0) {
if (nempty(openPositions))
throw fail("Already have an open position");
var lastClosed = last(closedPositions);
if (lastClosed != null) {
var minutes = toMinutes(currentTime()-lastClosed.closingTime);
if (minutes < cooldownMinutes)
throw fail("Last position closed " + formatMinutes(fromMinutes(minutes)) + " ago, cooldown is " + cooldownMinutes);
}
}
}
P openPosition(P p, int direction) { return openPosition(p, direction, null); }
P openPosition(P p, int direction, Object openReason) {
try {
assureCanOpen(p);
p.openReason(openReason);
var price = digitizedPrice();
var realPrice = currentPrice();
logVars("openPosition", "realPrice", realPrice, "price", price, "digitizer", digitizer);
if ((isNaN(price) || price == 0) && digitizer != null) {
price = digitizer.digitizeIndividually(currentPrice());
print("digitized individually: " + price);
}
p.openingPrice(realPrice);
p.direction(direction);
p.digitizedOpeningPrice(price);
double cryptoAmount = p.marginToUse/realPrice*leverage;
cryptoAmount = roundTo(cryptoStep, cryptoAmount);
log(renderVars("openPosition", "marginPerPosition", marginPerPosition, "realPrice", realPrice, "leverage", leverage, "cryptoAmount", cryptoAmount, "cryptoStep", cryptoStep));
p.cryptoAmount = max(minCrypto, cryptoAmount);
p.open();
//print("Opening " + p);
//printPositions();
p.openOnMarket();
return p;
} catch (Throwable e) { log(e);
throw rethrow(e); }
}
List status() {
double mulProf = multiplicativeProfit();
return llNonNulls(
empty(comment) ? null : "Comment: " + comment,
"Profit: " + marginCoin + " " + plusMinusFix(formatMarginPrice(coinProfit())),
"Realized profit: " + marginCoin + " " + formatMarginProfit(realizedCoinProfit) + " from " + n2(closedPositions, "closed position")
+ " (" + formatMarginProfit(realizedCoinWins) + " from " + n2(realizedWins, "win")
+ ", " + formatMarginProfit(realizedCoinLosses) + " from " + n2(realizedLosses, "loss", "losses") + ")",
"Unrealized profit: " + marginCoin + " " + formatMarginProfit(unrealizedCoinProfit()) + " in " + n2(openPositions, "open position"),
isNaN(mulProf) ? null : "Multiplicative profit: " + formatProfit(mulProf) + "%",
//baseToString(),
!primed() ? null : "Primed",
!started() ? null : "Started. current price: " + formatPriceX(currentPrice)
+ (isNaN(digitizedPrice()) ? "" : ", digitized: " + formatPriceX(digitizedPrice())),
(riskPerTrade == 0 ? "" : "Risk per trade: " + formatDouble1(riskPerTrade) + "%. ")
+ "Position size: " + marginCoin + " " + formatPrice(marginPerPosition) + "x" + formatDouble1(leverage) + " = " + marginCoin + " " + formatPrice(positionSize()),
!usingCells() ? null : "Cell size: " + formatCellSize(cellSize),
spaceCombine("Step " + n2(stepCount), renderStepSince()),
//"Debt: " + marginCoin + " " + formatMarginProfit(debt()) + " (max seen: " + formatMarginProfit(maxDebt) + ")",
"Investment used: " + marginCoin + " " + formatMarginPrice(maxInvestment()),
strategyJuicer == null ? null : "Strategy juicer: " + strategyJuicer);
}
String renderStepSince() {
if (stepSince == 0) return "";
return "since " + (active()
? formatHoursMinutesColonSeconds(currentTime()-stepSince)
: formatLocalDateWithSeconds(stepSince));
}
List fullStatus() {
return listCombine(
status(),
"",
n2(openPositions, "open position") + ":",
reversed(openPositions),
"",
n2(closedPositions, "closed position")
+ " (" + (showClosedPositionsBackwards ? "latest first" : "oldest first") + "):",
showClosedPositionsBackwards ? reversed(closedPositions) : closedPositions);
}
void feed(PricePoint pricePoint) {
if (!active()) return;
setTime(pricePoint.timestamp);
price(pricePoint.price);
}
void feed(TickerSequence ts) {
if (!active()) return;
if (ts == null) return;
for (var pricePoint : ts.pricePoints())
feed(pricePoint);
}
public int compareTo(G22TradingStrategy s) {
return s == null ? 1 : cmp(coinProfit(), s.coinProfit());
}
// Returns closed positions
List closeAllPositions() { return closeAllPositions("User close"); }
List closeAllPositions(Object reason) {
var positions = openPositions();
closePositions(positions, reason);
return (List) positions;
}
void closeMyself() {
closedItself(currentTime());
closeAllPositionsAndDeactivate();
}
void closeAllPositionsAndDeactivate() {
deactivate();
closeAllPositions();
}
void deactivate() {
if (!active) return;
active(false);
deactivated(currentTime());
log("Strategy deactivated.");
}
final void reset_G22TradingStrategy(){ reset(); }
void reset() {
resetFields(this, fieldsToReset());
change();
}
final G22TradingStrategy emptyClone_G22TradingStrategy(){ return emptyClone(); }
G22TradingStrategy emptyClone() {
var clone = shallowCloneToUnlistedConcept(this);
clone.reset();
return clone;
}
List allPositions() {
return concatLists(openPositions, closedPositions);
}
List sortedPositions() {
var allPositions = allPositions();
return sortedByCalculatedField(allPositions, __7 -> __7.openingTime());
}
boolean positionsAreNonOverlapping() {
for (var __0: overlappingPairs(sortedPositions()))
{ var a = pairA(__0); var b = pairB(__0); if (b.openingTime() < a.closingTime())
return false; }
return true;
}
// Profit when applying all positions (somewhat theoretical because you
// might go below platform limits)
// Also only works when positions are linear
double multiplicativeProfit() {
if (!positionsAreNonOverlapping()) return Double.NaN;
double profit = 1;
for (var p : sortedPositions())
profit *= 1+p.profit()/100;
return (profit-1)*100;
}
boolean haveBackData() { return backDataHoursWanted == 0 | backDataFed; }
boolean didRealTrades() {
return any(allPositions(), p -> p.openedOnMarket() || p.closedOnMarket());
}
String formatCellSize(double cellSize) {
return formatPercentage(cellSize, 3);
}
String areaDesc() {
if (eq(area, "Candidates")) return "Candidate";
return nempty(area) ? area : archived ? "Archived" : "";
}
final G22TradingStrategy currentTime(long time){ return setTime(time); }
G22TradingStrategy setTime(long time) {
int age = ifloor(ageInHours());
long lastMod = mod(currentTime()-startTime, hoursToMS(1));
currentTime = () -> time;
if (ifloor(ageInHours()) > age)
log("Hourly profit log: " + formatMarginProfit(coinProfit()));
return this;
}
double ageInHours() { return startTime == 0 ? 0 : msToHours(currentTime()-startTime); }
// only use near start of strategy, untested otherwise
G22TradingStrategy changeCellSize(double newCellSize) {
double oldCellSize = cellSize();
if (oldCellSize == newCellSize) return this;
cellSize(newCellSize);
if (digitizer != null)
digitizer.swapPriceCells(makePriceCells(priceCells().basePrice()));
log("Changed cell size from " + oldCellSize + " to " + newCellSize);
return this;
}
boolean hasClosedItself() { return closedItself != 0; }
public double juiceValue() { return coinProfit(); }
// drift is our cumulative delta (=sum of all signed position
// quantities)
double drift() {
double drift = 0;
for (var p : openPositions())
drift += p.cryptoAmount()*p.direction();
return drift;
}
String formatCryptoAmount(double amount) {
return formatDouble3(amount);
}
Position openShort() { return openPosition(-1); }
Position openLong() { return openPosition(1); }
Position openPosition(int direction) { return openPosition(direction, null); }
Position openPosition(int direction, Object openReason) {
Position p = new Position();
p.marginToUse = marginToUseForNewPosition();
return openPosition(p, direction, openReason);
}
// Open or close positions until the drift (delta) is
// equal to targetDrift (or as close as the platform's
// restrictions allow).
// Returns the list of positions opened or closed
// (Function is in dev.)
List adjustDrift(double targetDrift) { return adjustDrift(targetDrift, null); }
List adjustDrift(double targetDrift, Object reason) {
List changeList = new ArrayList();
// target 0? close all
if (targetDrift == 0)
return closeAllPositions(reason);
double drift = drift();
// target already reached? done
if (drift == targetDrift) return changeList;
// Are we changing direction? Then close everything first.
if (sign(drift) != sign(targetDrift))
changeList.addAll(closeAllPositions(reason));
// Now we know targetDrift and drift have the same sign.
if (abs(targetDrift) > abs(drift)) {
// We need to open new positions
} else {
// We need to close positions
}
return changeList;
}
List winners() { return filter(closedPositions(), p -> p.coinProfit() > 0); }
List losers() { return filter(closedPositions(), p -> p.coinProfit() < 0); }
List extends Position> openPositions() { return cloneList(openPositions); }
double winRate() {
return l(winners()) * 100.0 / l(closedPositions);
}
boolean usingCells() { return true; }
double positionSize() {
return marginToUseForNewPosition()*leverage;
}
double marginToUseForNewPosition() {
return scaleMargin(marginPerPosition);
}
// to simulate compounding in backtest
double scaleMargin(double margin) {
// Live? Then no scaling.
if (usingLiveData) return margin;
return margin*compoundingFactor();
}
double compoundingFactor() {
// No compounding selected?
if (compoundingBaseEquity == 0) return 1;
return max(0, remainingEquity()/compoundingBaseEquity);
}
// only for backtesting
double remainingEquity() {
return compoundingBaseEquity+realizedCoinProfit();
}
class RiskToMargin {
// in percent
final public RiskToMargin setRiskForTrade(double riskForTrade){ return riskForTrade(riskForTrade); }
public RiskToMargin riskForTrade(double riskForTrade) { this.riskForTrade = riskForTrade; return this; } final public double getRiskForTrade(){ return riskForTrade(); }
public double riskForTrade() { return riskForTrade; }
double riskForTrade = riskPerTrade;
final public RiskToMargin setStopLossPercent(double stopLossPercent){ return stopLossPercent(stopLossPercent); }
public RiskToMargin stopLossPercent(double stopLossPercent) { this.stopLossPercent = stopLossPercent; return this; } final public double getStopLossPercent(){ return stopLossPercent(); }
public double stopLossPercent() { return stopLossPercent; }
double stopLossPercent;
final public RiskToMargin setPrice(double price){ return price(price); }
public RiskToMargin price(double price) { this.price = price; return this; } final public double getPrice(){ return price(); }
public double price() { return price; }
double price = currentPrice();
final public RiskToMargin setFullEquity(double fullEquity){ return fullEquity(fullEquity); }
public RiskToMargin fullEquity(double fullEquity) { this.fullEquity = fullEquity; return this; } final public double getFullEquity(){ return fullEquity(); }
public double fullEquity() { return fullEquity; }
double fullEquity;
double riskedEquity() {
return fullEquity*riskForTrade/100;
}
double qty() {
return riskedEquity()/(price*stopLossPercent/100);
}
final double get(){ return margin(); }
double margin() {
return qty()*price/leverage();
}
}
void fixRealizedStats() {
realizedProfit(0);
realizedCoinProfit(0);
realizedWins(0);
realizedCoinWins(0);
realizedLosses(0);
realizedCoinLosses(0);
for (var p : closedPositions()) p.addToRealizedStats();
}
// p must not be open
boolean deletePosition(Position p) {
if (closedPositions.remove(p) || positionsThatFailedToOpen.remove(p)) {
fixRealizedStats();
return true;
}
return false;
}
boolean hasRiskPerTrade() { return riskPerTrade != 0; }
Position addFakePosition(double coinProfit) {
Position p = new Position();
p.makeFake(coinProfit);
return p;
}
}
static String aGlobalID() {
return randomID(globalIDLength());
}
static String aGlobalID(Random random) {
return randomID(random, globalIDLength());
}
static Q startQ() {
return new Q();
}
static Q startQ(String name) {
return new Q(name);
}
static boolean eq(Object a, Object b) {
return a == b || a != null && b != null && a.equals(b);
}
// a little kludge for stuff like eq(symbol, "$X")
static boolean eq(Symbol a, String b) {
return eq(str(a), b);
}
static void change() {
//mainConcepts.allChanged();
// safe version for now cause function is sometimes included unnecessarily (e.g. by EGDiff)
callOpt(getOptMC("mainConcepts"), "allChanged");
}
static int area(Rect r) {
return rectArea(r);
}
static double area(DoubleRect r) {
return r == null ? 0 : r.w*r.h;
}
static int area(WidthAndHeight img) {
return img == null ? 0 : img.getWidth()*img.getHeight();
}
static int area(BufferedImage img) {
return img == null ? 0 : img.getWidth()*img.getHeight();
}
static double log(double d) {
return Math.log(d);
}
static double log(double d, double base) {
return Math.log(d)/Math.log(base);
}
static double infinity() {
return positiveInfinity();
}
static long now_virtualTime;
static long now() {
return now_virtualTime != 0 ? now_virtualTime : System.currentTimeMillis();
}
static String str(Object o) {
return o == null ? "null" : o.toString();
}
static String str(char[] c) {
return c == null ? "null" : new String(c);
}
static String str(char[] c, int offset, int count) {
return new String(c, offset, count);
}
static