//FILENAME: gazelleModules/MPMInGazelle.java package gazelleModules; import java.util.*; import java.util.zip.*; import java.util.List; import java.util.regex.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.concurrent.locks.*; import java.util.function.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; import javax.swing.table.*; import java.io.*; import java.net.*; import java.lang.reflect.*; import java.lang.ref.*; import java.lang.management.*; import java.security.*; import java.security.spec.*; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.awt.geom.*; import javax.imageio.*; import java.math.*; import static loadableUtils.utils.l; import static loadableUtils.utils.get; import static loadableUtils.utils.assertTrue; import loadableUtils.utils; import static loadableUtils.utils.DynamicObject_loading; import static loadableUtils.utils.rethrow; import loadableUtils.utils.F0; import loadableUtils.utils.IF0; import loadableUtils.utils.IF1; import loadableUtils.utils.IterableIterator; import static loadableUtils.utils.callF; import static loadableUtils.utils.set; import static loadableUtils.utils.fail; import static loadableUtils.utils.print; import loadableUtils.utils.RandomAccessAbstractList; import loadableUtils.utils.Average; import static loadableUtils.utils.negativeInfinity; import static loadableUtils.utils.b; import static loadableUtils.utils.n; import static loadableUtils.utils.abs; import static loadableUtils.utils.add; import static loadableUtils.utils.maximumSafeArraySize; import static loadableUtils.utils.last; import static loadableUtils.utils.toString; import static loadableUtils.utils.shortClassName_dropNumberPrefix; import static loadableUtils.utils.runnableToIF0; import static loadableUtils.utils.renderVars; import static loadableUtils.utils.sysNow; import static loadableUtils.utils._hashCode; import static loadableUtils.utils._onJavaXSet; import static loadableUtils.utils.a; import static loadableUtils.utils.arraycopy; import static loadableUtils.utils.boostHashCombine; import static loadableUtils.utils.close; import static loadableUtils.utils.concatLists; import static loadableUtils.utils.createOrAddToSyncLinkedHashSet; import static loadableUtils.utils.done; import static loadableUtils.utils.done2_always; import static loadableUtils.utils.eq; import static loadableUtils.utils.flattenCollectionsAndArrays; import static loadableUtils.utils.hashCode; import static loadableUtils.utils.iceil; import static loadableUtils.utils.joinNempties; import static loadableUtils.utils.length; import static loadableUtils.utils.ll; import static loadableUtils.utils.max; import static loadableUtils.utils.min; import static loadableUtils.utils.n2; import static loadableUtils.utils.remove; import static loadableUtils.utils.str; import static loadableUtils.utils.swing; import static loadableUtils.utils.value; import loadableUtils.utils.IFieldsToList; import static loadableUtils.utils.infinity; import static loadableUtils.utils.pcallF_typed; import static loadableUtils.utils.step; import static loadableUtils.utils.zip; import loadableUtils.utils.WeightlessShuffledIterator; import static loadableUtils.utils.sign; import loadableUtils.utils.DoubleBuffer; import loadableUtils.utils.TickerSequence; public class MPMInGazelle { static public String programID; static public void _onLoad_initUtils() { utils.__javax = javax(); } static public ThreadLocal dynamicObjectIsLoading_threadLocal() { return DynamicObject_loading; } static public Class javax() { return getJavaX(); } static public Class __javax; static public Class getJavaX() { try { return __javax; } catch (Exception __e) { throw rethrow(__e); } } static public void __setJavaX(Class j) { __javax = j; _onJavaXSet(); } static public class MPM { final public PositionSet getImaginaryPositions() { return imaginaryPositions(); } public PositionSet imaginaryPositions() { return imaginaryPositions; } public PositionSet imaginaryPositions = new PositionSet(); final public PositionSet getRealPositions() { return realPositions(); } public PositionSet realPositions() { return realPositions; } public PositionSet realPositions = new PositionSet(); final public MPM setCryptoPrice(double cryptoPrice) { return cryptoPrice(cryptoPrice); } public MPM cryptoPrice(double cryptoPrice) { this.cryptoPrice = cryptoPrice; return this; } final public double getCryptoPrice() { return cryptoPrice(); } public double cryptoPrice() { return cryptoPrice; } public double cryptoPrice; public DoubleBuffer cryptoPriceLog = new DoubleBuffer(); final public MPM setCryptoDirection(int cryptoDirection) { return cryptoDirection(cryptoDirection); } public MPM cryptoDirection(int cryptoDirection) { this.cryptoDirection = cryptoDirection; return this; } final public int getCryptoDirection() { return cryptoDirection(); } public int cryptoDirection() { return cryptoDirection; } public int cryptoDirection; transient public Set onCryptoDirectionChanged; public MPM onCryptoDirectionChanged(Runnable r) { onCryptoDirectionChanged = createOrAddToSyncLinkedHashSet(onCryptoDirectionChanged, r); return this; } public MPM removeCryptoDirectionChangedListener(Runnable r) { loadableUtils.utils.remove(onCryptoDirectionChanged, r); return this; } public void cryptoDirectionChanged() { if (onCryptoDirectionChanged != null) for (var listener : onCryptoDirectionChanged) pcallF_typed(listener); } final public MPM setCurrentTime(double currentTime) { return currentTime(currentTime); } public MPM currentTime(double currentTime) { this.currentTime = currentTime; return this; } final public double getCurrentTime() { return currentTime(); } public double currentTime() { return currentTime; } public double currentTime; final public MPM setMaxAllowedLoss(double maxAllowedLoss) { return maxAllowedLoss(maxAllowedLoss); } public MPM maxAllowedLoss(double maxAllowedLoss) { this.maxAllowedLoss = maxAllowedLoss; return this; } final public double getMaxAllowedLoss() { return maxAllowedLoss(); } public double maxAllowedLoss() { return maxAllowedLoss; } public double maxAllowedLoss = 1; public Position newShort() { return new Position().direction(-1); } public Position newLong() { return new Position().direction(1); } abstract public class CloseReason { } public class LossClose extends CloseReason { } public class HappyClose extends CloseReason { } public class RegularClose extends CloseReason { } public class KillClose extends CloseReason { } final public MPM setFees(double fees) { return fees(fees); } public MPM fees(double fees) { this.fees = fees; return this; } final public double getFees() { return fees(); } public double fees() { return fees; } public double fees = 0.12; final public MPM setExpectedSlippage(double expectedSlippage) { return expectedSlippage(expectedSlippage); } public MPM expectedSlippage(double expectedSlippage) { this.expectedSlippage = expectedSlippage; return this; } final public double getExpectedSlippage() { return expectedSlippage(); } public double expectedSlippage() { return expectedSlippage; } public double expectedSlippage = .1; public class Position { final public Position setReal(boolean real) { return real(real); } public Position real(boolean real) { this.real = real; return this; } final public boolean getReal() { return real(); } public boolean real() { return real; } public boolean real = false; final public Position setDirection(int direction) { return direction(direction); } public Position direction(int direction) { this.direction = direction; return this; } final public int getDirection() { return direction(); } public int direction() { return direction; } public int direction; final public Position setOpeningTime(double openingTime) { return openingTime(openingTime); } public Position openingTime(double openingTime) { this.openingTime = openingTime; return this; } final public double getOpeningTime() { return openingTime(); } public double openingTime() { return openingTime; } public double openingTime; final public Position setOpeningPrice(double openingPrice) { return openingPrice(openingPrice); } public Position openingPrice(double openingPrice) { this.openingPrice = openingPrice; return this; } final public double getOpeningPrice() { return openingPrice(); } public double openingPrice() { return openingPrice; } public double openingPrice; final public Position setLastPrice(double lastPrice) { return lastPrice(lastPrice); } public Position lastPrice(double lastPrice) { this.lastPrice = lastPrice; return this; } final public double getLastPrice() { return lastPrice(); } public double lastPrice() { return lastPrice; } public double lastPrice; final public Position setClosingTime(double closingTime) { return closingTime(closingTime); } public Position closingTime(double closingTime) { this.closingTime = closingTime; return this; } final public double getClosingTime() { return closingTime(); } public double closingTime() { return closingTime; } public double closingTime; final public Position setClosingPrice(double closingPrice) { return closingPrice(closingPrice); } public Position closingPrice(double closingPrice) { this.closingPrice = closingPrice; return this; } final public double getClosingPrice() { return closingPrice(); } public double closingPrice() { return closingPrice; } public double closingPrice; public Double openingPriceWithFeesAndSlippage_cache; public double openingPriceWithFeesAndSlippage() { if (openingPriceWithFeesAndSlippage_cache == null) openingPriceWithFeesAndSlippage_cache = openingPriceWithFeesAndSlippage_load(); return openingPriceWithFeesAndSlippage_cache; } public double openingPriceWithFeesAndSlippage_load() { return openingPrice * (1 + (fees + expectedSlippage) / 100 * direction); } final public Position setRelativeValue(double relativeValue) { return relativeValue(relativeValue); } public Position relativeValue(double relativeValue) { this.relativeValue = relativeValue; return this; } final public double getRelativeValue() { return relativeValue(); } public double relativeValue() { return relativeValue; } public double relativeValue; final public Position setMaxRelativeValue(double maxRelativeValue) { return maxRelativeValue(maxRelativeValue); } public Position maxRelativeValue(double maxRelativeValue) { this.maxRelativeValue = maxRelativeValue; return this; } final public double getMaxRelativeValue() { return maxRelativeValue(); } public double maxRelativeValue() { return maxRelativeValue; } public double maxRelativeValue = negativeInfinity(); final public Position setMinRelativeValue(double minRelativeValue) { return minRelativeValue(minRelativeValue); } public Position minRelativeValue(double minRelativeValue) { this.minRelativeValue = minRelativeValue; return this; } final public double getMinRelativeValue() { return minRelativeValue(); } public double minRelativeValue() { return minRelativeValue; } public double minRelativeValue = infinity(); final public Position setPullbackThreshold(double pullbackThreshold) { return pullbackThreshold(pullbackThreshold); } public Position pullbackThreshold(double pullbackThreshold) { this.pullbackThreshold = pullbackThreshold; return this; } final public double getPullbackThreshold() { return pullbackThreshold(); } public double pullbackThreshold() { return pullbackThreshold; } public double pullbackThreshold; final public Position setCloseReason(CloseReason closeReason) { return closeReason(closeReason); } public Position closeReason(CloseReason closeReason) { this.closeReason = closeReason; return this; } final public CloseReason getCloseReason() { return closeReason(); } public CloseReason closeReason() { return closeReason; } public CloseReason closeReason; public PositionSet motherList() { return real ? realPositions : imaginaryPositions; } public void close(CloseReason reason) { closingTime(time()); closingPrice(cryptoPrice); closeReason(reason); motherList().wasClosed(this); } public void imagine() { real(false); launch(); } public void launch() { update(cryptoPrice); motherList().add(this); } public void update(double cryptoPrice) { lastPrice(cryptoPrice); relativeValue((cryptoPrice / openingPriceWithFeesAndSlippage() - 1) * direction * 100); maxRelativeValue(max(maxRelativeValue, relativeValue)); minRelativeValue(min(minRelativeValue, relativeValue)); pullbackThreshold(maxRelativeValue - pullback(this)); } public String toString() { return renderVars("direction", direction, "openingPrice", openingPrice, "openingPriceWithFeesAndSlippage", openingPriceWithFeesAndSlippage(), "lastPrice", lastPrice, "relativeValue", relativeValue, "maxRelativeValue", maxRelativeValue); } public void autoClose() { if (relativeValue < -maxAllowedLoss) { close(new LossClose()); return; } if (relativeValue >= 0 && relativeValue < pullbackThreshold) { close(new RegularClose()); return; } } public boolean isOpen() { return closeReason == null; } } public class PositionSet { public LinkedHashSet positionsByDate = new LinkedHashSet(); public LinkedHashSet openPositions = new LinkedHashSet(); public LinkedHashSet closedPositions = new LinkedHashSet(); public void add(Position p) { positionsByDate.add(p); (p.isOpen() ? openPositions : closedPositions).add(p); } public void remove(Position p) { positionsByDate.remove(p); openPositions.remove(p); closedPositions.remove(p); } public void wasClosed(Position p) { openPositions.remove(p); closedPositions.add(p); } } transient public IF1 pullback; public double pullback(Position p) { return pullback != null ? pullback.get(p) : pullback_base(p); } final public double pullback_fallback(IF1 _f, Position p) { return _f != null ? _f.get(p) : pullback_base(p); } public double pullback_base(Position p) { return 0.5; } public double time() { return currentTime; } public void addTickerSequence(TickerSequence ts) { int n = l(ts); for (int i = 0; i < n; i++) newCryptoPrice(ts.prices.get(i), ts.timestamps.get(i)); } public void newCryptoPrice(double price) { newCryptoPrice(price, currentTime + 1); } public void newCryptoPrice(double price, double time) { currentTime(time); if (price == cryptoPrice) return; int direction = sign(cryptoPrice - price); cryptoPriceLog.add(price); cryptoPrice(price); for (var p : allOpenPositions()) { p.update(cryptoPrice); p.autoClose(); } if (direction != cryptoDirection) { cryptoDirection(direction); cryptoDirectionChanged(); } } public List allPositions() { return concatLists(realPositions().positionsByDate, imaginaryPositions().positionsByDate); } public List allOpenPositions() { return concatLists(realPositions().openPositions, imaginaryPositions().openPositions); } { init(); } public void init() { onCryptoDirectionChanged(() -> { for (var p : ll(newLong(), newShort())) p.openingTime(time()).openingPrice(cryptoPrice).imagine(); }); } public void printStatus() { print(toString()); } public String toString() { return combineWithSeparator(" + ", n2(realPositions.positionsByDate, "real position"), n2(imaginaryPositions.positionsByDate, "imaginary position")); } } static public class MPM2 implements IFieldsToList { public float[] ticker; public long[] timestamps; public MPM2() { } public MPM2(float[] ticker, long[] timestamps) { this.timestamps = timestamps; this.ticker = ticker; } public Object[] _fieldsToList() { return new Object[] { ticker, timestamps }; } static public class Market implements IFieldsToList { public double adversity; public Market() { } public Market(double adversity) { this.adversity = adversity; } public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + adversity + ")"; } public boolean equals(Object o) { if (!(o instanceof Market)) return false; Market __2 = (Market) o; return adversity == __2.adversity; } public int hashCode() { int h = -1997438884; h = boostHashCombine(h, _hashCode(adversity)); return h; } public Object[] _fieldsToList() { return new Object[] { adversity }; } } static public class Position implements IFieldsToList { static final public String _fieldOrder = "openingTime direction"; public double openingTime; public double direction; public Position() { } public Position(double openingTime, double direction) { this.direction = direction; this.openingTime = openingTime; } public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + openingTime + ", " + direction + ")"; } public boolean equals(Object o) { if (!(o instanceof Position)) return false; Position __3 = (Position) o; return openingTime == __3.openingTime && direction == __3.direction; } public int hashCode() { int h = 812449097; h = boostHashCombine(h, _hashCode(openingTime)); h = boostHashCombine(h, _hashCode(direction)); return h; } public Object[] _fieldsToList() { return new Object[] { openingTime, direction }; } } public class ClosedPosition implements IFieldsToList { public Position p; public double closingTime; public ClosedPosition() { } public ClosedPosition(Position p, double closingTime) { this.closingTime = closingTime; this.p = p; } public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + p + ", " + closingTime + ")"; } public boolean equals(Object o) { if (!(o instanceof ClosedPosition)) return false; ClosedPosition __0 = (ClosedPosition) o; return eq(p, __0.p) && eq(closingTime, __0.closingTime); } public int hashCode() { int h = 1899462357; h = boostHashCombine(h, _hashCode(p)); h = boostHashCombine(h, _hashCode(closingTime)); return h; } public Object[] _fieldsToList() { return new Object[] { p, closingTime }; } final public ClosedPosition setTicker(Ticker ticker) { return ticker(ticker); } public ClosedPosition ticker(Ticker ticker) { this.ticker = ticker; return this; } final public Ticker getTicker() { return ticker(); } public Ticker ticker() { return ticker; } public Ticker ticker; final public ClosedPosition setProfit(double profit) { return profit(profit); } public ClosedPosition profit(double profit) { this.profit = profit; return this; } final public double getProfit() { return profit(); } public double profit() { return profit; } public double profit; public MPM2 mpm() { return MPM2.this; } public double openingPrice() { return MPM2.this.ticker(p.openingTime); } public double closingPrice() { return MPM2.this.ticker(closingTime); } } static public class Juicer implements IFieldsToList { public double lossTolerance; public double pullback; public Juicer() { } public Juicer(double lossTolerance, double pullback) { this.pullback = pullback; this.lossTolerance = lossTolerance; } public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + lossTolerance + ", " + pullback + ")"; } public boolean equals(Object o) { if (!(o instanceof Juicer)) return false; Juicer __4 = (Juicer) o; return lossTolerance == __4.lossTolerance && pullback == __4.pullback; } public int hashCode() { int h = -2065131726; h = boostHashCombine(h, _hashCode(lossTolerance)); h = boostHashCombine(h, _hashCode(pullback)); return h; } public Object[] _fieldsToList() { return new Object[] { lossTolerance, pullback }; } } abstract static public class Eye { abstract public double adviseDirection(Ticker ticker); } static public class Ticker implements IFieldsToList { public float[] data; public long[] timestamps; public int length; public long currentTime; public Ticker() { } public Ticker(float[] data, long[] timestamps, int length, long currentTime) { this.currentTime = currentTime; this.length = length; this.timestamps = timestamps; this.data = data; } public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + data + ", " + timestamps + ", " + length + ", " + currentTime + ")"; } public Object[] _fieldsToList() { return new Object[] { data, timestamps, length, currentTime }; } public double currentPrice() { return data[length - 1]; } public Double lookback(double time) { int index = binarySearch_insertionPoint(wrapLongArrayAsList(timestamps), lceil(currentTime - time)); return index >= 0 ? (double) data[index] : null; } } static public class LiveTicker extends Ticker { public void add(float value, long timestamp) { data = addToArrayWithDoublingStrategy(data, length, value); timestamps = addToArrayWithDoublingStrategy(timestamps, length, timestamp); length++; } } public Ticker ticker() { return new Ticker(ticker, timestamps, l(timestamps), last(timestamps)); } static public class SimpleEye extends Eye implements IFieldsToList { static final public String _fieldOrder = "time minMove"; public double time; public double minMove; public SimpleEye() { } public SimpleEye(double time, double minMove) { this.minMove = minMove; this.time = time; } public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + time + ", " + minMove + ")"; } public boolean equals(Object o) { if (!(o instanceof SimpleEye)) return false; SimpleEye __5 = (SimpleEye) o; return time == __5.time && minMove == __5.minMove; } public int hashCode() { int h = -120435969; h = boostHashCombine(h, _hashCode(time)); h = boostHashCombine(h, _hashCode(minMove)); return h; } public Object[] _fieldsToList() { return new Object[] { time, minMove }; } public double adviseDirection(Ticker ticker) { Double currentPrice = ticker.currentPrice(); if (currentPrice == null) return 0; Double before = ticker.lookback(time); if (before == null) return 0; double move = currentPrice / before * 100 - 100; if (abs(move) >= minMove) return (double) sign(move); return 0; } } public double ticker(double time) { return ticker[iceil(time)]; } public double openingPrice(Position p) { return ticker(p.openingTime); } public double profit(Position p, double time, Market m) { return (ticker(time) / openingPrice(p) * 100 - 100) * p.direction - m.adversity; } public Double closingTime(Position p, Juicer j, Market m) { int time = iceil(p.openingTime); double openingPrice = ticker(time); double crest = -infinity(); while (time < ticker.length - 1) { double profit = profit(p, time, m); crest = max(crest, profit); if (profit < (profit < 0 ? -j.lossTolerance : crest - j.pullback)) return (double) time; ++time; } return null; } public Double profit(Position p, Juicer j, Market m) { Double closingTime = closingTime(p, j, m); return closingTime == null ? null : profit(p, closingTime, m); } public Double profitability(double time, Juicer j, Market m) { return maxAllowingNull(profit(new Position(time, -1), j, m), profit(new Position(time, 1), j, m)); } public class Backtest extends Convergent implements IFieldsToList { public Eye eye; public Juicer j; public Market m; public Backtest() { } public Backtest(Eye eye, Juicer j, Market m) { this.m = m; this.j = j; this.eye = eye; } public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + eye + ", " + j + ", " + m + ")"; } public Object[] _fieldsToList() { return new Object[] { eye, j, m }; } public Iterator openingTimes = new WeightlessShuffledIterator(intRangeList(ticker.length)); public Average profitPerPosition = new Average(); public Average positionsOpenedPercentage = new Average(); public Average positionsClosedPercentage = new Average(); public List closedPositions = new ArrayList(); public Backtest(Juicer j, Market m) { this.m = m; this.j = j; } public void step() { if (!openingTimes.hasNext()) { done = true; return; } int openingTime = openingTimes.next(); Double profit = null; ClosedPosition closedPosition = null; if (eye == null) profit = profitability(openingTime, j, m); else { double direction = eye.adviseDirection(new Ticker(ticker, timestamps, openingTime, timestamps[openingTime])); positionsOpenedPercentage.addSample(direction != 0 ? 100 : 0); if (direction != 0) { Position position = new Position(openingTime, direction); Double closingTime = closingTime(position, j, m); if (closingTime != null) { profit = profit(position, closingTime, m); closedPosition = new ClosedPosition(position, closingTime).ticker(ticker()).profit(profit); closedPositions.add(closedPosition); } } } positionsClosedPercentage.addSample(profit != null ? 100 : 0); if (profit != null) { profitPerPosition.addSample(profit); } else profitPerPosition.addSample(0); value = profitPerPosition.get(); } } } abstract static public class Convergent extends IterableIterator { public A value; public boolean stepped, done; public boolean hasNext() { if (done) return false; if (!stepped) { stepped = true; step(); } return !done; } public A next() { assertTrue(hasNext()); stepped = false; return value; } abstract public void step(); } static public Object time(Object f) { long time = sysNow(); Object o = callF(f); done2_always(str(f), time); return o; } static public A time(F0 f) { return (A) time((Object) f); } static public A time(IF0 f) { return (A) time((Object) f); } static public A time(String msg, IF0 f) { long time = sysNow(); A o = f.get(); done2_always(msg, time); return o; } static public void time(Runnable f) { time(str(f), f); } static public void time(String msg, Runnable f) { time(msg, runnableToIF0(f)); } static public String combineWithSeparator(String separator, Object... l) { return customSeparatorCombine(separator, l); } static public > int binarySearch_insertionPoint(List l, A a) { int i = Collections.binarySearch(l, a); return i < 0 ? -i - 1 : i; } static public List wrapLongArrayAsList(long[] l) { return new RandomAccessAbstractList() { public int size() { return l.length; } public Long get(int i) { return l[i]; } public Long set(int i, Long val) { Long a = l[i]; l[i] = val; return a; } }; } static public long lceil(double d) { return (long) Math.ceil(d); } static public double[] addToArrayWithDoublingStrategy(double[] array, int currentSize, double element) { if (currentSize >= l(array)) { array = resizeArray(array, max(1, limitToSafeArraySize(l(array) * 2L))); if (currentSize >= l(array)) throw fail("Can't handle more than 2 billion elements at once"); } array[currentSize++] = element; return array; } static public float[] addToArrayWithDoublingStrategy(float[] array, int currentSize, float element) { if (currentSize >= l(array)) { array = resizeArray(array, max(1, limitToSafeArraySize(l(array) * 2L))); if (currentSize >= l(array)) throw fail("Can't handle more than 2 billion elements at once"); } array[currentSize++] = element; return array; } static public long[] addToArrayWithDoublingStrategy(long[] array, int currentSize, long element) { if (currentSize >= l(array)) { array = resizeArray(array, max(1, limitToSafeArraySize(l(array) * 2L))); if (currentSize >= l(array)) throw fail("Can't handle more than 2 billion elements at once"); } array[currentSize++] = element; return array; } static public Double maxAllowingNull(Double a, Double b) { if (a == null) return b; if (b == null) return a; return Math.max(a, b); } static public List intRangeList(int b) { return intRangeList(0, b); } static public List intRangeList(final int a, final int b) { final int l = max(0, b - a); return new RandomAccessAbstractList() { public int size() { return l; } public Integer get(int i) { return i >= 0 && i < l ? a + i : null; } }; } static public String customSeparatorCombine(String separator, Object... l) { return joinNempties(separator, flattenCollectionsAndArrays(l)); } static public Object[] resizeArray(Object[] a, int n) { if (n == l(a)) return a; Object[] b = new Object[n]; arraycopy(a, 0, b, 0, min(l(a), n)); return b; } static public double[] resizeArray(double[] a, int n) { if (n == l(a)) return a; double[] b = new double[n]; arraycopy(a, 0, b, 0, min(l(a), n)); return b; } static public float[] resizeArray(float[] a, int n) { if (n == l(a)) return a; float[] b = new float[n]; arraycopy(a, 0, b, 0, min(l(a), n)); return b; } static public long[] resizeArray(long[] a, int n) { if (n == l(a)) return a; long[] b = new long[n]; arraycopy(a, 0, b, 0, min(l(a), n)); return b; } static public int limitToSafeArraySize(long n) { return (int) Math.min(maximumSafeArraySize(), n); } }