Warning: session_start(): open(/var/lib/php/sessions/sess_l8i0qvdnb3fnketu6erqa6se8l, 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
concept Chaser extends G22TradingStrategy {
// Juicer parameters
// Maximum duration of a position
settable double maxPositionHours = 4;
// Max loss per position before leverage
settable double maxLoss = 0.6;
// Ordinary pullback (fixed price percentage)
settable double pullback = 0.4;
// Relative pullback (percentage of profit)
settable double relativePullback; // e.g. 20
settable double minPullback; // e.g. 0.1
settable double maxPullback; // e.g. 4
settable Opener opener;
// internal
new RunLengthCounter rlc;
OpenSignal openSignal;
// trigger = 0 to 1
record noeq CloseSignal(S reason, double trigger) {
double trigger() { ret trigger; }
toString { ret "Close signal " + reason + " (" + iround(trigger*100) + "%)"; }
}
// trigger = 0 to 1
record noeq OpenSignal(S reason, double trigger, int direction) {
double trigger() { ret trigger; }
toString { ret "Open signal " + reason + " (" + iround(trigger*100) + "%)"; }
}
persistable class BaseJuicer {
settable double maxLoss;
L closeSignals(Position p) {
new L signals;
double profit = p.profitBeforeLeverage();
// How close are we to the max closing time?
if (p.maxClosingTime() < infinity())
signals.add(new CloseSignal("Age", transformToZeroToOne(currentTime(), p.openingTime(), p.maxClosingTime())));
// How close are we to our loss limit?
if (profit < 0)
signals.add(new CloseSignal("Loss", doubleRatio(-profit, maxLoss));
ret signals;
}
}
// Juicer with fixed pullback
persistable class Juicer extends BaseJuicer {
settable double pullback;
*(double *maxLoss, double *pullback) {}
toString { ret renderRecord("Juicer", +maxLoss, +pullback); }
L closeSignals(Position p) {
L signals = super.closeSignals(p);
double profit = p.profitBeforeLeverage();
// How close are we to the pullback limit?
if (profit >= 0)
signals.add(new CloseSignal("Profit", transformToZeroToOne(profit,
p.crest, p.crest-pullback));
ret signals;
}
}
// Juicer with relative pullback
persistable class RelativePullbackJuicer extends BaseJuicer {
settable double relativePullback; // e.g. 20
settable double minPullback; // e.g. 0.1
settable double maxPullback; // e.g. 4
toString { ret renderRecord("RelativePullbackJuicer", +maxLoss, +relativePullback, +minPullback, +maxPullback); }
L closeSignals(Position p) {
L signals = super.closeSignals(p);
double profit = p.profitBeforeLeverage();
// How close are we to the pullback limit?
if (profit >= 0)
signals.add(new CloseSignal("Profit", transformToZeroToOne(profit,
p.crest, p.crest-calculatePullback(p.crest)));
ret signals;
}
double calculatePullback(double crest) {
ret clamp(relativePullback/100*crest, minPullback, maxPullback);
}
}
abstract sclass Opener {
abstract OpenSignal openSignal();
abstract Opener cloneInto(Chaser strat);
}
record PreRunOpener(int minRunUp, int minRunDown) extends Opener {
Opener cloneInto(Chaser strat) {
ret strat.new PreRunOpener(minRunUp, minRunDown);
}
OpenSignal openSignal() {
int direction = Chaser.this.direction;
if (direction == 0) null;
int preRun = direction > 1 ? minRunUp : minRunDown;
int runLength = toInt(rlc.runLength());
/*printConcat(
"[",
formatLocalDateWithSeconds(currentTime()),
"] ",
"Runlength ",
runLength,
" price: ",
formatPriceX(currentPrice())
);*/
// TODO: even more precise signal
//if (runLength >= preRun) print("Pre-run signal!");
ret new OpenSignal("RunLength " + runLength, doubleRatio(runLength, preRun), direction);
}
toString { ret "PreRunOpener up " + minRunUp + ", down " + minRunDown; }
}
persistable class Position extends G22TradingStrategy.Position {
settable OpenSignal openSignal;
settable BaseJuicer juicer;
// highest profit this position has reached
settable double crest = -infinity();
double maxClosingTime() {
ret openingTime()+hoursToMS(maxPositionHours);
}
L closeSignals() {
ret juicer == null ?: juicer.closeSignals(this);
}
// >= 1 to close
// otherwise keep open
double closeSignal() {
CloseSignal strongestSignal = closeSignalWithDescription();
ret strongestSignal == null ? 0 : strongestSignal.trigger;
}
CloseSignal closeSignalWithDescription() {
ret highestBy(closeSignals(), signal -> signal.trigger);
}
void update {
crest = max(crest, profitBeforeLeverage());
}
toString {
var closeSignal = closed() ? null : closeSignalWithDescription();
ret commaCombine(super.toString(),
closeSignal
);
}
}
LS status() {
ret listCombine(
commaCombine(
"Max loss: " + formatDouble3(maxLoss) + "%",
relativePullback == 0
? "pullback: " + formatDouble3(pullback) + "%"
: commaCombine(
"relative pullback: " + formatDouble3(relativePullback) + "%",
"min pb: " + formatDouble3(minPullback) + "%",
"max pb: " + formatDouble3(maxPullback) + "%",
)
),
"Opener: " + opener,
"Run length: " + signToUpDown(unnull(rlc.value())) + " " + rlc.runLength(),
openSignal,
super.status()
);
}
Position openPosition(OpenSignal openSignal) {
int direction = openSignal.direction;
if (l(positionsInDirection(direction)) > 0) {
log("Not opening a second position in direction " + direction);
null;
}
nextStep();
new Position p;
p.openSignal(openSignal);
p.juicer(makeJuicer());
ret openPosition(p, direction);
}
BaseJuicer makeJuicer() {
if (relativePullback != 0)
ret new RelativePullbackJuicer()
.relativePullback(relativePullback)
.minPullback(minPullback)
.maxPullback(maxPullback)
.maxLoss(maxLoss);
else
ret new Juicer(maxLoss, pullback);
}
void start {
if (started()) fail("Already started");
if (currentPrice() == 0)
ret with primed(true);
// Save starting price, create price cells & digitizer
startingPrice = currentPrice();
var priceCells = makePriceCells(startingPrice);
digitizer = new PriceDigitizer2(priceCells);
digitizer.verbose(verbose);
log("Starting CHASER at " + startingPrice + " +/- " + cellSize);
}
void price(double price) {
if (currentPrice == price) ret;
currentPrice = price;
if (!started()) {
if (primed) {
primed(false);
start();
nextStep();
beforeStep();
afterStep();
}
ret;
}
digitizer.digitize(price);
direction = sign(digitizedPrice()-lastDigitizedPrice());
if (direction != 0) lastDirection = direction;
if (started())
step();
}
swappable void step {
// Update positions with new price
for (p : openPositions()) {
cast p to Position;
p.update();
}
// Find positions to close
L toClose = new L;
for (p : openPositions()) {
cast p to Position;
CloseSignal signal = p.closeSignalWithDescription();
if (signal != null && signal.trigger() >= 1) {
p.closeReason(signal);
log("Closing position because " + signal + ": " + p);
toClose.add(p);
}
}
if (nempty(toClose)) {
nextStep();
closePositions(toClose);
afterStep();
}
beforeStep();
double p1 = lastDigitizedPrice();
double p2 = digitizedPrice();
direction = sign(p2-p1);
if (direction != 0)
rlc.add(direction);
openSignal = makeOpenSignal();
if (openSignal != null && openSignal.trigger >= 1)
openPosition(openSignal);
afterStep();
}
swappable OpenSignal makeOpenSignal() {
ret opener == null ?: opener.openSignal();
}
swappable void beforeStep() {}
S baseToString() {
ret colonCombine(
_conceptID() == 0 ? "" : "Strategy " + _conceptID(),
"Chaser profit " + formatMarginProfit(coinProfit()));
}
void reset :: before {
resetFields(this, "rlc openSignal");
}
Chaser emptyClone() {
Chaser strat = cast super.emptyClone();
ret strat.opener(opener?.cloneInto(strat));
}
// Chaser.toString
toString { ret baseToString(); }
selfType preRunOpener(int minRunUp, int minRunDown) {
ret opener(new PreRunOpener(minRunUp, minRunDown));
}
}