From d3f20eda6224ba8f821116640d9896ded165e786 Mon Sep 17 00:00:00 2001 From: cryptosharks131 <38626122+cryptosharks131@users.noreply.github.com> Date: Sun, 13 Feb 2022 16:23:28 -0500 Subject: [PATCH] v1.0.3 (#40) --- README.md | 5 + gui/forms.py | 2 + gui/migrations/0020_auto_20220126_2113.py | 71 ++++++++++++ gui/models.py | 14 ++- gui/serializers.py | 3 + gui/templates/advanced.html | 37 +++++- gui/templates/balances.html | 10 +- gui/templates/base.html | 2 +- gui/templates/channels.html | 6 +- gui/templates/closures.html | 41 +++++++ gui/templates/fee_rates.html | 66 +++++++++++ gui/templates/home.html | 24 ++-- gui/templates/open_list.html | 50 ++++---- gui/urls.py | 6 +- gui/views.py | 135 ++++++++++++++++++++-- jobs.py | 66 +++++++++-- rebalancer.py | 13 +-- 17 files changed, 471 insertions(+), 80 deletions(-) create mode 100644 gui/migrations/0020_auto_20220126_2113.py create mode 100644 gui/templates/closures.html create mode 100644 gui/templates/fee_rates.html diff --git a/README.md b/README.md index b726d339..73b55e65 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,11 @@ Alternatively, you may also make your own task for these files with your preferr You can serve the dashboard at all times using a webserver instead of the development server. Using a webserver will serve your static files and installing whitenoise is not required when running in this manner. Any webserver can be used to host the site if configured properly. A bash script has been included to help aide in the setup of a nginx webserver. `sudo bash nginx.sh` ## Key Features +### Suggests Fee Rates +LNDg will make suggestions on an adjustment to the current set outbound fee rate for each channel. This uses historical payment and forwarding data over the last 7 days to drive suggestions. + +You may see another adjustment right after setting the new suggested fee rate on some channels. This is normal and you should wait ~24 hours before changing the fee rate again on any given channel. + ### Suggests New Peers LNDg will make suggestions for new peers to open channels to based on your node's successful routing history. #### There are two unique values in LNDg: diff --git a/gui/forms.py b/gui/forms.py index 1948ab92..e5d4d450 100644 --- a/gui/forms.py +++ b/gui/forms.py @@ -79,6 +79,8 @@ class AutoRebalanceForm(forms.Form): (3, 'ar_in_target'), (4, 'ar_out_target'), (5, 'ar_enabled'), + (6, 'ar_max_cost'), + (7, 'channel_state'), ] class UpdateChannel(forms.Form): diff --git a/gui/migrations/0020_auto_20220126_2113.py b/gui/migrations/0020_auto_20220126_2113.py new file mode 100644 index 00000000..858580b5 --- /dev/null +++ b/gui/migrations/0020_auto_20220126_2113.py @@ -0,0 +1,71 @@ +# Generated by Django 3.2.7 on 2022-01-26 21:13 + +from django.db import migrations, models +import django.utils.timezone + +def update_defaults(apps, schedma_editor): + channels = apps.get_model('gui', 'channels') + settings = apps.get_model('gui', 'localsettings') + try: + if settings.objects.filter(key='AR-MaxCost%').exists(): + ar_max_cost = float(settings.objects.filter(key='AR-MaxCost%')[0].value) + else: + ar_max_cost = 0.75 + channels.objects.all().update(ar_max_cost=ar_max_cost*100) + except Exception as e: + print('Migration step failed:', str(e)) + +def revert_defaults(apps, schedma_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('gui', '0019_auto_20220122_1009'), + ] + + operations = [ + migrations.AddField( + model_name='channels', + name='ar_max_cost', + field=models.IntegerField(default=65), + ), + migrations.AddField( + model_name='channels', + name='last_update', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AddField( + model_name='channels', + name='local_disabled', + field=models.BooleanField(default=False), + preserve_default=False, + ), + migrations.AddField( + model_name='channels', + name='remote_disabled', + field=models.BooleanField(default=False), + preserve_default=False, + ), + migrations.AddField( + model_name='payments', + name='cleaned', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='invoices', + name='message', + field=models.CharField(max_length=500, null=True), + ), + migrations.RunPython(update_defaults, revert_defaults), + migrations.AlterField( + model_name='channels', + name='ar_max_cost', + field=models.IntegerField(), + ), + migrations.AlterField( + model_name='channels', + name='last_update', + field=models.DateTimeField(), + ), + ] diff --git a/gui/models.py b/gui/models.py index db489a57..0a5ce8c6 100644 --- a/gui/models.py +++ b/gui/models.py @@ -13,6 +13,7 @@ class Payments(models.Model): chan_out_alias = models.CharField(null=True, max_length=32) keysend_preimage = models.CharField(null=True, max_length=64) message = models.CharField(null=True, max_length=255) + cleaned = models.BooleanField(default=False) class Meta: app_label = 'gui' @@ -41,7 +42,7 @@ class Invoices(models.Model): chan_in = models.IntegerField(null=True) chan_in_alias = models.CharField(null=True, max_length=32) keysend_preimage = models.CharField(null=True, max_length=64) - message = models.CharField(null=True, max_length=255) + message = models.CharField(null=True, max_length=500) index = models.IntegerField() class Meta: app_label = 'gui' @@ -74,14 +75,18 @@ class Channels(models.Model): alias = models.CharField(max_length=32) local_base_fee = models.IntegerField() local_fee_rate = models.IntegerField() + local_disabled = models.BooleanField() remote_base_fee = models.IntegerField() remote_fee_rate = models.IntegerField() + remote_disabled = models.BooleanField() is_active = models.BooleanField() is_open = models.BooleanField() + last_update = models.DateTimeField() auto_rebalance = models.BooleanField(default=False) ar_amt_target = models.BigIntegerField() ar_in_target = models.IntegerField(default=100) ar_out_target = models.IntegerField() + ar_max_cost = models.IntegerField() def save(self, *args, **kwargs): if not self.ar_out_target: @@ -98,6 +103,13 @@ def save(self, *args, **kwargs): LocalSettings(key='AR-Target%', value='0.05').save() amt_setting = 0.05 self.ar_amt_target = int(amt_setting * self.capacity) + if not self.ar_max_cost: + if LocalSettings.objects.filter(key='AR-MaxCost%').exists(): + cost_setting = float(LocalSettings.objects.filter(key='AR-MaxCost%')[0].value) + else: + LocalSettings(key='AR-MaxCost%', value='0.65').save() + cost_setting = 0.65 + self.ar_max_cost = int(cost_setting * 100) super(Channels, self).save(*args, **kwargs) class Meta: diff --git a/gui/serializers.py b/gui/serializers.py index d5d8d7b5..b5ea05e3 100644 --- a/gui/serializers.py +++ b/gui/serializers.py @@ -41,6 +41,9 @@ class ChannelSerializer(serializers.HyperlinkedModelSerializer): is_active = serializers.ReadOnlyField() is_open = serializers.ReadOnlyField() num_updates = serializers.ReadOnlyField() + local_disabled = serializers.ReadOnlyField() + remote_disabled = serializers.ReadOnlyField() + last_update = serializers.ReadOnlyField() class Meta: model = Channels exclude = [] diff --git a/gui/templates/advanced.html b/gui/templates/advanced.html index 6a85bdc0..c3ffa55a 100644 --- a/gui/templates/advanced.html +++ b/gui/templates/advanced.html @@ -14,11 +14,13 @@

Advanced Channel Settings

Outbound Liquidity Inbound Liquidity + Channel State oRate oBase iRate iBase Target Amt + Max Cost % oTarget% iTarget% AR @@ -32,7 +34,20 @@

Advanced Channel Settings

{{ channel.local_balance|intcomma }} ({{ channel.out_percent }}%)
{% if channel.in_percent == 0 %}
{% elif channel.out_percent == 0 %}
{% else %}
{% endif %}
{{ channel.remote_balance|intcomma }} ({{ channel.in_percent }}%) - + + {% if channel.is_active == True %} +
+ {% csrf_token %} + + + + +
+ {% else %} + --- + {% endif %} + +
{% csrf_token %} @@ -40,7 +55,7 @@

Advanced Channel Settings

- +
{% csrf_token %} @@ -48,8 +63,8 @@

Advanced Channel Settings

- {{ channel.remote_fee_rate|intcomma }} - {{ channel.remote_base_fee|intcomma }} + {{ channel.remote_fee_rate|intcomma }} + {{ channel.remote_base_fee|intcomma }}
{% csrf_token %} @@ -58,6 +73,14 @@

Advanced Channel Settings

+ +
+ {% csrf_token %} + + + +
+
{% csrf_token %} @@ -83,7 +106,7 @@

Advanced Channel Settings

- {{ channel.is_active }} + {{ channel.is_active }} {% endfor %} @@ -115,6 +138,10 @@

Update Local Settings

{% elif settings.key == 'AR-MaxFeeRate' %} + {% elif settings.key == 'LND-CleanPayments' %} + + {% elif settings.key == 'LND-RetentionDays' %} + {% elif settings.key|slice:":3" == 'AR-' %} {% else %} diff --git a/gui/templates/balances.html b/gui/templates/balances.html index 41c14eda..2c34fa63 100644 --- a/gui/templates/balances.html +++ b/gui/templates/balances.html @@ -12,12 +12,12 @@

Balances

Outpoint Confirmations - {% for uxto in utxos %} + {% for utxo in utxos %} - {{ uxto.address }} - {{ uxto.amount_sat|intcomma }} - {{ uxto.outpoint.txid_str }} - {{ uxto.confirmations|intcomma }} + {{ utxo.address }} + {{ utxo.amount_sat|intcomma }} + {{ utxo.outpoint.txid_str }} + {{ utxo.confirmations|intcomma }} {% endfor %} diff --git a/gui/templates/base.html b/gui/templates/base.html index 00522fb7..75d7f1ad 100644 --- a/gui/templates/base.html +++ b/gui/templates/base.html @@ -28,7 +28,7 @@

My Lnd Overview

diff --git a/gui/templates/channels.html b/gui/templates/channels.html index 8c2555ba..486fe444 100644 --- a/gui/templates/channels.html +++ b/gui/templates/channels.html @@ -11,8 +11,8 @@

Channel Performance

- 7-Day Activity And Revenue - 30-Day Activity And Revenue + 7-Day Activity And Revenue | {{ apy_7day }}% APY + 30-Day Activity And Revenue | {{ apy_30day }}% APY Local Fees Peer Fees Channel Health @@ -49,7 +49,7 @@

Channel Performance

{{ channel.local_base_fee|intcomma }} {{ channel.remote_fee_rate|intcomma }} {{ channel.remote_base_fee|intcomma }} - {{ channel.num_updates|intcomma }} + {{ channel.updates }}% {{ channel.initiator }} {% endfor %} diff --git a/gui/templates/closures.html b/gui/templates/closures.html new file mode 100644 index 00000000..5ad26ab2 --- /dev/null +++ b/gui/templates/closures.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} +{% block title %} {{ block.super }} - Closures{% endblock %} +{% block content %} +{% load humanize %} +{% if closures %} +
+

