Skip to content

Commit

Permalink
Merge pull request #1878 from bonomat/feat/orders-in-webapp
Browse files Browse the repository at this point in the history
Make use of latest quote
  • Loading branch information
bonomat authored Jan 23, 2024
2 parents 30ce5b0 + 61161e6 commit ee6609a
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 47 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions webapp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ tower = { version = "0.4", features = ["util"] }
tower-http = { version = "0.5", features = ["fs", "trace", "cors"] }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
trade = { path = "../crates/trade" }
uuid = { version = "1.3.0", features = ["v4"] }
91 changes: 69 additions & 22 deletions webapp/frontend/lib/common/scaffold_with_nav.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get_10101/common/balance.dart';
import 'package:get_10101/common/version_service.dart';
import 'package:get_10101/logger/logger.dart';
import 'package:get_10101/trade/orderbook_service.dart';
import 'package:get_10101/wallet/wallet_service.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
Expand Down Expand Up @@ -38,6 +40,7 @@ class _ScaffoldWithNestedNavigation extends State<ScaffoldWithNestedNavigation>

String version = "unknown";
Balance balance = Balance.zero();
BestQuote? bestQuote;

void _goBranch(int index) {
widget.navigationShell.goBranch(
Expand All @@ -58,6 +61,9 @@ class _ScaffoldWithNestedNavigation extends State<ScaffoldWithNestedNavigation>
super.initState();
context.read<VersionService>().fetchVersion().then((v) => setState(() => version = v.version));
context.read<WalletService>().getBalance().then((b) => setState(() => balance = b));
context.read<QuoteService>().fetchQuote().then((q) => setState(() {
bestQuote = q;
}));
}

@override
Expand All @@ -72,6 +78,7 @@ class _ScaffoldWithNestedNavigation extends State<ScaffoldWithNestedNavigation>
showAsDrawer: showAsDrawer,
version: version,
balance: balance,
bestQuote: bestQuote,
);
} else {
return ScaffoldWithNavigationBar(
Expand Down Expand Up @@ -121,6 +128,7 @@ class ScaffoldWithNavigationRail extends StatelessWidget {
required this.showAsDrawer,
required this.version,
required this.balance,
required this.bestQuote,
});

final Widget body;
Expand All @@ -129,6 +137,7 @@ class ScaffoldWithNavigationRail extends StatelessWidget {
final bool showAsDrawer;
final String version;
final Balance balance;
final BestQuote? bestQuote;

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -170,29 +179,33 @@ class ScaffoldWithNavigationRail extends StatelessWidget {
padding: const EdgeInsets.all(25),
child: Row(
children: [
RichText(
text: TextSpan(
text: "Off-chain: ",
style: const TextStyle(fontSize: 16, color: Colors.black),
children: [
TextSpan(
text: balance.offChain.formatted(),
style: const TextStyle(fontWeight: FontWeight.bold)),
const TextSpan(text: " sats"),
]),
),
TopBarItem(label: 'Latest Bid: ', value: [
TextSpan(
text: bestQuote?.bid?.toString(),
style: const TextStyle(fontWeight: FontWeight.bold),
)
]),
const SizedBox(width: 30),
RichText(
text: TextSpan(
text: "On-chain: ",
style: const TextStyle(fontSize: 16, color: Colors.black),
children: [
TextSpan(
text: balance.onChain.formatted(),
style: const TextStyle(fontWeight: FontWeight.bold)),
const TextSpan(text: " sats"),
]),
),
TopBarItem(label: 'Latest Ask: ', value: [
TextSpan(
text: bestQuote?.ask?.toString(),
style: const TextStyle(fontWeight: FontWeight.bold),
)
]),
const SizedBox(width: 30),
TopBarItem(label: 'Off-chain: ', value: [
TextSpan(
text: balance.offChain.formatted(),
style: const TextStyle(fontWeight: FontWeight.bold)),
const TextSpan(text: " sats"),
]),
const SizedBox(width: 30),
TopBarItem(label: 'On-chain: ', value: [
TextSpan(
text: balance.onChain.formatted(),
style: const TextStyle(fontWeight: FontWeight.bold)),
const TextSpan(text: " sats"),
]),
],
),
),
Expand All @@ -207,3 +220,37 @@ class ScaffoldWithNavigationRail extends StatelessWidget {
);
}
}

