diff --git a/README_alarm.md b/README_alarm.md index ae53628..2ab1dfe 100644 --- a/README_alarm.md +++ b/README_alarm.md @@ -140,7 +140,7 @@ Supported column values: - `ID`: integer alarm identifier. IDs should be unique. Action files use this ID to select alarms. - `ASSET`: `SOL`, `ETH`, or `BTC`. - `CONDITION`: a comparison or range expression. It must be one token with no whitespace. -- `TRIGGER`: `ONETIME`, `PERSISTENT`, or `PERSISTENT:`. +- `TRIGGER`: `ONETIME`, `CROSSING`, `PERSISTENT`, or `PERSISTENT:`. A trailing comment is allowed after a variable value or trigger. @@ -210,7 +210,7 @@ At exactly `LIQ+1%`, only the first condition matches. The corresponding short-p ## Trigger behavior -On the first accepted oracle price after startup or reconnect, an already-satisfied alarm can trigger immediately. +On the first accepted oracle price after startup, an already-satisfied `ONETIME` or `PERSISTENT` alarm can trigger immediately. A `CROSSING` alarm only establishes its initial state. ### `ONETIME` @@ -219,6 +219,23 @@ On the first accepted oracle price after startup or reconnect, an already-satisf - Leaving and re-entering the condition does not re-arm it. - Restarting the application re-arms it because alarm state is held only in memory. +### `CROSSING` + +- Triggers when the condition changes from not satisfied to satisfied. +- The first accepted price establishes the initial state and never triggers the alarm. +- Remaining inside the condition does not trigger again. +- Leaving the condition re-arms the alarm, so the next entry triggers again. +- A grace period is not supported; `CROSSING:` is rejected. + +The condition determines the crossing direction. For example: + +```text +19 SOL >={{SOL_LONG_ENTRY_PRICE}} CROSSING +20 SOL <{{SOL_LONG_ENTRY_PRICE}} CROSSING +``` + +Alarm 19 triggers when the price enters the profitable side from below. Alarm 20 triggers when it enters the losing side from above. With a range condition, `CROSSING` triggers whenever the price enters the range from either side. + ### `PERSISTENT` - Triggers immediately on the first accepted matching price. diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmConfigurationParser.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmConfigurationParser.tjava index 64908ce..c540faa 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmConfigurationParser.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmConfigurationParser.tjava @@ -94,6 +94,10 @@ public final class AlarmConfigurationParser { return new TriggerConfiguration(AlarmTrigger.ONETIME, 0); } + if (normalized.equals("CROSSING")) { + return new TriggerConfiguration(AlarmTrigger.CROSSING, 0); + } + if (normalized.equals("PERSISTENT")) { return new TriggerConfiguration(AlarmTrigger.PERSISTENT, 0); } @@ -128,6 +132,10 @@ public final class AlarmConfigurationParser { throw new IllegalArgumentException("ONETIME cannot have a grace period: " + triggerText); } + if (normalized.startsWith("CROSSING:")) { + throw new IllegalArgumentException("CROSSING cannot have a grace period: " + triggerText); + } + throw new IllegalArgumentException("Unknown trigger: " + triggerText); } diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmTrigger.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmTrigger.tjava index f1e2ec2..b386714 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmTrigger.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmTrigger.tjava @@ -2,5 +2,6 @@ package com.r35157.jupiterperpsalarm.impl.ref; public enum AlarmTrigger { ONETIME, + CROSSING, PERSISTENT } diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/PriceAlarm.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/PriceAlarm.tjava index 62a9ab8..591eff7 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/PriceAlarm.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/PriceAlarm.tjava @@ -41,6 +41,10 @@ public final class PriceAlarm { ? reached : reached && !previousReached; + boolean crossedIntoTriggeredSide = previousReached != null + && reached + && !previousReached; + previousReached = reached; if (!reached) { @@ -57,6 +61,13 @@ public final class PriceAlarm { return; } + if (definition.trigger() == AlarmTrigger.CROSSING) { + if (crossedIntoTriggeredSide) { + trigger(price, condition); + } + return; + } + if (definition.trigger() == AlarmTrigger.PERSISTENT) { if (lastTriggeredAt == null || persistentGracePeriodHasPassed()) { trigger(price, condition);