Closures

+ + + + + + + + + + + + + {% for closure in closures %} + + + + + + + + + + + + {% endfor %} +
Channel IDCapacityClosing TXIDSettled BalanceLocked BalanceClose HeightClose TypeOpenerCloser
{{ closure.chan_id }}{{ closure.capacity|intcomma }}{{ closure.closing_tx_hash }}{{ closure.settled_balance|intcomma }}{{ closure.time_locked_balance|intcomma }}{{ closure.close_height|intcomma }}{% if closure.close_type == 0 %}Cooperative{% elif closure.close_type == 1 %}Local Force{% elif closure.close_type == 2 %}Remote Force{% elif closure.close_type == 3 %}Breach{% elif closure.close_type == 4 %}Funding Cancelled{% elif closure.close_type == 5 %}Abandoned{% else %}{{ closure.close_type }}{% endif %}{% if closure.open_initiator == 0 %}Unknown{% elif closure.open_initiator == 1 %}Local{% elif closure.open_initiator == 2 %}Remote{% elif closure.open_initiator == 3 %}Both{% else %}{{ closure.open_initiator }}{% endif %}{% if closure.close_initiator == 0 %}Unknown{% elif closure.close_initiator == 1 %}Local{% elif closure.close_initiator == 2 %}Remote{% elif closure.close_initiator == 3 %}Both{% else %}{{ closure.close_initiator }}{% endif %}
+
+{% endif %} +{% if not closures %} +
+