class TopBarItem extends StatelessWidget {
final String label;
final List<InlineSpan> value;

const TopBarItem({super.key, required this.label, required this.value});

@override
Widget build(BuildContext context) {
return value.isEmpty
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
label,
style: const TextStyle(color: Colors.black),
),
const SizedBox(width: 10),
const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(),
),
],
)
: RichText(
text: TextSpan(
text: label,
style: const TextStyle(fontSize: 16, color: Colors.black),
children: value,
),
);
}
}
2 changes: 2 additions & 0 deletions webapp/frontend/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get_10101/common/version_service.dart';
import 'package:get_10101/logger/logger.dart';
import 'package:get_10101/routes.dart';
import 'package:get_10101/trade/orderbook_service.dart';
import 'package:get_10101/settings/settings_service.dart';
import 'package:get_10101/wallet/wallet_service.dart';
import 'package:provider/provider.dart';
Expand All @@ -17,6 +18,7 @@ void main() {
var providers = [
Provider(create: (context) => const VersionService()),
Provider(create: (context) => const WalletService()),
Provider(create: (context) => const QuoteService()),
Provider(create: (context) => const SettingsService())
];
runApp(MultiProvider(providers: providers, child: const TenTenOneApp()));
Expand Down
3 changes: 3 additions & 0 deletions webapp/frontend/lib/trade/open_position_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class Position {
final DateTime expiry;
final DateTime updated;
final DateTime created;
final Amount pnlSats;

Position({
required this.leverage,
Expand All @@ -43,6 +44,7 @@ class Position {
required this.expiry,
required this.updated,
required this.created,
required this.pnlSats,
});

factory Position.fromJson(Map<String, dynamic> json) {
Expand All @@ -58,6 +60,7 @@ class Position {
expiry: DateTime.parse(json['expiry'] as String),
updated: DateTime.parse(json['updated'] as String),
created: DateTime.parse(json['created'] as String),
pnlSats: Amount(json['pnl_sats']),
);
}
}
30 changes: 20 additions & 10 deletions webapp/frontend/lib/trade/order_and_position_table.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import 'package:flutter/material.dart';
import 'package:get_10101/common/color.dart';
import 'package:get_10101/logger/logger.dart';
import 'package:get_10101/trade/open_position_service.dart';
import 'package:get_10101/trade/orderbook_service.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';

class OrderAndPositionTable extends StatefulWidget {
const OrderAndPositionTable({super.key});
Expand All @@ -14,6 +16,15 @@ class OrderAndPositionTable extends StatefulWidget {
class OrderAndPositionTableState extends State<OrderAndPositionTable>
with SingleTickerProviderStateMixin {
late final _tabController = TabController(length: 2, vsync: this);
BestQuote? bestQuote;

@override
void initState() {
super.initState();
context.read<QuoteService>().fetchQuote().then((q) => setState(() {
bestQuote = q;
}));
}

@override
Widget build(BuildContext context) {
Expand All @@ -39,7 +50,7 @@ class OrderAndPositionTableState extends State<OrderAndPositionTable>
child: TabBarView(
controller: _tabController,
children: const <Widget>[
SimpleTableWidget(),
OpenPositionTable(),
Text("Pending"),
],
))
Expand All @@ -48,8 +59,8 @@ class OrderAndPositionTableState extends State<OrderAndPositionTable>
}
}

class SimpleTableWidget extends StatelessWidget {
const SimpleTableWidget({super.key});
class OpenPositionTable extends StatelessWidget {
const OpenPositionTable({super.key});

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -79,7 +90,7 @@ class SimpleTableWidget extends StatelessWidget {
0: MinColumnWidth(FixedColumnWidth(100.0), FractionColumnWidth(0.1)),
1: MinColumnWidth(FixedColumnWidth(100.0), FractionColumnWidth(0.1)),
2: MinColumnWidth(FixedColumnWidth(100.0), FractionColumnWidth(0.1)),
3: MinColumnWidth(FixedColumnWidth(100.0), FractionColumnWidth(0.1)),
3: MinColumnWidth(FixedColumnWidth(150.0), FractionColumnWidth(0.1)),
4: MinColumnWidth(FixedColumnWidth(100.0), FractionColumnWidth(0.1)),
5: MinColumnWidth(FixedColumnWidth(100.0), FractionColumnWidth(0.1)),
6: MinColumnWidth(FixedColumnWidth(200.0), FractionColumnWidth(0.2)),
Expand Down Expand Up @@ -107,13 +118,12 @@ class SimpleTableWidget extends StatelessWidget {
for (var position in positions)
TableRow(
children: [
buildTableCell(position.quantity.formatted()),
buildTableCell(position.averageEntryPrice.formatted()),
buildTableCell(position.liquidationPrice.formatted()),
buildTableCell(position.collateral.formatted()),
buildTableCell(position.quantity.toString()),
buildTableCell(position.averageEntryPrice.toString()),
buildTableCell(position.liquidationPrice.toString()),
buildTableCell(position.collateral.toString()),
buildTableCell(position.leverage.formatted()),
// TODO: we need to get the latest quote to be able to calculate this
buildTableCell("0.0"),
buildTableCell(position.pnlSats.toString()),
buildTableCell("${DateFormat('dd-MM-yyyy – HH:mm').format(position.expiry)} CET"),
],
),
Expand Down
36 changes: 36 additions & 0 deletions webapp/frontend/lib/trade/orderbook_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:decimal/decimal.dart';
import 'package:get_10101/common/http_client.dart';
import 'package:get_10101/common/model.dart';
import 'dart:convert';

import 'package:get_10101/logger/logger.dart';

class BestQuote {
Price? bid;
Price? ask;

BestQuote({this.bid, this.ask});

factory BestQuote.fromJson(Map<String, dynamic> json) {
return BestQuote(
bid: (Price.parseString(json['bid'])),
ask: (Price.parseString(json['ask'])),
);
}
}

class QuoteService {
const QuoteService();

Future<BestQuote?> fetchQuote() async {
final response = await HttpClientManager.instance.get(Uri(path: '/api/quotes/BtcUsd'));

if (response.statusCode == 200) {
var quote2 = BestQuote.fromJson(jsonDecode(response.body) as Map<String, dynamic>);

return quote2;
} else {
return null;
}
}
}
24 changes: 12 additions & 12 deletions webapp/frontend/lib/trade/trade_screen_order_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ import 'package:get_10101/common/amount_text_input_form_field.dart';
import 'package:get_10101/common/model.dart';
import 'package:get_10101/common/snack_bar.dart';
import 'package:get_10101/common/theme.dart';
import 'package:get_10101/logger/logger.dart';
import 'package:get_10101/trade/new_order_service.dart';
import 'package:get_10101/trade/orderbook_service.dart';
import 'package:provider/provider.dart';

class NewOrderForm extends StatefulWidget {
final bool isLong;

// TODO: get price from service
final Quote quote = Quote(Price(41129.0), Price(41130.5));

// for now just to avoid division by 0 errors, later we should introduce a maintenance margin
final double maintenanceMargin = 0.001;

NewOrderForm({super.key, required this.isLong});
const NewOrderForm({super.key, required this.isLong});

@override
State<NewOrderForm> createState() => _NewOrderForm();
}

class _NewOrderForm extends State<NewOrderForm> {
Quote? _quote;
Usd? _quantity;
BestQuote? _quote;
Usd? _quantity = Usd(100);
Leverage _leverage = Leverage(1);
bool isBuy = true;
bool _isLoading = false;
Expand All @@ -36,12 +36,12 @@ class _NewOrderForm extends State<NewOrderForm> {
@override
void initState() {
super.initState();
_quote = widget.quote;
_quantity = Usd(100);
isBuy = widget.isLong;
_isLoading = false;
context.read<QuoteService>().fetchQuote().then((q) => setState(() {
_quote = q;

updateOrderValues();
updateOrderValues();
}));
}

@override
Expand Down Expand Up @@ -160,7 +160,7 @@ class _NewOrderForm extends State<NewOrderForm> {
}
}

Amount calculateMargin(Usd quantity, Quote quote, Leverage leverage, bool isLong) {
Amount calculateMargin(Usd quantity, BestQuote quote, Leverage leverage, bool isLong) {
if (isLong && quote.ask != null) {
return Amount.fromBtc(quantity.asDouble / (quote.ask!.asDouble * leverage.asDouble));
} else if (!isLong && quote.bid != null) {
Expand All @@ -171,7 +171,7 @@ Amount calculateMargin(Usd quantity, Quote quote, Leverage leverage, bool isLong
}

Amount calculateLiquidationPrice(
Usd quantity, Quote quote, Leverage leverage, double maintenanceMargin, bool isLong) {
Usd quantity, BestQuote quote, Leverage leverage, double maintenanceMargin, bool isLong) {
if (isLong && quote.ask != null) {
return Amount((quote.bid!.asDouble * leverage.asDouble) ~/
(leverage.asDouble + 1.0 + (maintenanceMargin * leverage.asDouble)));
Expand Down
1 change: 0 additions & 1 deletion webapp/frontend/lib/wallet/wallet_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class WalletService {
final response = await HttpClientManager.instance.get(Uri(path: '/api/balance'));

if (response.statusCode == 200) {
logger.i("body ${response.body}");
return Balance.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
} else {
throw FlutterError("Failed to fetch balance");
Expand Down
Loading

0 comments on commit ee6609a

Please sign in to comment.