37: Add re-armable CROSSING alarm trigger
This commit is contained in:
+19
-2
@@ -140,7 +140,7 @@ Supported column values:
|
|||||||
- `ID`: integer alarm identifier. IDs should be unique. Action files use this ID to select alarms.
|
- `ID`: integer alarm identifier. IDs should be unique. Action files use this ID to select alarms.
|
||||||
- `ASSET`: `SOL`, `ETH`, or `BTC`.
|
- `ASSET`: `SOL`, `ETH`, or `BTC`.
|
||||||
- `CONDITION`: a comparison or range expression. It must be one token with no whitespace.
|
- `CONDITION`: a comparison or range expression. It must be one token with no whitespace.
|
||||||
- `TRIGGER`: `ONETIME`, `PERSISTENT`, or `PERSISTENT:<seconds>`.
|
- `TRIGGER`: `ONETIME`, `CROSSING`, `PERSISTENT`, or `PERSISTENT:<seconds>`.
|
||||||
|
|
||||||
A trailing comment is allowed after a variable value or trigger.
|
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
|
## 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`
|
### `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.
|
- 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.
|
- 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:<seconds>` 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`
|
### `PERSISTENT`
|
||||||
|
|
||||||
- Triggers immediately on the first accepted matching price.
|
- Triggers immediately on the first accepted matching price.
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ public final class AlarmConfigurationParser {
|
|||||||
return new TriggerConfiguration(AlarmTrigger.ONETIME, 0);
|
return new TriggerConfiguration(AlarmTrigger.ONETIME, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (normalized.equals("CROSSING")) {
|
||||||
|
return new TriggerConfiguration(AlarmTrigger.CROSSING, 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (normalized.equals("PERSISTENT")) {
|
if (normalized.equals("PERSISTENT")) {
|
||||||
return new TriggerConfiguration(AlarmTrigger.PERSISTENT, 0);
|
return new TriggerConfiguration(AlarmTrigger.PERSISTENT, 0);
|
||||||
}
|
}
|
||||||
@@ -128,6 +132,10 @@ public final class AlarmConfigurationParser {
|
|||||||
throw new IllegalArgumentException("ONETIME cannot have a grace period: " + triggerText);
|
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);
|
throw new IllegalArgumentException("Unknown trigger: " + triggerText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ package com.r35157.jupiterperpsalarm.impl.ref;
|
|||||||
|
|
||||||
public enum AlarmTrigger {
|
public enum AlarmTrigger {
|
||||||
ONETIME,
|
ONETIME,
|
||||||
|
CROSSING,
|
||||||
PERSISTENT
|
PERSISTENT
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ public final class PriceAlarm {
|
|||||||
? reached
|
? reached
|
||||||
: reached && !previousReached;
|
: reached && !previousReached;
|
||||||
|
|
||||||
|
boolean crossedIntoTriggeredSide = previousReached != null
|
||||||
|
&& reached
|
||||||
|
&& !previousReached;
|
||||||
|
|
||||||
previousReached = reached;
|
previousReached = reached;
|
||||||
|
|
||||||
if (!reached) {
|
if (!reached) {
|
||||||
@@ -57,6 +61,13 @@ public final class PriceAlarm {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (definition.trigger() == AlarmTrigger.CROSSING) {
|
||||||
|
if (crossedIntoTriggeredSide) {
|
||||||
|
trigger(price, condition);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (definition.trigger() == AlarmTrigger.PERSISTENT) {
|
if (definition.trigger() == AlarmTrigger.PERSISTENT) {
|
||||||
if (lastTriggeredAt == null || persistentGracePeriodHasPassed()) {
|
if (lastTriggeredAt == null || persistentGracePeriodHasPassed()) {
|
||||||
trigger(price, condition);
|
trigger(price, condition);
|
||||||
|
|||||||
Reference in New Issue
Block a user