No channel closures found!

+
+{% endif %} +{% endblock %} \ No newline at end of file diff --git a/gui/templates/fee_rates.html b/gui/templates/fee_rates.html new file mode 100644 index 00000000..3d1a469c --- /dev/null +++ b/gui/templates/fee_rates.html @@ -0,0 +1,66 @@ +{% extends "base.html" %} +{% block title %} {{ block.super }} - Fee Rates{% endblock %} +{% block content %} +{% load humanize %} +{% if channels %} +
+

Suggested Fee Rates

+
+ + + + + + + + + + + + + + + + + + + + + {% for channel in channels %} + + + + + + + + + + + + + + + + + + + + {% endfor %} +
Channel IDPeer AliasCapacityOutbound LiquidityInbound Liquidity7Day FlowOut RateRebal RateAssisted RatioAdjustmentSuggested RateoRateoBaseMax CostiRateiBase
{{ channel.chan_id }}{{ channel.alias }}{{ channel.capacity|intcomma }}{{ channel.local_balance|intcomma }} ({{ channel.out_percent }}%)
{% if channel.in_percent == 0 %}
{% elif channel.out_percent == 0 %}
{% else %}
{% endif %}
{{ channel.remote_balance|intcomma }} ({{ channel.in_percent }}%){% if channel.net_routed_7day > 0 %}OUT{% elif channel.net_routed_7day < 0 %}IN{% else %}---{% endif %}{% if channel.net_routed_7day != 0 %} | {{ channel.net_routed_7day }}{% endif %}{{ channel.out_rate }}{{ channel.rebal_ppm }}{{ channel.assisted_ratio }}{{ channel.adjustment }}{{ channel.new_rate|intcomma }} +
+ {% csrf_token %} + + + +
+
{{ channel.local_base_fee|intcomma }}{{ channel.ar_max_cost }}%{{ channel.remote_fee_rate|intcomma }}{{ channel.remote_base_fee|intcomma }}
+
+
+{% endif %} +{% if not channels %} +
+

You dont have any channels to analyze yet!

+
+{% endif %} +{% endblock %} \ No newline at end of file diff --git a/gui/templates/home.html b/gui/templates/home.html index 0116e537..ac9f2ce7 100644 --- a/gui/templates/home.html +++ b/gui/templates/home.html @@ -24,7 +24,7 @@

