Skip to content

Commit

Permalink
Merge pull request #2379 from get10101/chore/flickering-price
Browse files Browse the repository at this point in the history
fix: flickering prices
  • Loading branch information
bonomat authored Apr 9, 2024
2 parents 41afeb3 + b4ce185 commit 3b8d670
Show file tree
Hide file tree
Showing 21 changed files with 285 additions and 257 deletions.
2 changes: 2 additions & 0 deletions crates/commons/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub use message::*;
pub use order::*;
pub use order_matching_fee::order_matching_fee;
pub use polls::*;
pub use price::best_ask_price;
pub use price::best_bid_price;
pub use price::best_current_price;
pub use price::Price;
pub use price::Prices;
Expand Down
4 changes: 2 additions & 2 deletions crates/commons/src/price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub fn best_current_price(current_orders: &[Order]) -> Prices {
///
/// If you SELL, you ask and you get the best price someone is willing to buy at i.e. the highest
/// bid price.
fn best_bid_price(orders: &[Order], symbol: ContractSymbol) -> Option<Decimal> {
pub fn best_bid_price(orders: &[Order], symbol: ContractSymbol) -> Option<Decimal> {
orders
.iter()
.filter(|o| {
Expand All @@ -68,7 +68,7 @@ fn best_bid_price(orders: &[Order], symbol: ContractSymbol) -> Option<Decimal> {
///
/// If you BUY, you bid and you get the best price someone is willing to sell at i.e. the lowest ask
/// price.
fn best_ask_price(orders: &[Order], symbol: ContractSymbol) -> Option<Decimal> {
pub fn best_ask_price(orders: &[Order], symbol: ContractSymbol) -> Option<Decimal> {
orders
.iter()
.filter(|o| {
Expand Down
34 changes: 23 additions & 11 deletions crates/tests-e2e/src/test_subscriber.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use commons::Prices;
use commons::TradeParams;
use native::api::ContractSymbol;
use native::api::WalletInfo;
Expand All @@ -10,6 +9,7 @@ use native::health::ServiceUpdate;
use native::trade::order::Order;
use native::trade::position::Position;
use parking_lot::Mutex;
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::watch;
Expand All @@ -21,7 +21,8 @@ pub struct Senders {
position: watch::Sender<Option<Position>>,
/// Init messages are simple strings
init_msg: watch::Sender<Option<String>>,
prices: watch::Sender<Option<Prices>>,
ask_price: watch::Sender<Option<Decimal>>,
bid_price: watch::Sender<Option<Decimal>>,
position_close: watch::Sender<Option<ContractSymbol>>,
service: watch::Sender<Option<ServiceUpdate>>,
}
Expand All @@ -34,7 +35,8 @@ pub struct TestSubscriber {
order_filled: watch::Receiver<Option<Box<TradeParams>>>,
position: watch::Receiver<Option<Position>>,
init_msg: watch::Receiver<Option<String>>,
prices: watch::Receiver<Option<Prices>>,
ask_price: watch::Receiver<Option<Decimal>>,
bid_price: watch::Receiver<Option<Decimal>>,
position_close: watch::Receiver<Option<ContractSymbol>>,
services: Arc<Mutex<HashMap<Service, ServiceStatus>>>,
_service_map_updater: tokio::task::JoinHandle<()>,
Expand All @@ -47,7 +49,8 @@ impl TestSubscriber {
let (order_filled_tx, order_filled_rx) = watch::channel(None);
let (position_tx, position_rx) = watch::channel(None);
let (init_msg_tx, init_msg_rx) = watch::channel(None);
let (prices_tx, prices_rx) = watch::channel(None);
let (ask_prices_tx, ask_prices_rx) = watch::channel(None);
let (bid_prices_tx, bid_prices_rx) = watch::channel(None);
let (position_close_tx, position_close_rx) = watch::channel(None);
let (service_tx, mut service_rx) = watch::channel(None);

Expand All @@ -57,7 +60,8 @@ impl TestSubscriber {
order_filled: order_filled_tx,
position: position_tx,
init_msg: init_msg_tx,
prices: prices_tx,
ask_price: ask_prices_tx,
bid_price: bid_prices_tx,
position_close: position_close_tx,
service: service_tx,
};
Expand All @@ -83,7 +87,8 @@ impl TestSubscriber {
order: order_rx,
position: position_rx,
init_msg: init_msg_rx,
prices: prices_rx,
ask_price: ask_prices_rx,
bid_price: bid_prices_rx,
position_close: position_close_rx,
services,
_service_map_updater,
Expand Down Expand Up @@ -111,8 +116,11 @@ impl TestSubscriber {
self.init_msg.borrow().as_ref().cloned()
}

pub fn prices(&self) -> Option<Prices> {
self.prices.borrow().as_ref().cloned()
pub fn ask_price(&self) -> Option<Decimal> {
self.ask_price.borrow().as_ref().cloned()
}
pub fn bid_price(&self) -> Option<Decimal> {
self.bid_price.borrow().as_ref().cloned()
}

pub fn position_close(&self) -> Option<ContractSymbol> {
Expand Down Expand Up @@ -142,7 +150,8 @@ impl Subscriber for Senders {
EventType::OrderUpdateNotification,
EventType::PositionUpdateNotification,
EventType::PositionClosedNotification,
EventType::PriceUpdateNotification,
EventType::AskPriceUpdateNotification,
EventType::BidPriceUpdateNotification,
EventType::ServiceHealthUpdate,
EventType::ChannelStatusUpdate,
]
Expand Down Expand Up @@ -174,8 +183,11 @@ impl Senders {
native::event::EventInternal::PositionCloseNotification(contract_symbol) => {
self.position_close.send(Some(*contract_symbol))?;
}
native::event::EventInternal::PriceUpdateNotification(prices) => {
self.prices.send(Some(prices.clone()))?;
native::event::EventInternal::AskPriceUpdateNotification(price) => {
self.ask_price.send(Some(*price))?;
}
native::event::EventInternal::BidPriceUpdateNotification(price) => {
self.bid_price.send(Some(*price))?;
}
native::event::EventInternal::ServiceHealthUpdate(update) => {
self.service.send(Some(update.clone()))?;
Expand Down
11 changes: 1 addition & 10 deletions crates/tests-e2e/tests/e2e_open_position_small_utxos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,7 @@ async fn can_open_position_with_multiple_small_utxos() {
};

// We take the ask price because the app is going long.
let ask_price = app
.rx
.prices()
.unwrap()
.get(&ContractSymbol::BtcUsd)
.unwrap()
.ask
.unwrap()
.to_f32()
.unwrap();
let ask_price = app.rx.ask_price().unwrap().to_f32().unwrap();

let margin_app = calculate_margin(ask_price, order.quantity, order.leverage).0;

Expand Down
2 changes: 1 addition & 1 deletion crates/trade/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl ContractSymbol {
}
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Direction {
Long,
Short,
Expand Down
4 changes: 2 additions & 2 deletions mobile/lib/common/amount_and_fiat_text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class AmountAndFiatText extends StatelessWidget {
Widget build(BuildContext context) {
return Selector<TradeValuesChangeNotifier, double>(
selector: (_, provider) {
var askPrice = provider.getPrice()?.ask ?? 0.0;
var bidPrice = provider.getPrice()?.bid ?? 0.0;
var askPrice = provider.getAskPrice() ?? 0.0;
var bidPrice = provider.getBidPrice() ?? 0.0;
var midMarket = (askPrice + bidPrice) / 2;
return midMarket;
},
Expand Down
9 changes: 6 additions & 3 deletions mobile/lib/common/init_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import 'package:get_10101/common/domain/background_task.dart';
import 'package:get_10101/common/domain/service_status.dart';
import 'package:get_10101/features/trade/domain/order.dart';
import 'package:get_10101/features/trade/domain/position.dart';
import 'package:get_10101/features/trade/domain/price.dart';
import 'package:get_10101/features/wallet/domain/wallet_info.dart';
import 'package:get_10101/common/application/event_service.dart';
import 'package:get_10101/logger/logger.dart';
Expand Down Expand Up @@ -117,10 +116,14 @@ void subscribeToNotifiers(BuildContext context) {
walletChangeNotifier, bridge.Event.walletInfoUpdateNotification(WalletInfo.apiDummy()));

eventService.subscribe(
tradeValuesChangeNotifier, bridge.Event.priceUpdateNotification(Price.apiDummy()));
tradeValuesChangeNotifier, const bridge.Event.askPriceUpdateNotification(0.0));
eventService.subscribe(
tradeValuesChangeNotifier, const bridge.Event.bidPriceUpdateNotification(0.0));

eventService.subscribe(
positionChangeNotifier, bridge.Event.priceUpdateNotification(Price.apiDummy()));
positionChangeNotifier, const bridge.Event.askPriceUpdateNotification(0.0));
eventService.subscribe(
positionChangeNotifier, const bridge.Event.bidPriceUpdateNotification(0.0));

eventService.subscribe(
serviceStatusNotifier, bridge.Event.serviceHealthUpdate(serviceUpdateApiDummy()));
Expand Down
10 changes: 3 additions & 7 deletions mobile/lib/features/trade/application/position_service.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:get_10101/features/trade/domain/position.dart';
import 'package:get_10101/features/trade/domain/price.dart';
import 'package:get_10101/ffi.dart' as rust;

class PositionService {
Expand All @@ -11,13 +10,10 @@ class PositionService {
}

/// Returns the pnl in sat
int? calculatePnl(Position position, Price price) {
if (!price.isValid()) {
return null;
}
int? calculatePnl(Position position, double askPrice, double bidPrice) {
final closingPrice = rust.Price(
bid: price.bid!,
ask: price.ask!,
bid: bidPrice,
ask: askPrice,
);
return rust.api.calculatePnl(
openingPrice: position.averageEntryPrice,
Expand Down
20 changes: 0 additions & 20 deletions mobile/lib/features/trade/domain/price.dart

This file was deleted.

24 changes: 16 additions & 8 deletions mobile/lib/features/trade/position_change_notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import 'package:get_10101/features/trade/application/position_service.dart';
import 'package:get_10101/features/trade/domain/contract_symbol.dart';
import 'package:get_10101/util/preferences.dart';
import 'package:get_10101/features/trade/domain/position.dart';
import 'package:get_10101/features/trade/domain/price.dart';

class PositionChangeNotifier extends ChangeNotifier implements Subscriber {
final PositionService _positionService;

Map<ContractSymbol, Position> positions = {};

Price? price;
double? askPrice;
double? bidPrice;

/// Amount of stabilised bitcoin in terms of USD (fiat)
double getStableUSDAmountInFiat() {
Expand Down Expand Up @@ -94,8 +94,8 @@ class PositionChangeNotifier extends ChangeNotifier implements Subscriber {
if (event is bridge.Event_PositionUpdateNotification) {
Position position = Position.fromApi(event.field0);

if (price != null) {
final pnl = _positionService.calculatePnl(position, price!);
if (askPrice != null && bidPrice != null) {
final pnl = _positionService.calculatePnl(position, askPrice!, bidPrice!);
position.unrealizedPnl = pnl != null ? Amount(pnl) : null;
} else {
position.unrealizedPnl = null;
Expand All @@ -116,12 +116,20 @@ class PositionChangeNotifier extends ChangeNotifier implements Subscriber {
Preferences.instance.unsetOpenPosition();

notifyListeners();
} else if (event is bridge.Event_PriceUpdateNotification) {
price = Price.fromApi(event.field0);
} else if (event is bridge.Event_AskPriceUpdateNotification ||
event is bridge.Event_BidPriceUpdateNotification) {
if (event is bridge.Event_AskPriceUpdateNotification) {
askPrice = event.field0;
}
if (event is bridge.Event_BidPriceUpdateNotification) {
bidPrice = event.field0;
}

for (ContractSymbol symbol in positions.keys) {
if (price != null) {
if (askPrice != null && bidPrice != null) {
if (positions[symbol] != null) {
final pnl = _positionService.calculatePnl(positions[symbol]!, price!);
// TODO: we can optimize this as we know the direction already we should only need to pass in one of the prices
final pnl = _positionService.calculatePnl(positions[symbol]!, askPrice!, bidPrice!);
positions[symbol]!.unrealizedPnl = pnl != null ? Amount(pnl) : null;
}
}
Expand Down
4 changes: 2 additions & 2 deletions mobile/lib/features/trade/position_list_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ class _PositionListItemState extends State<PositionListItem> {

// We're a bit conservative, we only enable action when we have both bid and ask
bool priceAvailable = notNullPosition.direction == Direction.long
? positionChangeNotifier.price?.ask != null
: positionChangeNotifier.price?.bid != null;
? positionChangeNotifier.bidPrice != null
: positionChangeNotifier.askPrice != null;

if (!isPositionExpired) {
Timer(notNullPosition.expiry.difference(DateTime.now().toUtc()), () {
Expand Down
41 changes: 22 additions & 19 deletions mobile/lib/features/trade/trade_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:get_10101/features/trade/candlestick_change_notifier.dart';
import 'package:get_10101/features/trade/domain/direction.dart';
import 'package:get_10101/features/trade/domain/order.dart';
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/order_list_item.dart';
import 'package:get_10101/features/trade/position_change_notifier.dart';
Expand Down Expand Up @@ -50,32 +49,36 @@ class TradeScreen extends StatelessWidget {
height: 60,
);

bool isBuyButtonEnabled = positionChangeNotifier.price?.ask != null;
bool isSellButtonEnabled = positionChangeNotifier.price?.bid != null;
bool isBuyButtonEnabled = positionChangeNotifier.askPrice != null;
bool isSellButtonEnabled = positionChangeNotifier.bidPrice != null;

return Scaffold(
body: Container(
padding: const EdgeInsets.only(left: 15, right: 15),
child: Column(
children: [
const SizedBox(height: 5),
Selector<TradeValuesChangeNotifier, Price?>(selector: (_, provider) {
return provider.getPrice();
}, builder: (context, price, child) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
LatestPriceWidget(
label: "Latest Bid: ",
price: Usd.fromDouble(price?.bid ?? 0.0),
),
LatestPriceWidget(
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Selector<TradeValuesChangeNotifier, double?>(selector: (_, provider) {
return provider.getAskPrice();
}, builder: (context, price, child) {
return LatestPriceWidget(
label: "Latest Ask: ",
price: Usd.fromDouble(price?.ask ?? 0.0),
),
],
);
}),
price: Usd.fromDouble(price ?? 0.0),
);
}),
Selector<TradeValuesChangeNotifier, double?>(selector: (_, provider) {
return provider.getBidPrice();
}, builder: (context, price, child) {
return LatestPriceWidget(
label: "Latest Bid: ",
price: Usd.fromDouble(price ?? 0.0),
);
}),
],
),
const SizedBox(height: 5),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
Expand Down
Loading

0 comments on commit 3b8d670

Please sign in to comment.