Skip to content

Commit

Permalink
feat: Automatically rollover if user opens app during rollover weekend.
Browse files Browse the repository at this point in the history
  • Loading branch information
holzeis committed Sep 19, 2023
1 parent cdd708e commit 48e857a
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 17 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix: Prevent crashing the app when there's no Internet connection
- feat: Allow exporting the seed phrase even if the Node is offline
- Changed expiry to next Sunday 3 pm UTC
- Automatically rollover if user opens app during rollover weekend

## [1.2.6] - 2023-09-06

Expand Down
30 changes: 27 additions & 3 deletions coordinator/src/orderbook/trading.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::db::positions;
use crate::orderbook::db::matches;
use crate::orderbook::db::orders;
use anyhow::anyhow;
Expand Down Expand Up @@ -129,7 +130,7 @@ pub fn start(
let authenticated_users = authenticated_users.clone();
async move {
tracing::debug!(trader_id=%new_user_msg.new_user, "Checking if the user needs to be notified about pending matches");
if let Err(e) = process_pending_match(&mut conn, &authenticated_users, new_user_msg.new_user).await {
if let Err(e) = process_pending_actions(&mut conn, &authenticated_users, new_user_msg.new_user).await {
tracing::error!("Failed to process pending match. Error: {e:#}");
}
}
Expand Down Expand Up @@ -273,8 +274,10 @@ async fn process_new_order(
Ok(order)
}

/// Notifies the trader if a pending match is waiting for them.
async fn process_pending_match(
/// Checks if there are any immediate actions to be processed by the app
/// - Pending Match
/// - Rollover
async fn process_pending_actions(
conn: &mut PgConnection,
authenticated_users: &HashMap<PublicKey, mpsc::Sender<OrderbookMsg>>,
trader_id: PublicKey,
Expand All @@ -293,6 +296,27 @@ async fn process_pending_match(
if let Err(e) = notify_trader(trader_id, message, authenticated_users).await {
tracing::warn!("Failed to notify trader. Error: {e:#}");
}
} else if let Some(position) =
positions::Position::get_open_position_by_trader(conn, trader_id.to_string())?
{
tracing::debug!(%trader_id, position_id=position.id, "Checking if the users positions is eligible for rollover");

if orderbook_commons::is_in_rollover_weekend(position.expiry_timestamp) {
let next_expiry = orderbook_commons::get_expiry_timestamp(OffsetDateTime::now_utc());
if position.expiry_timestamp == next_expiry {
tracing::trace!(%trader_id, position_id=position.id, "Position has already been rolled over");
return Ok(());
}

tracing::debug!(%trader_id, position_id=position.id, "Proposing to rollover users position");

let message = OrderbookMsg::Rollover;
if let Err(e) = notify_trader(trader_id, message, authenticated_users).await {
// if that happens, it's most likely that the user already closed its app again
// and we can simply wait for the user to come online again to retry.
tracing::debug!("Failed to notify trader. Error: {e:#}");
}
}
}

Ok(())
Expand Down
1 change: 1 addition & 0 deletions crates/orderbook-commons/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ pub enum OrderbookMsg {
order: Order,
filled_with: FilledWith,
},
Rollover,
}

/// A match for an order
Expand Down
2 changes: 1 addition & 1 deletion crates/tests-e2e/src/test_subscriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ impl Senders {
native::event::EventInternal::PaymentClaimed(_amount_msats) => {
unreachable!("PaymentClaimed event should not be sent to the subscriber");
}
native::event::EventInternal::AsyncTrade(_order_id) => {
native::event::EventInternal::BackgroundNotification(_task) => {
// ignored
}
}
Expand Down
51 changes: 51 additions & 0 deletions mobile/lib/common/domain/background_task.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'package:get_10101/bridge_generated/bridge_definitions.dart' as bridge;
import 'package:get_10101/features/trade/domain/order.dart';

class AsyncTrade {
final OrderReason orderReason;

AsyncTrade({required this.orderReason});

static AsyncTrade fromApi(bridge.BackgroundTask_AsyncTrade asyncTrade) {
return AsyncTrade(orderReason: OrderReason.fromApi(asyncTrade.field0));
}

static bridge.BackgroundTask apiDummy() {
return bridge.BackgroundTask_AsyncTrade(OrderReason.apiDummy());
}
}

enum TaskStatus {
pending,
failed,
success;

static TaskStatus fromApi(bridge.TaskStatus taskStatus) {
switch (taskStatus) {
case bridge.TaskStatus.Pending:
return TaskStatus.pending;
case bridge.TaskStatus.Failed:
return TaskStatus.failed;
case bridge.TaskStatus.Success:
return TaskStatus.success;
}
}

static bridge.TaskStatus apiDummy() {
return bridge.TaskStatus.Pending;
}
}

class Rollover {
final TaskStatus taskStatus;

Rollover({required this.taskStatus});

static Rollover fromApi(bridge.BackgroundTask_Rollover rollover) {
return Rollover(taskStatus: TaskStatus.fromApi(rollover.field0));
}

static bridge.BackgroundTask apiDummy() {
return bridge.BackgroundTask_Rollover(TaskStatus.apiDummy());
}
}
10 changes: 6 additions & 4 deletions mobile/lib/features/trade/async_order_change_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:f_logs/model/flog/flog.dart';
import 'package:flutter/material.dart';
import 'package:get_10101/bridge_generated/bridge_definitions.dart' as bridge;
import 'package:get_10101/common/application/event_service.dart';
import 'package:get_10101/common/domain/background_task.dart';
import 'package:get_10101/common/global_keys.dart';
import 'package:get_10101/features/trade/application/order_service.dart';
import 'package:get_10101/features/trade/domain/order.dart';
Expand All @@ -26,9 +27,10 @@ class AsyncOrderChangeNotifier extends ChangeNotifier implements Subscriber {

@override
void notify(bridge.Event event) {
if (event is bridge.Event_AsyncTrade) {
OrderReason reason = OrderReason.fromApi(event.field0);
FLog.debug(text: "Received a async trade event. Reason: $reason");
if (event is bridge.Event_BackgroundNotification &&
event.field0 is bridge.BackgroundTask_AsyncTrade) {
AsyncTrade asyncTrade = AsyncTrade.fromApi(event.field0 as bridge.BackgroundTask_AsyncTrade);
FLog.debug(text: "Received a async trade event. Reason: ${asyncTrade.orderReason}");
showDialog(
context: shellNavigatorKey.currentContext!,
builder: (context) {
Expand All @@ -47,7 +49,7 @@ class AsyncOrderChangeNotifier extends ChangeNotifier implements Subscriber {
}

late Widget content;
switch (reason) {
switch (asyncTrade.orderReason) {
case OrderReason.expired:
content = const Text("Your position has been closed due to expiry.");
case OrderReason.manual:
Expand Down
54 changes: 54 additions & 0 deletions mobile/lib/features/trade/rollover_change_notifier.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'package:f_logs/model/flog/flog.dart';
import 'package:flutter/material.dart';
import 'package:get_10101/bridge_generated/bridge_definitions.dart' as bridge;
import 'package:get_10101/common/application/event_service.dart';
import 'package:get_10101/common/domain/background_task.dart';
import 'package:get_10101/common/global_keys.dart';
import 'package:get_10101/features/trade/order_submission_status_dialog.dart';
import 'package:provider/provider.dart';

class RolloverChangeNotifier extends ChangeNotifier implements Subscriber {
late TaskStatus taskStatus;

@override
void notify(bridge.Event event) {
if (event is bridge.Event_BackgroundNotification &&
event.field0 is bridge.BackgroundTask_Rollover) {
Rollover rollover = Rollover.fromApi(event.field0 as bridge.BackgroundTask_Rollover);
FLog.debug(text: "Received a rollover event. Status: ${rollover.taskStatus}");

taskStatus = rollover.taskStatus;

if (taskStatus == TaskStatus.pending) {
// initialize dialog for the pending task
showDialog(
context: shellNavigatorKey.currentContext!,
builder: (context) {
TaskStatus status = context.watch<RolloverChangeNotifier>().taskStatus;

// todo(holzeis): Reusing the order submission status dialog is not nice, but it's actually suitable for any task execution that has pending,
// failed and success states. We may should consider renaming this dialog for its more generic purpose.
OrderSubmissionStatusDialogType type = OrderSubmissionStatusDialogType.pendingSubmit;
switch (status) {
case TaskStatus.pending:
type = OrderSubmissionStatusDialogType.successfulSubmit;
case TaskStatus.failed:
type = OrderSubmissionStatusDialogType.failedFill;
case TaskStatus.success:
type = OrderSubmissionStatusDialogType.filled;
}

late Widget content = const Text("Rolling over your position");

return OrderSubmissionStatusDialog(title: "Catching up!", type: type, content: content);
},
);
} else {
// notify dialog about changed task status
notifyListeners();
}
} else {
FLog.warning(text: "Received unexpected event: ${event.toString()}");
}
}
}
10 changes: 9 additions & 1 deletion mobile/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:get_10101/common/application/channel_info_service.dart';
import 'package:get_10101/common/application/event_service.dart';
import 'package:get_10101/common/channel_status_notifier.dart';
import 'package:get_10101/common/color.dart';
import 'package:get_10101/common/domain/background_task.dart';
import 'package:get_10101/common/domain/service_status.dart';
import 'package:get_10101/common/global_keys.dart';
import 'package:get_10101/common/service_status_notifier.dart';
Expand All @@ -27,6 +28,7 @@ import 'package:get_10101/features/trade/domain/position.dart';
import 'package:get_10101/features/trade/domain/price.dart';
import 'package:get_10101/features/trade/order_change_notifier.dart';
import 'package:get_10101/features/trade/position_change_notifier.dart';
import 'package:get_10101/features/trade/rollover_change_notifier.dart';
import 'package:get_10101/features/trade/submit_order_change_notifier.dart';
import 'package:get_10101/features/trade/trade_screen.dart';
import 'package:get_10101/features/trade/trade_theme.dart';
Expand Down Expand Up @@ -87,6 +89,7 @@ void main() async {
ChangeNotifierProvider(create: (context) => ServiceStatusNotifier()),
ChangeNotifierProvider(create: (context) => ChannelStatusNotifier()),
ChangeNotifierProvider(create: (context) => AsyncOrderChangeNotifier(OrderService())),
ChangeNotifierProvider(create: (context) => RolloverChangeNotifier()),
Provider(create: (context) => Environment.parse()),
Provider(create: (context) => channelInfoService)
], child: const TenTenOneApp()));
Expand Down Expand Up @@ -477,6 +480,7 @@ void subscribeToNotifiers(BuildContext context) {
final channelStatusNotifier = context.read<ChannelStatusNotifier>();
final stableValuesChangeNotifier = context.read<StableValuesChangeNotifier>();
final asyncOrderChangeNotifier = context.read<AsyncOrderChangeNotifier>();
final rolloverChangeNotifier = context.read<RolloverChangeNotifier>();

eventService.subscribe(
orderChangeNotifier, bridge.Event.orderUpdateNotification(Order.apiDummy()));
Expand Down Expand Up @@ -509,7 +513,11 @@ void subscribeToNotifiers(BuildContext context) {

eventService.subscribe(
asyncOrderChangeNotifier, bridge.Event.orderUpdateNotification(Order.apiDummy()));
eventService.subscribe(asyncOrderChangeNotifier, bridge.Event.asyncTrade(OrderReason.apiDummy()));
eventService.subscribe(
asyncOrderChangeNotifier, bridge.Event.backgroundNotification(AsyncTrade.apiDummy()));

eventService.subscribe(
rolloverChangeNotifier, bridge.Event.backgroundNotification(Rollover.apiDummy()));

channelStatusNotifier.subscribe(eventService);

Expand Down
43 changes: 41 additions & 2 deletions mobile/native/src/event/api.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::api::WalletInfo;
use crate::event;
use crate::event::subscriber::Subscriber;
use crate::event::EventInternal;
use crate::event::EventType;
Expand All @@ -25,7 +26,14 @@ pub enum Event {
PriceUpdateNotification(BestPrice),
ServiceHealthUpdate(ServiceUpdate),
ChannelStatusUpdate(ChannelStatus),
BackgroundNotification(BackgroundTask),
}

#[frb]
#[derive(Clone)]
pub enum BackgroundTask {
AsyncTrade(OrderReason),
Rollover(TaskStatus),
}

impl From<EventInternal> for Event {
Expand Down Expand Up @@ -64,7 +72,9 @@ impl From<EventInternal> for Event {
EventInternal::PaymentClaimed(_) => {
unreachable!("This internal event is not exposed to the UI")
}
EventInternal::AsyncTrade(reason) => Event::AsyncTrade(reason.into()),
EventInternal::BackgroundNotification(task) => {
Event::BackgroundNotification(task.into())
}
}
}
}
Expand Down Expand Up @@ -100,7 +110,7 @@ impl Subscriber for FlutterSubscriber {
EventType::PriceUpdateNotification,
EventType::ServiceHealthUpdate,
EventType::ChannelStatusUpdate,
EventType::AsyncTrade,
EventType::BackgroundNotification,
]
}
}
Expand All @@ -111,6 +121,35 @@ impl FlutterSubscriber {
}
}

impl From<event::BackgroundTask> for BackgroundTask {
fn from(value: event::BackgroundTask) -> Self {
match value {
event::BackgroundTask::AsyncTrade(order_reason) => {
BackgroundTask::AsyncTrade(order_reason.into())
}
event::BackgroundTask::Rollover(status) => BackgroundTask::Rollover(status.into()),
}
}
}

#[frb]
#[derive(Clone)]
pub enum TaskStatus {
Pending,
Failed,
Success,
}

impl From<event::TaskStatus> for TaskStatus {
fn from(value: event::TaskStatus) -> Self {
match value {
event::TaskStatus::Pending => TaskStatus::Pending,
event::TaskStatus::Failed => TaskStatus::Failed,
event::TaskStatus::Success => TaskStatus::Success,
}
}
}

/// The best bid and ask price for a contract.
///
/// Best prices come from an orderbook. Contrary to the `Price` struct, we can have no price
Expand Down
21 changes: 17 additions & 4 deletions mobile/native/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ pub fn publish(event: &EventInternal) {
pub enum EventInternal {
Init(String),
Log(String),
AsyncTrade(OrderReason),
OrderUpdateNotification(Order),
WalletInfoUpdateNotification(WalletInfo),
OrderFilledWith(Box<TradeParams>),
Expand All @@ -41,6 +40,20 @@ pub enum EventInternal {
PaymentClaimed(u64),
ServiceHealthUpdate(ServiceUpdate),
ChannelStatusUpdate(ChannelStatus),
BackgroundNotification(BackgroundTask),
}

#[derive(Clone, Debug)]
pub enum BackgroundTask {
AsyncTrade(OrderReason),
Rollover(TaskStatus),
}

#[derive(Clone, Debug)]
pub enum TaskStatus {
Pending,
Failed,
Success,
}

impl fmt::Display for EventInternal {
Expand All @@ -58,7 +71,7 @@ impl fmt::Display for EventInternal {
EventInternal::PaymentClaimed(_) => "PaymentClaimed",
EventInternal::ServiceHealthUpdate(_) => "ServiceHealthUpdate",
EventInternal::ChannelStatusUpdate(_) => "ChannelStatusUpdate",
EventInternal::AsyncTrade(_) => "AsyncTrade",
EventInternal::BackgroundNotification(_) => "BackgroundNotification",
}
.fmt(f)
}
Expand All @@ -81,7 +94,7 @@ impl From<EventInternal> for EventType {
EventInternal::PaymentClaimed(_) => EventType::PaymentClaimed,
EventInternal::ServiceHealthUpdate(_) => EventType::ServiceHealthUpdate,
EventInternal::ChannelStatusUpdate(_) => EventType::ChannelStatusUpdate,
EventInternal::AsyncTrade(_) => EventType::AsyncTrade,
EventInternal::BackgroundNotification(_) => EventType::BackgroundNotification,
}
}
}
Expand All @@ -100,5 +113,5 @@ pub enum EventType {
PaymentClaimed,
ServiceHealthUpdate,
ChannelStatusUpdate,
AsyncTrade,
BackgroundNotification,
}
Loading

0 comments on commit 48e857a

Please sign in to comment.