7-Day Routed: {{ routed_7day|intcomma }} | Value: {{ routed_7day_amt|intcomm

Inbound Liquidity: {{ inbound|intcomma }} | Outbound Liquidity: {{ outbound|intcomma }} | Liquidity Ratio: {{ liq_ratio }}%

Balance In Limbo: {{ limbo_balance|intcomma }} | Unsettled Liquidity: {{ unsettled|intcomma }} | Pending HTLCs: {{ pending_htlc_count }}

-

Suggested New Peers | Suggested AR Actions | Autopilot Logs | Channel Performance | Advanced Settings

+

Closures | New Peers | AR Actions | Fee Rates | Autopilot Logs | Channel Performance | Advanced Settings

{% if active_channels %}
@@ -54,19 +54,19 @@

Active Channels

{{ channel.chan_id }} {{ channel.alias }} - {{ channel.capacity|intcomma }} + {{ channel.capacity|intcomma }} {{ channel.local_balance|intcomma }} ({{ channel.outbound_percent }}%)
{% if channel.inbound_percent == 0 %}
{% elif channel.outbound_percent == 0 %}
{% else %}
{% endif %}
{{ channel.remote_balance|intcomma }} ({{ channel.inbound_percent }}%) {{ channel.unsettled_balance|intcomma }} ({{ channel.htlc_count }}) - {{ channel.local_fee_rate|intcomma }} - {{ channel.local_base_fee|intcomma }} + {{ channel.local_fee_rate|intcomma }} + {{ channel.local_base_fee|intcomma }} {{ channel.amt_routed_out_7day|intcomma }} M ({{ channel.routed_out_7day }}) {{ channel.amt_routed_in_7day|intcomma }} M ({{ channel.routed_in_7day }}) {{ channel.amt_routed_out|intcomma }} M ({{ channel.routed_out }}) {{ channel.amt_routed_in|intcomma }} M ({{ channel.routed_in }}) - {{ channel.remote_fee_rate|intcomma }} - {{ channel.remote_base_fee|intcomma }} + {{ channel.remote_fee_rate|intcomma }} + {{ channel.remote_base_fee|intcomma }} {% if channel.auto_rebalance == True %}
@@ -112,6 +112,7 @@

Inactive Channels

iBase Local Commit Local Reserve + Downtime Initiated By Me iTarget% AR @@ -125,12 +126,13 @@

Inactive Channels

{% if channel.inbound_percent == 0 %}
{% elif channel.outbound_percent == 0 %}
{% else %}
{% endif %}
{{ channel.remote_balance|intcomma }} {{ channel.unsettled_balance|intcomma }} - {{ channel.local_fee_rate|intcomma }} - {{ channel.local_base_fee|intcomma }} - {{ channel.remote_fee_rate|intcomma }} - {{ channel.remote_base_fee|intcomma }} + {{ channel.local_fee_rate|intcomma }} + {{ channel.local_base_fee|intcomma }} + {{ channel.remote_fee_rate|intcomma }} + {{ channel.remote_base_fee|intcomma }} {{ channel.local_commit|intcomma }} {{ channel.local_chan_reserve|intcomma }} + {{ channel.last_update|naturaltime|slice:":-4" }} {{ channel.initiator }} {% if channel.auto_rebalance == True %} @@ -307,7 +309,7 @@

Last 10 Payments Routed

{% endif %} {% if rebalances %}
-

Last 10 Rebalance Requests

+

Last 10 Rebalance Requests (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing)

diff --git a/gui/templates/open_list.html b/gui/templates/open_list.html index 7825e23a..70dfcd34 100644 --- a/gui/templates/open_list.html +++ b/gui/templates/open_list.html @@ -5,30 +5,32 @@ {% if open_list %}

Suggested Open List

-
Requested
- - - - - - - - - - - {% for node in open_list %} - - - - - - - - - - - {% endfor %} -
Node PubkeyNode AliasSuccessful Payments RoutedAmount RoutedFees PaidEffective PPMVolume ScoreSavings By Volume
{{ node.node_pubkey }}{{ node.alias }}{{ node.count }}{{ node.amount|add:"0"|intcomma }}{{ node.fees|add:"0"|intcomma }}{{ node.ppm|add:"0"|intcomma }}{{ node.score }}{{ node.sum_cost_to|add:"0"|intcomma }}
+
+ + + + + + + + + + + + {% for node in open_list %} + + + + + + + + + + + {% endfor %} +
Node PubkeyNode AliasSuccessful Payments RoutedAmount RoutedFees PaidEffective PPMVolume ScoreSavings By Volume
{{ node.node_pubkey }}{{ node.alias }}{{ node.count }}{{ node.amount|add:"0"|intcomma }}{{ node.fees|add:"0"|intcomma }}{{ node.ppm|add:"0"|intcomma }}{{ node.score }}{{ node.sum_cost_to|add:"0"|intcomma }}
+
{% endif %} {% if not open_list %} diff --git a/gui/urls.py b/gui/urls.py index a4f78d38..714725c0 100644 --- a/gui/urls.py +++ b/gui/urls.py @@ -21,6 +21,7 @@ path('route', views.route, name='route'), path('peers', views.peers, name='peers'), path('balances', views.balances, name='balances'), + path('closures', views.closures, name='closures'), path('pending_htlcs', views.pending_htlcs, name='pending-htlcs'), path('failed_htlcs', views.failed_htlcs, name='failed-htlcs'), path('payments', views.payments, name='payments'), @@ -36,8 +37,9 @@ path('autorebalance/', views.auto_rebalance, name='auto-rebalance'), path('update_channel/', views.update_channel, name='update-channel'), path('update_setting/', views.update_setting, name='update-setting'), - path('suggested_opens/', views.suggested_opens, name='suggested-opens'), - path('suggested_actions/', views.suggested_actions, name='suggested-actions'), + path('opens/', views.opens, name='opens'), + path('actions/', views.actions, name='actions'), + path('fees/', views.fees, name='fees'), path('keysends/', views.keysends, name='keysends'), path('channels/', views.channels, name='channels'), path('autopilot/', views.autopilot, name='autopilot'), diff --git a/gui/views.py b/gui/views.py index ce4c93ca..ce978dfc 100644 --- a/gui/views.py +++ b/gui/views.py @@ -13,6 +13,8 @@ from .serializers import ConnectPeerSerializer, FailedHTLCSerializer, LocalSettingsSerializer, OpenChannelSerializer, CloseChannelSerializer, AddInvoiceSerializer, PaymentHopsSerializer, PaymentSerializer, InvoiceSerializer, ForwardSerializer, ChannelSerializer, PendingHTLCSerializer, RebalancerSerializer, UpdateAliasSerializer, PeerSerializer, OnchainSerializer, PendingHTLCs, FailedHTLCs from .lnd_deps import lightning_pb2 as ln from .lnd_deps import lightning_pb2_grpc as lnrpc +from gui.lnd_deps import router_pb2 as lnr +from gui.lnd_deps import router_pb2_grpc as lnrouter from .lnd_deps.lnd_connect import lnd_connect from lndg.settings import LND_NETWORK, LND_DIR_PATH from os import path @@ -100,8 +102,11 @@ def home(request): detailed_channel['alias'] = channel.alias detailed_channel['local_base_fee'] = channel.local_base_fee detailed_channel['local_fee_rate'] = channel.local_fee_rate + detailed_channel['local_disabled'] = channel.local_disabled detailed_channel['remote_base_fee'] = channel.remote_base_fee detailed_channel['remote_fee_rate'] = channel.remote_fee_rate + detailed_channel['remote_disabled'] = channel.remote_disabled + detailed_channel['last_update'] = channel.last_update detailed_channel['funding_txid'] = channel.funding_txid detailed_channel['output_index'] = channel.output_index detailed_channel['outbound_percent'] = int(round(channel.outbound_percent/10, 0)) @@ -119,7 +124,7 @@ def home(request): detailed_channel['ar_in_target'] = channel.ar_in_target detailed_active_channels.append(detailed_channel) #Get current inactive channels - inactive_channels = Channels.objects.filter(is_active=False, is_open=True).annotate(outbound_percent=(Sum('local_balance')*100)/Sum('capacity')).annotate(inbound_percent=(Sum('remote_balance')*100)/Sum('capacity')).order_by('-local_fee_rate').order_by('outbound_percent') + inactive_channels = Channels.objects.filter(is_active=False, is_open=True).annotate(outbound_percent=(Sum('local_balance')*100)/Sum('capacity')).annotate(inbound_percent=(Sum('remote_balance')*100)/Sum('capacity')).order_by('outbound_percent') inactive_outbound = 0 if inactive_channels.count() == 0 else inactive_channels.aggregate(Sum('local_balance'))['local_balance__sum'] sum_outbound = total_outbound + pending_outbound + inactive_outbound onchain_txs = Onchain.objects.all() @@ -184,6 +189,8 @@ def home(request): '7day_routed_ppm': 0 if routed_7day_amt == 0 else int((total_earned_7day/routed_7day_amt)*1000000), '7day_payments_ppm': 0 if payments_7day_amt == 0 else int((total_7day_fees/payments_7day_amt)*1000000), 'liq_ratio': 0 if total_outbound == 0 else int((total_inbound/sum_outbound)*100), + 'eligible_count': Channels.objects.filter(is_active=True, is_open=True, auto_rebalance=True).annotate(inbound_can=(Sum('remote_balance')*100)/Sum('capacity')).annotate(fee_ratio=(Sum('remote_fee_rate')*100)/Sum('local_fee_rate')).filter(inbound_can__gte=F('ar_in_target'), fee_ratio__lte=F('ar_max_cost')).count(), + 'enabled_count': Channels.objects.filter(is_open=True, auto_rebalance=True).count(), 'network': 'testnet/' if LND_NETWORK == 'testnet' else '', 'graph_links': graph_links(), 'network_links': network_links(), @@ -254,8 +261,14 @@ def channels(request): channels_df['profits_7day'] = channels_df.apply(lambda row: 0 if row['revenue_7day'] == 0 else row['revenue_7day'] - row['costs_7day'], axis=1) channels_df['profits_30day'] = channels_df.apply(lambda row: 0 if row['revenue_30day'] == 0 else row['revenue_30day'] - row['costs_30day'], axis=1) channels_df['open_block'] = channels_df.apply(lambda row: row.chan_id>>40, axis=1) + apy_7day = round((channels_df['profits_7day'].sum()*5214.2857)/channels_df['local_balance'].sum(), 2) + apy_30day = round((channels_df['profits_30day'].sum()*1216.6667)/channels_df['local_balance'].sum(), 2) + active_updates = channels_df['num_updates'].sum() + channels_df['updates'] = channels_df.apply(lambda row: 0 if active_updates == 0 else int(round((row['num_updates']/active_updates)*100, 0)), axis=1) context = { 'channels': channels_df.to_dict(orient='records'), + 'apy_7day': apy_7day, + 'apy_30day': apy_30day, 'network': 'testnet/' if LND_NETWORK == 'testnet' else '', 'graph_links': graph_links(), 'network_links': network_links() @@ -264,6 +277,58 @@ def channels(request): else: return redirect('home') +@login_required(login_url='/lndg-admin/login/?next=/') +def fees(request): + if request.method == 'GET': + filter_7day = datetime.now() - timedelta(days=7) + forwards = Forwards.objects.filter(forward_date__gte=filter_7day, amt_out_msat__gte=1000000) + channels = Channels.objects.filter(is_open=True) + channels_df = DataFrame.from_records(channels.values()) + if channels_df.shape[0] > 0: + forwards_df_7d = DataFrame.from_records(forwards.values()) + forwards_df_in_7d_sum = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_in', as_index=True).sum() + forwards_df_out_7d_sum = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_out', as_index=True).sum() + channels_df['in_percent'] = channels_df.apply(lambda row: int(round((row['remote_balance']/row['capacity'])*100, 0)), axis=1) + channels_df['out_percent'] = channels_df.apply(lambda row: int(round((row['local_balance']/row['capacity'])*100, 0)), axis=1) + channels_df['amt_routed_in_7day'] = channels_df.apply(lambda row: int(forwards_df_in_7d_sum.loc[row.chan_id].amt_out_msat/1000) if (forwards_df_in_7d_sum.index == row.chan_id).any() else 0, axis=1) + channels_df['amt_routed_out_7day'] = channels_df.apply(lambda row: int(forwards_df_out_7d_sum.loc[row.chan_id].amt_out_msat/1000) if (forwards_df_out_7d_sum.index == row.chan_id).any() else 0, axis=1) + channels_df['net_routed_7day'] = channels_df.apply(lambda row: round((row['amt_routed_out_7day']-row['amt_routed_in_7day'])/row['capacity'], 1), axis=1) + channels_df['revenue_7day'] = channels_df.apply(lambda row: int(forwards_df_out_7d_sum.loc[row.chan_id].fee) if forwards_df_out_7d_sum.empty == False and (forwards_df_out_7d_sum.index == row.chan_id).any() else 0, axis=1) + channels_df['revenue_assist_7day'] = channels_df.apply(lambda row: int(forwards_df_in_7d_sum.loc[row.chan_id].fee) if forwards_df_in_7d_sum.empty == False and (forwards_df_in_7d_sum.index == row.chan_id).any() else 0, axis=1) + channels_df['out_rate'] = channels_df.apply(lambda row: int((row['revenue_7day']/row['amt_routed_out_7day'])*1000000) if row['amt_routed_out_7day'] > 0 else 0, axis=1) + payments = Payments.objects.filter(status=2).filter(creation_date__gte=filter_7day).filter(payment_hash__in=Invoices.objects.filter(state=1).filter(settle_date__gte=filter_7day).values_list('r_hash')) + invoices = Invoices.objects.filter(state=1).filter(settle_date__gte=filter_7day).filter(r_hash__in=payments.values_list('payment_hash')) + payments_df_7d = DataFrame.from_records(payments.filter(creation_date__gte=filter_7day).values()) + invoices_df_7d = DataFrame.from_records(invoices.filter(settle_date__gte=filter_7day).values()) + invoices_df_7d_sum = DataFrame() if invoices_df_7d.empty else invoices_df_7d.groupby('chan_in', as_index=True).sum() + invoice_hashes_7d = DataFrame() if invoices_df_7d.empty else invoices_df_7d.groupby('chan_in', as_index=True)['r_hash'].apply(list) + channels_df['amt_rebal_in_7day'] = channels_df.apply(lambda row: int(invoices_df_7d_sum.loc[row.chan_id].amt_paid) if invoices_df_7d_sum.empty == False and (invoices_df_7d_sum.index == row.chan_id).any() else 0, axis=1) + channels_df['costs_7day'] = channels_df.apply(lambda row: 0 if row['amt_rebal_in_7day'] == 0 else int(payments_df_7d.set_index('payment_hash', inplace=False).loc[invoice_hashes_7d[row.chan_id] if invoice_hashes_7d.empty == False and (invoice_hashes_7d.index == row.chan_id).any() else []]['fee'].sum()), axis=1) + channels_df['rebal_ppm'] = channels_df.apply(lambda row: int((row['costs_7day']/row['amt_rebal_in_7day'])*1000000) if row['amt_rebal_in_7day'] > 0 else 0, axis=1) + channels_df['max_suggestion'] = channels_df.apply(lambda row: int((row['out_rate'] if row['out_rate'] > 0 else row['local_fee_rate'])*1.15), axis=1) + channels_df['min_suggestion'] = channels_df.apply(lambda row: int((row['out_rate'] if row['out_rate'] > 0 else row['local_fee_rate'])*0.75), axis=1) + channels_df['assisted_ratio'] = channels_df.apply(lambda row: round((row['revenue_assist_7day'] if row['revenue_7day'] == 0 else row['revenue_assist_7day']/row['revenue_7day']), 2), axis=1) + channels_df['profit_margin'] = channels_df.apply(lambda row: row['out_rate']*((100-row['ar_max_cost'])/100), axis=1) + channels_df['adjusted_out_rate'] = channels_df.apply(lambda row: int(row['out_rate']+row['net_routed_7day']*row['assisted_ratio']), axis=1) + channels_df['adjusted_rebal_rate'] = channels_df.apply(lambda row: int(row['rebal_ppm']+row['profit_margin']), axis=1) + channels_df['out_rate_only'] = channels_df.apply(lambda row: int(row['out_rate']+row['net_routed_7day']*row['out_rate']*0.02), axis=1) + channels_df['fee_rate_only'] = channels_df.apply(lambda row: int(row['local_fee_rate']+row['net_routed_7day']*row['local_fee_rate']*0.05), axis=1) + channels_df['new_rate'] = channels_df.apply(lambda row: row['adjusted_out_rate'] if row['net_routed_7day'] != 0 else (row['adjusted_rebal_rate'] if row['rebal_ppm'] > 0 and row['out_rate'] > 0 else (row['out_rate_only'] if row['out_rate'] > 0 else (row['min_suggestion'] if row['net_routed_7day'] == 0 and row['in_percent'] < 25 else row['fee_rate_only']))), axis=1) + channels_df['new_rate'] = channels_df.apply(lambda row: 0 if row['new_rate'] < 0 else row['new_rate'], axis=1) + channels_df['new_rate'] = channels_df.apply(lambda row: row['max_suggestion'] if row['max_suggestion'] > 0 and row['new_rate'] > row['max_suggestion'] else row['new_rate'], axis=1) + channels_df['new_rate'] = channels_df.apply(lambda row: row['min_suggestion'] if row['new_rate'] < row['min_suggestion'] else row['new_rate'], axis=1) + channels_df['new_rate'] = channels_df.apply(lambda row: int(round(row['new_rate']/5, 0)*5), axis=1) + channels_df['adjustment'] = channels_df.apply(lambda row: int(row['new_rate']-row['local_fee_rate']), axis=1) + context = { + 'channels': channels_df.sort_values(by=['out_percent']).to_dict(orient='records'), + 'network': 'testnet/' if LND_NETWORK == 'testnet' else '', + 'graph_links': graph_links(), + 'network_links': network_links() + } + return render(request, 'fee_rates.html', context) + else: + return redirect('home') + @login_required(login_url='/lndg-admin/login/?next=/') def advanced(request): if request.method == 'GET': @@ -272,6 +337,7 @@ def advanced(request): if channels_df.shape[0] > 0: channels_df['out_percent'] = channels_df.apply(lambda row: int(round(row['outbound_percent']/10, 0)), axis=1) channels_df['in_percent'] = channels_df.apply(lambda row: int(round(row['inbound_percent']/10, 0)), axis=1) + channels_df['fee_ratio'] = channels_df.apply(lambda row: 0 if row['local_fee_rate'] == 0 else int(round(((row['remote_fee_rate']/row['local_fee_rate'])*1000)/10, 0)), axis=1) context = { 'channels': channels_df.to_dict(orient='records'), 'local_settings': LocalSettings.objects.all(), @@ -322,14 +388,27 @@ def balances(request): return redirect('home') @login_required(login_url='/lndg-admin/login/?next=/') -def suggested_opens(request): +def closures(request): + if request.method == 'GET': + stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER)) + context = { + 'closures': stub.ClosedChannels(ln.ClosedChannelsRequest()).channels[::-1], + 'network': 'testnet/' if LND_NETWORK == 'testnet' else '', + 'network_links': network_links() + } + return render(request, 'closures.html', context) + else: + return redirect('home') + +@login_required(login_url='/lndg-admin/login/?next=/') +def opens(request): if request.method == 'GET': stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER)) self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey current_peers = Channels.objects.filter(is_open=True).values_list('remote_pubkey') filter_60day = datetime.now() - timedelta(days=60) payments_60day = Payments.objects.filter(creation_date__gte=filter_60day).values_list('payment_hash') - open_list = PaymentHops.objects.filter(payment_hash__in=payments_60day).exclude(node_pubkey=self_pubkey).exclude(node_pubkey__in=current_peers).values('node_pubkey', 'alias').annotate(ppm=(Sum('fee')/Sum('amt'))*1000000).annotate(score=Round((Round(Count('id')/5, output_field=IntegerField())+Round(Sum('amt')/500000, output_field=IntegerField()))/10, output_field=IntegerField())).annotate(count=Count('id')).annotate(amount=Sum('amt')).annotate(fees=Sum('fee')).annotate(sum_cost_to=Sum('cost_to')/(Sum('amt')/1000000)).order_by('-score', 'ppm')[:21] + open_list = PaymentHops.objects.filter(payment_hash__in=payments_60day).exclude(node_pubkey=self_pubkey).exclude(node_pubkey__in=current_peers).values('node_pubkey', 'alias').annotate(ppm=(Sum('fee')/Sum('amt'))*1000000).annotate(score=Round((Round(Count('id')/5, output_field=IntegerField())+Round(Sum('amt')/500000, output_field=IntegerField()))/10, output_field=IntegerField())).annotate(count=Count('id')).annotate(amount=Sum('amt')).annotate(fees=Sum('fee')).annotate(sum_cost_to=Sum('cost_to')/(Sum('amt')/1000000)).exclude(score=0).order_by('-score', 'ppm')[:21] context = { 'open_list': open_list, 'network': 'testnet/' if LND_NETWORK == 'testnet' else '', @@ -340,7 +419,7 @@ def suggested_opens(request): return redirect('home') @login_required(login_url='/lndg-admin/login/?next=/') -def suggested_actions(request): +def actions(request): if request.method == 'GET': channels = Channels.objects.filter(is_active=True, is_open=True).annotate(outbound_percent=(Sum('local_balance')*1000)/Sum('capacity')).annotate(inbound_percent=(Sum('remote_balance')*1000)/Sum('capacity')) filter_7day = datetime.now() - timedelta(days=7) @@ -720,7 +799,7 @@ def auto_rebalance(request): db_outbound_target = LocalSettings.objects.get(key='AR-Outbound%') db_outbound_target.value = outbound_percent db_outbound_target.save() - Channels.objects.all().update(ar_out_target=(round(outbound_percent*100, 0))) + Channels.objects.all().update(ar_out_target=int(outbound_percent*100)) messages.success(request, 'Updated auto rebalancer target outbound percent setting for all channels to: ' + str(outbound_percent)) if form.cleaned_data['fee_rate'] is not None: fee_rate = form.cleaned_data['fee_rate'] @@ -737,10 +816,11 @@ def auto_rebalance(request): try: db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%') except: - LocalSettings(key='AR-MaxCost%', value='0.50').save() + LocalSettings(key='AR-MaxCost%', value='0.65').save() db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%') db_max_cost.value = max_cost db_max_cost.save() + Channels.objects.all().update(ar_max_cost=int(max_cost*100)) messages.success(request, 'Updated auto rebalancer max cost setting to: ' + str(max_cost)) if form.cleaned_data['autopilot'] is not None: autopilot = form.cleaned_data['autopilot'] @@ -801,6 +881,24 @@ def update_channel(request): db_channel.auto_rebalance = True if db_channel.auto_rebalance == False else False db_channel.save() messages.success(request, 'Auto rebalancer status for chanel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(db_channel.auto_rebalance)) + elif update_target == 6: + db_channel.ar_max_cost = target + db_channel.save() + messages.success(request, 'Auto rebalancer max cost for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target) + '%') + elif update_target == 7: + stub = lnrouter.RouterStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER)) + channel_point = ln.ChannelPoint() + channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid) + channel_point.funding_txid_str = db_channel.funding_txid + channel_point.output_index = db_channel.output_index + stub.UpdateChanStatus(lnr.UpdateChanStatusRequest(chan_point=channel_point, action=0)) if target == 1 else stub.UpdateChanStatus(lnr.UpdateChanStatusRequest(chan_point=channel_point, action=1)) + db_channel.local_disabled = False if target == 1 else True + db_channel.save() + messages.success(request, 'Toggled channel state for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') to a value of: ' + ('Enabled' if target == 1 else 'Disabled')) + if target == 0: + messages.warning(request, 'Use with caution, while a channel is disabled (local fees highlighted in red) it will not route out.') + else: + messages.error(request, 'Invalid target code. Please try again.') else: messages.error(request, 'Invalid Request. Please try again.') return redirect(request.META.get('HTTP_REFERER')) @@ -852,7 +950,7 @@ def update_setting(request): db_outbound_target = LocalSettings.objects.get(key='AR-Outbound%') db_outbound_target.value = outbound_percent db_outbound_target.save() - Channels.objects.all().update(ar_out_target=(round(outbound_percent*100, 0))) + Channels.objects.all().update(ar_out_target=int(outbound_percent*100)) messages.success(request, 'Updated auto rebalancer target outbound percent setting for all channels to: ' + str(outbound_percent)) elif key == 'AR-MaxFeeRate': fee_rate = int(value) @@ -869,10 +967,11 @@ def update_setting(request): try: db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%') except: - LocalSettings(key='AR-MaxCost%', value='0.50').save() + LocalSettings(key='AR-MaxCost%', value='0.65').save() db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%') db_max_cost.value = max_cost db_max_cost.save() + Channels.objects.all().update(ar_max_cost=int(max_cost*100)) messages.success(request, 'Updated auto rebalancer max cost setting to: ' + str(max_cost)) elif key == 'AR-Autopilot': autopilot = int(value) @@ -904,6 +1003,26 @@ def update_setting(request): db_links.value = links db_links.save() messages.success(request, 'Updated network links to use: ' + str(links)) + elif key == 'LND-CleanPayments': + clean_payments = int(value) + try: + db_clean_payments = LocalSettings.objects.get(key='LND-CleanPayments') + except: + LocalSettings(key='LND-CleanPayments', value='0').save() + db_clean_payments = LocalSettings.objects.get(key='LND-CleanPayments') + db_clean_payments.value = clean_payments + db_clean_payments.save() + messages.success(request, 'Updated auto payment cleanup setting to: ' + str(clean_payments)) + elif key == 'LND-RetentionDays': + retention_days = int(value) + try: + db_retention_days = LocalSettings.objects.get(key='LND-RetentionDays') + except: + LocalSettings(key='LND-RetentionDays', value='0').save() + db_retention_days = LocalSettings.objects.get(key='LND-RetentionDays') + db_retention_days.value = retention_days + db_retention_days.save() + messages.success(request, 'Updated payment cleanup retention days to: ' + str(retention_days)) else: messages.error(request, 'Invalid Request. Please try again.') else: diff --git a/jobs.py b/jobs.py index 4a69a796..0de42748 100644 --- a/jobs.py +++ b/jobs.py @@ -1,6 +1,6 @@ import django from django.db.models import Max -from datetime import datetime +from datetime import datetime, timedelta from gui.lnd_deps import lightning_pb2 as ln from gui.lnd_deps import lightning_pb2_grpc as lnrpc from gui.lnd_deps.lnd_connect import lnd_connect @@ -8,7 +8,7 @@ from os import environ environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings' django.setup() -from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, PendingHTLCs +from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, PendingHTLCs, LocalSettings def update_payments(stub): #Remove anything in-flight so we can get most up to date status @@ -95,7 +95,7 @@ def update_invoices(stub): alias = Channels.objects.filter(chan_id=invoice.htlcs[0].chan_id)[0].alias if Channels.objects.filter(chan_id=invoice.htlcs[0].chan_id).exists() else None records = invoice.htlcs[0].custom_records keysend_preimage = records[5482373484].hex() if 5482373484 in records else None - message = records[34349334].decode('utf-8', errors='ignore')[:255] if 34349334 in records else None + message = records[34349334].decode('utf-8', errors='ignore')[:500] if 34349334 in records else None Invoices(creation_date=datetime.fromtimestamp(invoice.creation_date), settle_date=datetime.fromtimestamp(invoice.settle_date), r_hash=invoice.r_hash.hex(), value=round(invoice.value_msat/1000, 3), amt_paid=invoice.amt_paid_sat, state=invoice.state, chan_in=invoice.htlcs[0].chan_id, chan_in_alias=alias, keysend_preimage=keysend_preimage, message=message, index=invoice.add_index).save() else: Invoices(creation_date=datetime.fromtimestamp(invoice.creation_date), r_hash=invoice.r_hash.hex(), value=round(invoice.value_msat/1000, 3), amt_paid=invoice.amt_paid_sat, state=invoice.state, index=invoice.add_index).save() @@ -134,18 +134,24 @@ def update_channels(stub): if chan_data.node1_pub == channel.remote_pubkey: db_channel.local_base_fee = chan_data.node2_policy.fee_base_msat db_channel.local_fee_rate = chan_data.node2_policy.fee_rate_milli_msat + db_channel.local_disabled = chan_data.node2_policy.disabled db_channel.remote_base_fee = chan_data.node1_policy.fee_base_msat db_channel.remote_fee_rate = chan_data.node1_policy.fee_rate_milli_msat + db_channel.remote_disabled = chan_data.node1_policy.disabled else: db_channel.local_base_fee = chan_data.node1_policy.fee_base_msat db_channel.local_fee_rate = chan_data.node1_policy.fee_rate_milli_msat + db_channel.local_disabled = chan_data.node1_policy.disabled db_channel.remote_base_fee = chan_data.node2_policy.fee_base_msat db_channel.remote_fee_rate = chan_data.node2_policy.fee_rate_milli_msat + db_channel.remote_disabled = chan_data.node2_policy.disabled except: db_channel.local_base_fee = 0 db_channel.local_fee_rate = 0 + db_channel.local_disabled = False db_channel.remote_base_fee = 0 db_channel.remote_fee_rate = 0 + db_channel.remote_disabled = False db_channel.capacity = channel.capacity db_channel.local_balance = channel.local_balance db_channel.remote_balance = channel.remote_balance @@ -153,6 +159,7 @@ def update_channels(stub): db_channel.local_commit = channel.commit_fee db_channel.local_chan_reserve = channel.local_chan_reserve_sat db_channel.num_updates = channel.num_updates + db_channel.last_update = datetime.now() if db_channel.is_active != channel.active else db_channel.last_update db_channel.is_active = channel.active db_channel.is_open = True db_channel.save() @@ -233,16 +240,51 @@ def reconnect_peers(stub): peer.last_reconnected = datetime.now() peer.save() +def clean_payments(stub): + if LocalSettings.objects.filter(key='LND-CleanPayments').exists(): + enabled = int(LocalSettings.objects.filter(key='LND-CleanPayments')[0].value) + else: + LocalSettings(key='LND-CleanPayments', value='0').save() + LocalSettings(key='LND-RetentionDays', value='30').save() + enabled = 0 + if enabled == 1: + if LocalSettings.objects.filter(key='LND-RetentionDays').exists(): + retention_days = int(LocalSettings.objects.filter(key='LND-RetentionDays')[0].value) + else: + LocalSettings(key='LND-RetentionDays', value='30').save() + retention_days = 30 + time_filter = datetime.now() - timedelta(days=retention_days) + target_payments = Payments.objects.exclude(status=1).filter(cleaned=False).filter(creation_date__lte=time_filter).order_by('index')[:10] + for payment in target_payments: + payment_hash = bytes.fromhex(payment.payment_hash) + htlcs_only = True if payment.status == 2 else False + try: + stub.DeletePayment(ln.DeletePaymentRequest(payment_hash=payment_hash, failed_htlcs_only=htlcs_only)) + except Exception as e: + error = str(e) + details_index = error.find('details =') + 11 + debug_error_index = error.find('debug_error_string =') - 3 + error_msg = error[details_index:debug_error_index] + print('Error occured when cleaning payment: ' + payment.payment_hash) + print('Error: ' + error_msg) + finally: + payment.cleaned = True + payment.save() + def main(): - stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER)) - #Update data - update_channels(stub) - update_peers(stub) - update_payments(stub) - update_invoices(stub) - update_forwards(stub) - update_onchain(stub) - reconnect_peers(stub) + try: + stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER)) + #Update data + update_channels(stub) + update_peers(stub) + update_payments(stub) + update_invoices(stub) + update_forwards(stub) + update_onchain(stub) + reconnect_peers(stub) + clean_payments(stub) + except Exception as e: + print('Error processing background data: ' + str(e)) if __name__ == '__main__': main() diff --git a/rebalancer.py b/rebalancer.py index 99ec7082..f74671a7 100644 --- a/rebalancer.py +++ b/rebalancer.py @@ -81,21 +81,18 @@ def auto_schedule(): outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).values_list('chan_id', flat=True)) inbound_cans = auto_rebalance_channels.filter(auto_rebalance=True, inbound_can__gte=1) if len(inbound_cans) > 0 and len(outbound_cans) > 0: - if not LocalSettings.objects.filter(key='AR-Target%').exists(): - LocalSettings(key='AR-Target%', value='0.05').save() if LocalSettings.objects.filter(key='AR-MaxFeeRate').exists(): max_fee_rate = int(LocalSettings.objects.filter(key='AR-MaxFeeRate')[0].value) else: LocalSettings(key='AR-MaxFeeRate', value='100').save() max_fee_rate = 100 - if LocalSettings.objects.filter(key='AR-MaxCost%').exists(): - max_cost = float(LocalSettings.objects.filter(key='AR-MaxCost%')[0].value) - else: - LocalSettings(key='AR-MaxCost%', value='0.50').save() - max_cost = 0.50 + if not LocalSettings.objects.filter(key='AR-Target%').exists(): + LocalSettings(key='AR-Target%', value='0.05').save() + if not LocalSettings.objects.filter(key='AR-MaxCost%').exists(): + LocalSettings(key='AR-MaxCost%', value='0.65').save() # TLDR: lets target a custom % of the amount that would bring us back to a 50/50 channel balance using the MaxFeerate to calculate sat fee intervals for target in inbound_cans: - target_fee_rate = int(target.local_fee_rate * max_cost) + target_fee_rate = int(target.local_fee_rate * (target.ar_max_cost/100)) if target_fee_rate > 0 and target_fee_rate > target.remote_fee_rate: value_per_fee = int(1 / (target_fee_rate / 1000000)) if target_fee_rate <= max_fee_rate else int(1 / (max_fee_rate / 1000000)) target_value = int(target.ar_amt_target / value_per_fee) * value_per_fee