Warning: session_start(): open(/var/lib/php/sessions/sess_b6s6qauoe6ug5jqfb9t4541tso, O_RDWR) failed: No space left on device (28) in /var/www/tb-usercake/models/config.php on line 51
Warning: session_start(): Failed to read session data: files (path: /var/lib/php/sessions) in /var/www/tb-usercake/models/config.php on line 51
set flag Reparse.
persistable sclass Corridor {
// how many percent the crypto price needs to move
// for one profitable trade
settable double cellSize = 0.2;
// how many cells the corridor has
settable int capacity = 6;
settable bool verbose;
settable bool openTwoPositionsAtStart = true;
settable bool alwaysOpenTwoPositions;
// position options
settable double leverage = 1;
settable double marginPerPosition = 1; // in USDT
// link to market (optional)
settable IFuturesMarket market;
settable S marginCoin = "USDT";
settable double adversity = 0.02;
settable new LS log;
// internal
settable PriceDigitizer2 digitizer;
gettable int direction;
settable double epsilon = 1e-6;
// maximum debt seen
gettable double maxDebt;
// increment of crypto we can bet on
settable double cryptoStep = 0.0001;
// minimum crypto we need to bet on
settable double minCrypto = 0.0001;
record noeq Position(double openingPrice, double direction, double digitizedOpeningPrice) {
double closingPrice = Double.NaN;
long openingStep = stepCount, closingStep;
long openingTime = currentTime(), closingTime;
double leverage = Corridor.this.leverage;
settable double cryptoAmount;
settable O closeReason;
settable O closeError;
double digitizedOpeningPrice() { ret digitizedOpeningPrice; }
bool isLong() { ret direction > 0; }
bool isShort() { ret direction < 0; }
bool closed() { ret !isNaN(closingPrice); }
S type() { ret trading_directionToPositionType(direction); }
double profitAtPrice(double price) {
ret ((price-openingPrice)/openingPrice*direction*100-adversity)*leverage;
}
double profit() {
ret profitAtPrice(closed() ? closingPrice : currentPrice());
}
void close(O closeReason) {
if (closed()) fail("Can't close again");
if (closeReason != null) closeReason(closeReason);
openPositions.remove(this);
closingPrice = currentPrice();
closingTime = currentTime();
closingStep = stepCount;
closedPositions.add(this);
realizedProfit += profit();
print(this);
printPositions();
}
toString {
ret commaCombine(
spaceCombine(
closed() ? "Closed" : null,
formatDouble1(leverage) + "X " + upper(type())
),
!closed() ? null : or(closeReason, "unknown reason"),
"profit: " + formatProfit(profit()),
"margin: " + marginCoin + " " + formatPrice(margin()),
"crypto: " + /*formatPrice*/(cryptoAmount),
"opening price: " + formatPriceX(openingPrice)
+ " @ step " + openingStep,
!closed() ? null : "closing price: " + formatPriceX(closingPrice)
+ " @ step " + closingStep,
//"digitized: " + formatPrice(digitizedOpeningPrice()),
);
}
double margin() {
ret cryptoAmount*openingPrice/leverage;
}
void closeOnMarket {
try {
if (market != null)
market.closePosition(new IFuturesMarket.CloseOrder()
.holdSide(HoldSide.fromInt(direction))
.cryptoAmount(cryptoAmount));
} catch e {
closeError = toPersistableThrowable(e);
}
}
void openOnMarket {
try {
if (market != null)
market.openPosition(new IFuturesMarket.OpenOrder()
.holdSide(HoldSide.fromInt(direction))
.cryptoAmount(cryptoAmount)
.leverage(leverage)
.isCross(true));
} catch e {
close(toPersistableThrowable(e));
}
}
}
gettable double currentPrice = 0;
gettable double oldPrice = Double.NaN;
gettable double startingPrice = Double.NaN;
gettable int lastDirection;
gettable double realizedProfit;
gettable long stepCount;
gettable new LinkedHashSet openPositions;
gettable new L closedPositions;
Position openPosition(double direction) {
var price = digitizedPrice();
if (isNaN(price)) price = digitizer.digitizeIndividually(currentPrice());
var p = new Position(currentPrice(), direction, price);
double cryptoAmount = marginPerPosition/price*leverage;
cryptoAmount = roundTo(cryptoStep, cryptoAmount);
p.cryptoAmount = max(minCrypto, cryptoAmount);
openPositions.add(p);
print("Opening " + p);
printPositions();
p.openOnMarket();
ret p;
}
bool hasPosition(double price, double direction) {
ret findPosition(price, direction) != null;
}
Position closePosition(double price, double direction, O closeReason) {
var p = findPosition(price, direction);
p?.close(closeReason);
ret p;
}
void closePositions(Cl positions, O closeReason default null) {
forEach(positions, -> .close(closeReason));
}
Position findPosition(double digitizedPrice, double direction) {
ret firstThat(openPositions(), p ->
diffRatio(p.digitizedOpeningPrice(), digitizedPrice) <= epsilon() && p.direction == direction);
}
Position openShort() { ret openPosition(-1); }
Position openLong() { ret openPosition(1); }
void printPositions {
print(colonCombine(n2(openPositions, "open position"),
joinWithComma(openPositions)));
}
bool started() { ret !isNaN(startingPrice); }
void start {
if (started()) fail("Already started");
if (isNaN(currentPrice())) fail("Please feed me the first price before starting me");
// Save starting price, create price cells & digitizer
startingPrice = currentPrice();
var priceCells = makePriceCells(startingPrice);
digitizer = new PriceDigitizer2(priceCells);
digitizer.verbose(verbose);
//digitizer.init(startingPrice);
print("Starting CORRIDOR at " + startingPrice + " +/- " + cellSize);
if (openTwoPositionsAtStart) {
++stepCount;
openPosition(1);
openPosition(-1);
afterStep();
}
}
void prices(double... prices) {
fOr (price : prices)
price(price);
}
swappable PriceCells makePriceCells(double basePrice) {
//ret new RegularPriceCells(basePrice, cellSize);
ret new GeometricPriceCells(basePrice, cellSize);
}
double digitizedPrice() { ret digitizer.digitizedPrice(); }
double lastDigitizedPrice() { ret digitizer.lastDigitizedPrice(); }
void currentPrice aka price(double price) {
currentPrice = price;
if (digitizer == null) ret;
digitizer.digitize(price);
if (isNaN(digitizedPrice()))
// Haven't crossed a cell limit yet
ret;
direction = sign(digitizedPrice()-lastDigitizedPrice());
if (direction == 0) ret; // No cell move
++stepCount;
printWithPrecedingNL("New digitized price: " + digitizedPrice());
if (started())
step();
print(this);
}
swappable void step {
double p1 = lastDigitizedPrice();
double p2 = digitizedPrice();
direction = sign(p2-p1);
if (direction == 0) ret;
// Find winning positions to close
new L toClose;
for (Position p : direction > 0
? longPositionsAtOrBelowDigitizedPrice(p1)
: shortPositionsAtOrAboveDigitizedPrice(p1)) {
p.closeReason("WIN");
log("Closing winner position at " + currentPrice + " (" + p1 + " -> " + p2 + "): " + p);
toClose.add(p);
}
// Dismantle corridor if too large
double limit = fromCellNumber(toCellNumber(digitizedPrice())-capacity*direction);
var toDismantle = direction > 0
? shortPositionsAtOrBelowDigitizedPrice(limit)
: longPositionsAtOrAboveDigitizedPrice(limit);
print("DISMANTLE LIMIT: " + formatPriceX(limit));
for (Position p : toDismantle) {
p.closeReason("DISMANTLE");
toClose.add(p);
}
//printVars("step", +p1, +p2, +direction, +closed, +opened);
if (nempty(toClose))
closePositions(toClose);
// Open backward position here (if not there yet)
if (!hasPosition(p2, -direction))
openPosition(-direction);
// Open forward position if configured as such
if (alwaysOpenTwoPositions && !hasPosition(p2, direction))
openPosition(direction);
afterStep();
}
void afterStep {
maxDebt = max(maxDebt, debt());
}
double fromCellNumber(double cellNumber) { ret cells().fromCellNumber(cellNumber); }
double toCellNumber(double price) { ret cells().toCellNumber(price); }
double unrealizedProfit() {
ret doubleSum(map(openPositions, ->.profit()));
}
L shortPositionsAtOrBelowPrice(double openingPrice) {
ret filter(openPositions, p -> p.isShort() && p.openingPrice <= openingPrice);
}
L longPositionsAtOrAbovePrice(double openingPrice) {
ret filter(openPositions, p -> p.isLong() && p.openingPrice >= openingPrice);
}
L shortPositionsAtOrBelowDigitizedPrice(double openingPrice) {
ret filter(openPositions, p -> p.isShort() && p.digitizedOpeningPrice() <= openingPrice);
}
L shortPositionsAtOrAboveDigitizedPrice(double openingPrice) {
ret filter(openPositions, p -> p.isShort() && p.digitizedOpeningPrice() >= openingPrice);
}
L longPositionsAtOrAboveDigitizedPrice(double openingPrice) {
ret filter(openPositions, p -> p.isLong() && p.digitizedOpeningPrice() >= openingPrice);
}
L longPositionsAtOrBelowDigitizedPrice(double openingPrice) {
ret filter(openPositions, p -> p.isLong() && p.digitizedOpeningPrice() <= openingPrice);
}
L negativePositions() {
ret filter(openPositions, p -> p.profit() < 0);
}
double debt() {
ret max(0, -unrealizedProfit());
}
double profit() {
ret realizedProfit+unrealizedProfit();
}
LS status() {
var r = currentCorridorPriceRange();
ret ll(
"Current price: " + formatPriceX(currentPrice) + ", digitized: " + formatPriceX(digitizedPrice()),
"Leverage: " + leverage + ", margin per position: " + marginCoin+ " " + formatPrice(marginPerPosition),
"Cell size: " + formatPercentage(cellSize, 3) + ", max capacity: " + capacity,
"Current corridor: " + (r == null ? "-" : formatPrice(r.start) + " to " + formatPrice(r.end) + " (" + formatPercentage(currentCorridorSizeInPercent(), 3) + ")"),
"Step " + n2(stepCount),
"Profit: " + formatProfit(profit()),
"Realized profit: " + formatProfit(realizedProfit) + " from " + n2(closedPositions, "closed position"),
"Unrealized profit: " + formatProfit(unrealizedProfit()) + " in " + n2(openPositions, "open position"),
"Debt: " + formatProfit(debt()) + " (max seen: " + formatProfit(maxDebt) + ")",
);
}
LS fullStatus() {
ret listCombine(
status(),
"",
n2(openPositions, "open position") + ":",
openPositions,
"",
n2(closedPositions, "closed position") + ":",
reversed(closedPositions)
);
}
S formatProfit(double x) {
ret dropMinusFromZero(addPlusIfPositive(formatDouble1(x)));
}
toString { ret commaCombine(status()); }
double maxCorridorPercentSize() {
ret capacity*cellSize;
}
PriceCells cells() {
ret digitizer?.cells;
}
swappable long currentTime() { ret now(); }
DoubleRange currentCorridorPriceRange() {
L openingPrices = map(openPositions, ->.openingPrice);
ret doubleRange(doubleMin(openingPrices), doubleMax(openingPrices));
}
double currentCorridorSizeInPercent() {
DoubleRange r = currentCorridorPriceRange();
ret r == null ? 0 : asPercentIncrease(r.end, r.start);
}
void log(O o) {
if (o != null)
log.add(print(str(o)));
}
S formatPriceX(double price) {
if (isNaN(price)) ret "-";
double num = cells().priceToCellNumber(price);
ret formatPrice(price) + " (C" + formatDouble2(num) + ")";
}
}