• Talinn, Estonia
  • support@lnsolutions.ee

Select your language

Managing Git Feature Branches and Resolving Merge Conflicts

Managing Git Feature Branches and Resolving Merge Conflicts

Read more on Medium

 

Git is a powerful version control system widely used by developers to manage source code repositories. One of the key features of Git is its support for branching, allowing developers to work on new features or experiments without affecting the main codebase. However, managing branches and resolving merge conflicts effectively are crucial skills for successful collaboration and code management. In this article, we’ll explore common Git branching tasks and how to handle merge conflicts gracefully for feature branches.

Understanding Git Branches

Git branches are independent lines of development within a Git repository. They allow developers to work on different features, bug fixes, or experiments without affecting the main codebase. Here are some essential concepts related to Git branches:

  1. Creating a Branch: To create a new branch, you can use the git checkout -b <branch-name> command. This creates a new branch and switches to it in one step.
  2. Switching Branches: You can switch between branches using the git checkout <branch-name> command. This allows you to work on different features or bug fixes seamlessly.
  3. Listing Branches: To view a list of branches in your repository, you can use the git branch command. The branch you are currently on will be highlighted.
  4. Deleting a Branch: To delete a local branch in Git, you can use the git branch -d <branch-name> command. Be cautious when deleting branches, especially if they contain unmerged changes.

Handling Merge Conflicts

Merge conflicts occur when Git cannot automatically merge changes from different branches due to conflicting changes in the same part of a file. Here’s how to handle merge conflicts effectively:

  1. Fetching Remote Changes: Before merging branches, it’s essential to fetch the latest changes from the remote repository using git pull origin <branch-name>.
  2. Merging Branches: To merge changes from one branch into another, you can use the git merge <branch-name> command. Git will attempt to automatically merge the changes. If there are conflicts, Git will pause the merge process.
  3. Resolving Conflicts: To resolve merge conflicts, open the conflicted files in a text editor and manually resolve the conflicting sections. Remove the conflict markers (<<<<<<<=======>>>>>>>) and keep the changes you want to retain.
  4. Committing Changes: After resolving conflicts, stage the changes using git add . and commit the merge using git commit. Git will create a merge commit to finalize the merge process.

Working on a feature branch

Example diagram for a workflow with a feature branch

In this example we start with main and then create a new local branch and switch to it

Read more on Medium

Bitcoin Trading Bot Development on Discord Trading Signals and LN Markets

Bitcoin Trading Bot Development on Discord Trading Signals and LN Markets

 

Read on Medium

 

Photo by Alex Knight on Unsplash

Introduction

Discord has become a cornerstone platform for communication among gamers, communities, and teams worldwide. The extensibility of Discord through bots has unlocked a plethora of possibilities, enabling automation, moderation, and enhanced user experiences. In this guide, we’ll delve into Discord bot development, using LN Markets as trading platform.

Trading signals Discord channels and groups are online communities where traders come together to share valuable insights, trading signals, and analyses pertaining to the crypto market. These channels and groups are hosted on the Discord messaging platform, enabling seamless real-time communication and information exchange among members.

LN Markets is a platform that enables users to trade Bitcoin futures contracts using the Lightning Network, a second-layer solution for faster and cheaper Bitcoin transactions. LN Markets leverages the Lightning Network’s micro-payment capabilities to provide instant settlement and low transaction fees for trading activities.

Overall, LN Markets provides a unique trading experience for Bitcoin enthusiasts and traders looking to participate in the cryptocurrency markets using the Lightning Network’s innovative technology.

Getting Started with Discord Bot Development

  • Understanding Discord Bots: Bots are automated agents that can perform various tasks on Discord servers, ranging from moderation to information retrieval.
  • Setting Up Your Development Environment: Install Node.js and Discord.js library to begin building your bot.
  • Creating Your First Bot: Let’s create a simple bot that responds to commands and interacts with users.
// Import necessary modules
const Discord = require('discord.js');
const client = new Discord.Client();

// Define bot behavior
client.on('message', message => {
if (message.content === '!hello') {
message.reply('Hello, world!');
}
});

// Log in to Discord with your bot token
client.login('YOUR_DISCORD_BOT_TOKEN');

Essential Bot Features and Functionality

  • Message Handling: Read and respond to messages from users, including commands and regular chat.
  • User Interaction: Engage with users through reactions, mentions, and interactive commands.
  • Server Management: Join servers, manage channels, and handle server events like member joins or leaves.
  • Role Management: Assign roles, manage permissions, and enforce server rules.

Here we will just focus on reading the last message from the trading channel

// Register event for when client receives a message.
client.on('message', message => {
console.log(message.content);
processSignal(message);
});

Parsing the text and trading on LN Markets

We need to install the ln-markets api for our project via this command:

 npm install @ln-markets/api

The next code snippet will open a connection to the LN Markets API.

import { createRestClient } from '@ln-markets/api';

const key = config.lnmarketsKey.trim();
const secret = config.lnmarketsSecret.trim();
const passphrase = config.lnmarketsPassphrase.trim();
const network = 'testnet';
const lnclient = createRestClient({ key, secret, passphrase });

Now we parse the text from the Discord message and trade accordingly. In this example the signal didn’t come in plain text, but it used some rich text features from Discord (message.embeds), so these fields had to be parsed and not only the message.text field.

async function processSignal(message) {
if (message.embeds.length > 0 && message.embeds[0].title.includes('Buy Signal!')) {
// Extract trade information
let entryPrice = null;
let stopLoss = null;
let takeProfit = null;

// Split message content by lines
const createdTimestamp = message.createdTimestamp;
const lines = message.embeds[0].fields;
lines.forEach((line, index) => {
if (line.name.includes('Entry Price')) {
const priceLine = line.value.replace(/`/g, '');
entryPrice = parseFloat(priceLine);
} else if (line.name.includes('Stop Loss')) {
const priceLine = line.value.replace(/`/g, '');
stopLoss = Math.round(parseFloat(priceLine)) + 0.5;
} else if (!line.value.includes('There will be a separate signal to take profit') && line.name.includes('Take Profit')) {
const priceLine = line.value.replace(/`/g, '');
takeProfit = Math.round(parseFloat(priceLine)) + 0.5;
} else if (line.value.includes('There will be a separate signal to take profit that could be higher than this price.')) {
const regex = /```(\d+(\.\d+)?)```/;
// Extract the numeric value using the match method and regular expression
const match = line.value.match(regex);
if (match) {
// Extract the numeric value from the matched string
const priceLine = parseFloat(match[1]);
takeProfit = Math.round(parseFloat(priceLine)) + 0.5;
}
}
});

if (entryPrice !== null && stopLoss !== null) {
console.log(`Entry Price: ${entryPrice}`);
console.log(`Stop Loss: ${stopLoss}`);
if (takeProfit !== null) {
console.log(`Take Profit: ${takeProfit}`);
}

await getBuyTrade(createdTimestamp, takeProfit, stopLoss, entryPrice);
}
} else if (message.embeds.length > 0 && message.embeds[0].title.includes('Sell Signal!')) {
let sellPrice = null;

const createdTimestamp = message.createdTimestamp;
const lines = message.embeds[0].fields;
lines.forEach((line, index) => {
if (line.value.includes('position has been closed at:')) {
const regex = /```(\d+(\.\d+)?)```/;
// Extract the numeric value using the match method and regular expression
const match = line.value.match(regex);
if (match) {
// Extract the numeric value from the matched string
const priceLine = parseFloat(match[1]);
sellPrice = parseFloat(priceLine);
}
}
});

if (sellPrice !== null) {
console.log(`Sell Price: ${sellPrice}`);
await getTradeByTimestamp(createdTimestamp, (err, rows) => {
if (err) {
console.error(err.message);
} else {
// Check if any rows were returned
if (rows.length > 0) {
// Assuming id is the first column in the result set
const tradeId = rows[0].id;
console.log('Trade ID in Sell:', tradeId);
} else if (lastBuyTradeId != null) { // This is a new sell trade
// Perform Sell Trade in LNMarkets
const data = {
id: lastBuyTradeId,
};

console.log(data);

const trade = lnclient.futuresCloseTrade(lastBuyTradeId).then((response) => {
saveToDatabase(response.id, createdTimestamp, 0.0, 0.0, sellPrice, response.pl, 'Sell');
})
.catch((err) => {
// Handle error
console.error(err.message);
});

}
}
});
}
}
}

async function getBuyTrade(createdTimestamp, takeProfit, stopLoss, entryPrice) {
try {
// Call getTradeByTimestamp and wait for it to complete
const rows = await new Promise((resolve, reject) => {
getTradeByTimestamp(createdTimestamp, (err, rows) => {
if (err) {
reject(err);
} else {
resolve(rows);
}
});
});

// Check if any rows were returned
if (rows.length > 0) {
const tradeId = rows[0].id;
lastBuyTradeId = tradeId;
console.log('Trade ID in Buy:', tradeId);
} else {
// Perform Trade in LNMarkets
let data = {
"type": 'm',
"side": 'b',
"leverage": LEVERAGE,
"quantity": QUANTITY,
"takeprofit": takeProfit,
"stoploss": stopLoss
};

const trade = await lnclient.futuresNewTrade(data);
saveToDatabase(trade.id, createdTimestamp, entryPrice, stopLoss, takeProfit, 0.0, 'Buy');
}
} catch (error) {
console.error(error.message);
}

In the previous code snippet, the function getBuyTrade uses getTradeByTimestamp, which queries a local sqlite database file to query if this trade was already done. The function saveToDatabase saves the trade to this database when the trade is new. This in order to execute each trade only once.

function saveToDatabase(lnMarketsId, createdTimestamp, entryPrice, stopLoss, takeProfit, profit, tradeType) {
lastBuyTradeId = lnMarketsId;
createTrade(lnMarketsId, createdTimestamp, entryPrice, stopLoss, takeProfit, profit, tradeType, err => {
if (err) {
console.error(err.message);
} else {
console.log(tradeType + " Trade created successfully!");
}
});
}

Finally we add some cleanup code for our database connection

// Clean up database connection when the application exits
process.on('exit', () => {
closeConnection();
console.log('Database connection closed');
});

// Handle Ctrl+C (SIGINT) signal
process.on('SIGINT', () => {
closeConnection();
console.log('Database connection closed');
process.exit();
});

Conclusion

In this guide we saw an introduction on how to program a Bitcoin trading bot on LN Markets, based on trading signals sent to a specific Discord channel. By mastering the concepts and techniques outlined in this guide, you’ll be well-equipped to build bots that take signals on specific trading servers and automate your trading

References

 

Customization of ccGains for the generation of a yearly Crypto Report in jurisdictions where there is tax exemption for crypto holdings held for one year or more

Customization of ccGains for the generation of a yearly Crypto Report in jurisdictions where there is tax exemption for crypto holdings held for one year or more

Für Deutsch hier

Read on Medium

Image by fabrikasimf on Freepik

Introduction

In the dynamic world of cryptocurrency, where volatility and innovation intersect, the regulatory landscape is continuously evolving. One notable aspect of this evolution is the taxation of cryptocurrency gains. While many countries have implemented various tax regimes for cryptocurrencies, some offer favorable policies that exempt long-term holders from certain tax liabilities. One such policy gaining traction in several jurisdictions is the tax exemption for crypto holdings held for one year or more.

What is the One-Year Holding Policy?

The one-year holding policy, also known as long-term capital gains treatment, is a taxation framework that grants exemptions or reduced tax rates on profits generated from the sale or exchange of cryptocurrencies held for a minimum duration of one year. This policy aims to incentivize long-term investment in cryptocurrencies while encouraging stability in the market.

Countries with Tax-Free Crypto Gains Policy

Several countries have embraced the concept of tax-free crypto gains for long-term holders. Some of these nations include:

  1. Germany: Germany has emerged as one of the frontrunners in providing clarity on cryptocurrency taxation. In Germany, if you hold your cryptocurrencies for more than one year, any resulting gains are completely tax-free.
  2. Portugal: Portugal has gained attention for its tax-friendly policies towards cryptocurrencies. The country does not tax individuals on capital gains from the sale of cryptocurrencies if they have been held for more than one year.
  3. Singapore: Singapore is renowned for its pro-business environment and has adopted a similar approach to cryptocurrencies. Capital gains from long-term cryptocurrency holdings are not taxed in Singapore, provided certain conditions are met.
  4. Belarus: Belarus has implemented legislation that exempts individuals and businesses from taxes on cryptocurrency transactions, including capital gains, until 2023. This policy aims to attract investment and innovation in the country’s emerging tech sector.
  5. Switzerland: Switzerland, known for its favorable regulatory environment for finance and technology, treats cryptocurrencies as assets rather than currencies for tax purposes. Consequently, capital gains from long-term cryptocurrency holdings are typically tax-free for individuals.

What is ccGains?

The ccGains (cryptocurrency gains) package provides a python library for calculating capital gains made by trading cryptocurrencies or foreign currencies. It was created by Jürgen Probst and it is hosted in Github (link here). Some of its features are:

  • Calculates the capital gains using the first-in/first out (FIFO) principle, • creates capital gains reports as CSV, HTML or PDF (instantly ready to print out for the tax office),
  • Can create a more detailed capital gains report outlining the calculation and used bags,
  • Differs between short and long term gains (amounts held for less or more than a year),
  • Treats amounts held and traded on different exchanges separately,
  • Treats exchange fees and transaction fees directly resulting from trading properly as losses,
  • Provides methods to import your trading history from various exchanges,
  • Loads historic cryptocurrency prices from CSV files and/or
  • Loads historic prices from APIs provided by exchanges,
  • Caches historic price data on disk for quicker and offline retrieval and less traffic to exchanges,
  • For highest accuracy, uses the decimal data type for all amounts
  • Supports saving and loading the state of your portfolio as JSON file for use in ccGains calculations in following years

Installation

You’ll need Python (ccGains is tested under Python 2.7 and Python 3.x). Get it here: https://www.python.org/

ccGains can then easily be installed via the Python package manager pip:

  • Download the source code, e.g. by git clone https://github.com/probstj/ccgains.git
  • Inside the main ccGains directory run: pip install . (note the . at the end)
  • Alternatively, to install locally without admin rights: pip install --user .
  • And if you want to add changes to the source code and quickly want to try it without reinstalling, pip can install by linking to the source code folder: pip install -e .

Usage

Please have a look at examples/example.py and follow the comments to adapt it for your purposes.

Copy it to a new file for example taxReport2023.py.

1. Provide list of BTC historical prices in your native fiat currency

Hourly data for a lot of exchanges is available for download at:
https://api.bitcoincharts.com/v1/csv/
To understand which file to download, consult this list:
https://bitcoincharts.com/markets/list/

For EUR prices on Kraken, download: https://api.bitcoincharts.com/v1/csv/krakenEUR.csv.gz and place it in the ../data folder.

For CHF prices, download: https://api.bitcoincharts.com/v1/csv/anxhkCHF.csv.gz

For SGD prices, download: https://api.bitcoincharts.com/v1/csv/anxhkSGD.csv.gz

The file consists of three comma-separated columns: the unix timestamp, the price, and the volume (amount traded).

Create the HistoricData object by loading the mentioned file and
specifying the price unit, i.e. fiat/btc:

h1 = ccgains.HistoricDataCSV(
'../data/bitcoin_de_EUR_abridged_as_example.csv.gz', 'EUR/BTC')

2. Provide source of historical BTC prices for all traded alt coins

For all coins that you possessed at some point, their historical price in your native fiat currency must be known, which can also be derived from their BTC price and the BTC/fiat price given above (or even from their price in any other alt-coin, whose price can be derived, in turn.)

This data can be provided from any website that serves this data through an API, or from a csv-file, like above. Note that currently, only the API from Poloniex.com is implemented.

Create HistoricData objects to fetch rates from Poloniex.com: (it is important to mention at least all traded coins here)

    h2 = ccgains.HistoricDataAPI('data', 'btc/xmr')
h3 = ccgains.HistoricDataAPI('data', 'btc/eth')
h4 = ccgains.HistoricDataAPI('data', 'btc/usdt')
h5 = ccgains.HistoricDataAPI('data', 'btc/link')
h6 = ccgains.HistoricDataAPI('data', 'btc/bat')
h7 = ccgains.HistoricDataAPI('data', 'btc/zrx')
h8 = ccgains.HistoricDataAPI('data', 'btc/cvc')
h9 = ccgains.HistoricDataAPI('data', 'btc/dash')
h10 = ccgains.HistoricDataAPI('data', 'btc/knc')
h11 = ccgains.HistoricDataAPI('data', 'btc/mkr')
h12 = ccgains.HistoricDataAPI('data', 'btc/matic')
h13 = ccgains.HistoricDataAPI('data', 'btc/doge')
h14 = ccgains.HistoricDataAPI('data', 'btc/bch')
h15 = ccgains.HistoricDataAPI('data', 'btc/dot')
h16 = ccgains.HistoricDataAPI('data', 'btc/qtum')
h17 = ccgains.HistoricDataAPI('data', 'btc/ren')
h18 = ccgains.HistoricDataAPI('data', 'btc/str')
h19 = ccgains.HistoricDataAPI('data', 'btc/xtz')
h20 = ccgains.HistoricDataAPI('data', 'btc/trx')
h21 = ccgains.HistoricDataAPI('data', 'btc/zec')
h22 = ccgains.HistoricDataAPI('data', 'btc/ltc')
h23 = ccgains.HistoricDataAPI('data', 'btc/xrp')
h24 = ccgains.HistoricDataAPI('data', 'btc/omg')
h25 = ccgains.HistoricDataAPI('data', 'btc/etc')
h26 = ccgains.HistoricDataAPI('data', 'btc/dot')
h27 = ccgains.HistoricDataAPI('data', 'btc/dai')
h28 = ccgains.HistoricDataAPI('data', 'btc/usdc')
h29 = ccgains.HistoricDataAPICoinbase('data', 'cro/eur')
h30 = ccgains.HistoricDataAPIBinance('data', 'btc/uni')
h31 = ccgains.HistoricDataAPIBinance('data', 'avax/eur')
h32 = ccgains.HistoricDataAPIBinance('data', 'btc/dydx')
h33 = ccgains.HistoricDataAPIBinance('data', 'btc/iota')
h34 = ccgains.HistoricDataAPIBinance('data', 'btc/axs')

In h2 to h28 I used the class HistoricDataAPI which uses the public Poloniex API: https://poloniex.com/public?command=returnTradeHistory, since this is the exchange that has these pairs traded in the year 2023 in this example.

In h29 I used the class HistoricDataAPICoinbase which uses the public Coinbase API: ‘https://api.pro.coinbase.com/products/:SYMBOL:/candles’.

In h30 to h34 I used the class HistoricDataAPIBinance which will transparently fetch data on request (get_price) from the public Binance API: https://api.binance.com/api/v1/klines

For faster loading times on future calls, a HDF5 file is created from the requested data and used transparently the next time a request for the same day and pair is made. These HDF5 files are saved in cache_folder. The unit must be a string in the form ‘currency_one/currency_two’, e.g. ‘NEO/BTC’. The data will be resampled by calculating the weighted price for interval steps specified by interval. See: http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases for possible values.

prepare_request(dtime) Return a pandas DataFrame which contains the data for the requested datetime dtime.

3. Add all objects from above into a single ‘CurrencyRelation’ object

Create a CurrencyRelation object that puts all provided HistoricData currencies in relation in order to serve exchange rates for any pair of these currencies:

rel = ccgains.CurrencyRelation(h1, h2, h3, h4, h5, h6, h7, h8, h9, h10, h11, h12, h13, h14, h15, h16, h17, h18, h19, h20, h21, h22, h23, h24, h25, h26,h27, h28, h29, h30, h31, h32, h33, h34)

4. Create the ‘BagQueue’, which calculates the capital gains

Create the BagQueue object that calculates taxable profit from trades using the first-in/first-out method:

(this needs to know your native fiat currency and the CurrencyRelation created above)

bf = ccgains.BagQueue('EUR', rel)

5. Create the object that will load all your trades

The TradeHistory object provides methods to load your trades from csv-files exported from various exchanges or apps.

 th = ccgains.TradeHistory()

6. Load all your trades from csv-files

Export your trades from exchanges or apps as comma-separated files and append them to the list of trades managed by the TradeHistory object. All trades will be sorted automatically.

To load from a supported exchange, use the methods named `append_<exchange_name>_csv` found in TradeHistory (see trades.py).

    th.append_poloniex_csv(
'./data/2020/poloniex_depositHistory_2023.csv',
'deposits')
th.append_poloniex_csv(
'./data/2020/poloniex_tradeHistory_2023.csv',
'trades',
condense_trades=True)
th.append_poloniex_csv(
'./data/2020/poloniex_withdrawalHistory_2023.csv',
'withdr')

th.append_binance_csv(
'./data/2020/binance_depositHistory_2023.csv',
'deposits')
th.append_binance_csv(
'./data/2020/binance_tradeHistory_2023.csv',
'deposits')
th.append_binance_csv(
'./data/2020/binance_withdrawalHistory_2023.csv',
'deposits')
th.append_wirex_csv(
'./data/2020/wirex_btc_tradeHistory_2023.csv',
'trades')
th.append_cro_csv(
'./data/2021/cro_tradeHistory_2023.csv',
'trades')

If your exchange is not supported yet, add a new method in trades.py. In this example the methods append_wirex_csv and append_cro_csv are not supported in the original GitHub project.

Next I will show how I did it with append_cro_csv (Crypto.com). First import TPLOC_CRO_TRADES, which is the library that knows how to read the CSV file from the Crypto.com exchange.

from .cro_util import (
TPLOC_CRO_TRADES)
 def append_cro_csv(
self, file_name, which_data='trades', delimiter=',',
skiprows=1, default_timezone=tz.tzutc()
):

wdata = which_data[:5].lower()
if wdata not in ['trade']:
raise ValueError(
'`which_data` must be one of "trades"')

plocs = TPLOC_CRO_TRADES
self.append_csv(
file_name=file_name,
param_locs=plocs,
delimiter=delimiter,
skiprows=skiprows,
default_timezone=default_timezone
)

Now create the cro_util.py file

from decimal import Decimal

def kind_for(csv_line):
if 'Withdraw' in (csv_line[1].strip('" \n\t')) or ('Transfer' in (csv_line[1].strip('" \n\t')) and 'App' in csv_line[1].strip('" \n\t').split("->")[0]):
return 'Withdrawal'
elif 'Deposit' in (csv_line[1].strip('" \n\t')) or 'Reward' in (csv_line[1].strip('" \n\t')) or ('Transfer' in (csv_line[1].strip('" \n\t')) and 'Exchange' in csv_line[1].strip('" \n\t').split("->")[0]):
return 'Deposit'
elif 'Buy' in (csv_line[1].strip('" \n\t')):
return 'Buy'
elif '->' in csv_line[1].strip('" \n\t'):
return 'Sell'
else:
return None

def get_buy_currency(csv_line):
if not '->' in csv_line[1].strip('" \n\t') and (kind_for(csv_line) == 'Buy' or kind_for(csv_line) == 'Deposit' or 'App' in csv_line[1].strip('" \n\t').split("->")[0]):
return csv_line[2].strip('" \n\t')
elif '->' in csv_line[1].strip('" \n\t') and (kind_for(csv_line) == 'Buy' or kind_for(csv_line) == 'Deposit' or 'App' in csv_line[1].strip('" \n\t').split("->")[0]):
return csv_line[2].strip('" \n\t')
else:
return csv_line[1].strip('" \n\t').split("->")[1]

def get_sell_currency(csv_line):
if not '->' in csv_line[1].strip('" \n\t') and (kind_for(csv_line) == 'Sell' or kind_for(csv_line) == 'Withdrawal' or 'Exchange' in csv_line[1].strip('" \n\t').split("->")[0]):
return csv_line[2].strip('" \n\t')
elif '->' in csv_line[1].strip('" \n\t') and (kind_for(csv_line) == 'Sell' or kind_for(csv_line) == 'Withdrawal' or 'Exchange' in csv_line[1].strip('" \n\t').split("->")[0]):
return csv_line[2].strip('" \n\t')
else:
return csv_line[1].strip('" \n\t').split("->")[0]

#Trade parameters in csv from Crypto.com
TPLOC_CRO_TRADES = {
'kind': lambda cols: kind_for(cols),
'dtime': 0,
'buy_currency': lambda cols: get_buy_currency(cols) if kind_for(cols) == 'Buy' or kind_for(cols) == 'Deposit' else cols[4] if cols[4].strip('" \n\t') != '' else '',
'buy_amount': lambda cols: abs(Decimal(cols[3])) if kind_for(cols) == 'Buy' or kind_for(cols) == 'Deposit' else abs(Decimal(cols[5])) if cols[5].strip('" \n\t') != '' else '',
'sell_currency': lambda cols: get_sell_currency(cols) if kind_for(cols) == 'Sell' or kind_for(cols) == 'Withdrawal' else 'EUR',
'sell_amount': lambda cols: abs(Decimal(cols[3])) if kind_for(cols) == 'Sell' or kind_for(cols) == 'Withdrawal' else cols[7],
'fee_currency': -1,
'fee_amount': -1,
'exchange': 'Crypto.com', 'mark': -1,
'comment': lambda cols: cols[1]
}

7. Optionally, fix withdrawal fees

Some exchanges, like Poloniex, does not include withdrawal fees in their exported csv files. This will try to add these missing fees by comparing withdrawn amounts with amounts deposited on other exchanges shortly after withdrawal. Call this only after all transactions from every involved exchange and wallet were imported.

This uses a really simple algorithm, so it is not guaranteed to work in every case, especially if you made withdrawals in tight succession on different exchanges, so please check the output.

th.add_missing_transaction_fees(raise_on_error=False)

8. Optionally, rename currencies

Some currencies have changed ticker symbols since their first listing date (e.g., AntShares (ANS) -> Neo (NEO)). This can lead to situations where all historical pricing data lists the new ticker symbol, but transaction history still lists the old ticker.

This method allows for renaming symbols in the TradeHistory, if any occurrences of the old name/ticker are found.

th.update_ticker_names({'ANS': 'NEO'})

9. Optionally, export all trades for future reference

You can export all imported trades for future reference into a single file, optionally filtered by year.

…either as a comma-separated text file (can be imported into ccgains):

th.export_to_csv('transactions2023.csv', year=2023)

…or as html or pdf file, with the possibility to filter or rename column headers or contents:
(This is an example for a translation into German)

 my_column_names=[
'Art', 'Datum', 'Kaufmenge', 'Verkaufsmenge', u'Gebühren', u'Börse']
transdct = {'Buy': 'Anschaffung',
'BUY': 'Anschaffung',
'Sell': 'Tausch',
'SELL': 'Tausch',
'Purchase': 'Anschaffung',
'Exchange': 'Tausch', 'Disbursement': 'Abhebung',
'Deposit': 'Einzahlung',
'Withdrawal': 'Abhebung',
'Received funds': 'Einzahlung',
'Withdrawn from wallet': 'Abhebung',
'Create offer fee: a5ed7482': u'Börsengebühr',
'Buy BTC' : 'Anschaffung',
'MultiSig deposit: a5ed7482': 'Abhebung',
'MultiSig payout: a5ed7482' : 'Einzahlung'}
th.export_to_pdf('Transactions2021.pdf',
year=2021, drop_columns=['mark', 'comment'],
font_size=12,
caption=u"Handel mit digitalen Währungen %(year)s",
intro=u"<h4>Auflistung aller Transaktionen zwischen "
"%(fromdate)s und %(todate)s:</h4>",
locale="de_DE",
custom_column_names=my_column_names,
custom_formatters={
'Art': lambda x: transdct[x] if x in transdct else x})

10. Now, finally, the calculation is ready to start

If the calculation run for previous years already, we can load the state of the bags here, no need to calculate everything again:

bf.load('./status2022.json')

Or, if the current calculation crashed (e.g. you forgot to add a traded currency in #2 above), the file ‘precrash.json’ will be created automatically. Load it here to continue:

bf.load('./precrash.json')

The following just looks where to start calculating trades, in case you already calculated some and restarted by loading ‘precrash.json’:

last_trade = 0
while (last_trade < len(th.tlist)
and th[last_trade].dtime <= bf._last_date):
last_trade += 1
if last_trade > 0:
logger.info("continuing with trade #%i" % (last_trade + 1))

# Now, the calculation. This goes through your imported list of trades:
for i, trade in enumerate(th.tlist[last_trade:]):
# Most of this is just the log output to the console and to the
# file 'ccgains_<date-time>.log'
# (check out this file for all gory calculation details!):
logger.info('TRADE #%i', i + last_trade + 1)
logger.info(trade)
# This is the important part:
bf.process_trade(trade)
# more logging:
log_bags(bf)
logger.info("Totals: %s", str(bf.totals))
logger.info("Gains (in %s): %s\n" % (bf.currency, str(bf.profit)))

11. Save the state of your holdings for the calculation due next year

bf.save('status2023.json')

12. Create your capital gains report for cryptocurrency trades

The default column names used in the report don’t look very nice: [‘kind’, ‘bag_spent’, ‘currency’, ‘bag_date’, ‘sell_date’, ‘exchange’, ‘short_term’, ‘spent_cost’, ‘proceeds’, ‘profit’], so we rename them:

my_column_names=[
'Type', 'Amount spent', u'Currency', 'Purchase date',
'Sell date', u'Exchange', u'Short term', 'Purchase cost',
'Proceeds', 'Profit']

Here we create the report pdf for capital gains in 2023.

The date_precision=’D’ means we only mention the day of the trade, not the precise time. We also set combine=True, so multiple trades made on the same day and on the same exchange are combined into a single trade on the report:

 my_column_names=[
'Art', 'Verkaufsmenge', u'Währung', 'Erwerbsdatum',
'Verkaufsdatum', u'Börse', u'in\xa0Besitz',
'Anschaffungskosten', u'Verkaufserlös', 'Gewinn']
transdct = {'sale': u'Veräußerung',
'withdrawal fee': u'Börsengebühr',
'deposit fee': u'Börsengebühr',
'exchange fee': u'Börsengebühr'}
convert_short_term=[u'>\xa01\xa0Jahr', u'<\xa01\xa0Jahr']

bf.report.export_report_to_pdf(
'Report2021_de.pdf', year=2023,
date_precision='D', combine=True,
custom_column_names=my_column_names,
custom_formatters={
u'in\xa0Besitz': lambda b: convert_short_term[b],
'Art': lambda x: transdct[x]},
locale="de_DE",
template_file='shortreport_de.html'
)
# If you rather want your report in a spreadsheet, you can export
# to csv:
bf.report.export_short_report_to_csv(
'report_2023.csv', year=2023,
date_precision='D', combine=False,
convert_timezone=True, strip_timezone=True)

13. Optional: Create a detailed report outlining the calculation

The simple capital gains report created above is just a plain listing of all trades and the gains made, enough for the tax report.

A more detailed listing outlining the calculation is also available:

bf.report.export_extended_report_to_pdf(
'Details_2023.pdf', year=2023,
date_precision='S', combine=False,
font_size=10, locale="en_US")

And again, let’s translate this report to German: (Using transdct from above again to translate the payment kind)

    bf.report.export_extended_report_to_pdf(
'Details_2023_de.pdf', year=2023,
date_precision='S', combine=False,
font_size=10, locale="de_DE",
template_file='fullreport_de.html',
payment_kind_translation=transdct)

Now run

python taxReport2023.py

This should generate the pdf files Details_2023.pdf and Details_2023_de.pdf, which are the reports needed by the tax authorities.

For more information on crypto tax report generation or customization to your needs, contact us at lnsolutions.ee

References

GitHub - probstj/ccGains: Python package for calculating cryptocurrency trading profits and…

Python package for calculating cryptocurrency trading profits and creating capital gains reports - probstj/ccGains

github.com

 

How to Install and Automatically Run a Node.js App with Systemd

How to Install and Automatically Run a Node.js App with Systemd

Read more on Medium

Introduction

Systemd is a powerful tool for managing services on Linux systems. By configuring systemd, you can automate the startup, shutdown, and management of your Node.js applications. In this article, we’ll guide you through the process of installing a Node.js app and setting up a systemd configuration to automatically run it.

Prerequisites

  • A Linux-based operating system (e.g., Ubuntu, Debian, CentOS)
  • Node.js installed on your system
  • Basic knowledge of the command line

Steps:

1. Install Node.js: Ensure that Node.js is installed on your system. You can install it using the package manager of your Linux distribution or by downloading and installing it from the official Node.js website. Alternatively you can use nvm to install a specific version of Node.js not included in your Linux distro. For more information read the official github project here: https://github.com/nvm-sh/nvm

2. Install Your Node.js App: Navigate to the directory where you want to install your Node.js app and clone your app’s repository or copy your app’s files into that directory.

git clone https://github.com/your-username/your-node-app.git
cd your-node-app

3. Install Dependencies: Use npm or yarn to install the dependencies of your Node.js app.

npm install
# or
yarn install

4. Test Your App: Before setting up systemd, ensure that your Node.js app runs correctly by manually starting it.

npm start
# or
yarn start

Test your app by accessing it in a web browser or using any other appropriate method.

5. Create a Systemd Service File: Now, let’s create a systemd service file to manage our Node.js app.

sudo nano /etc/systemd/system/your-app-name.service

Replace your-app-name with a descriptive name for your service. Add the following content to the file:

[Unit]
Description=Your Node.js App
After=network.target[Service]
ExecStart=/usr/bin/node /path/to/your/app/app.js
WorkingDirectory=/path/to/your/app
Restart=always
User=your-username
Environment=NODE_ENV=production

[Install]
WantedBy=multi-user.target

Adjust the paths and filenames according to your app’s setup. The ExecStart directive specifies the command to start your app. The User directive specifies the user account under which the service should run.

6. Grant permission to another non-root user to start and manage the systemd service. When specifying the User directive in the systemd service file, you are defining the user account under which the service runs. You can allow other non-root users to control the service by adding them to the relevant systemd user group.

Here are the steps to grant permission to another non-root user:

a. Add the non-root user to the systemd user group:

sudo usermod -aG systemd-journal your-username

b. Replace your-username with the non-root username that you want to grant permission to.

c. Reload the systemd manager configuration:

sudo systemctl daemon-reload

d. Grant the necessary privileges to the user:

sudo systemctl enable your-app-name sudo systemctl start your-app-name

Now, the specified non-root user should have permission to start and manage the service. Note that this grants the user permission to interact with the service through systemd commands. If there are additional file or directory permissions needed for your Yarn app, you should ensure that the user has the appropriate access rights.

Also, keep in mind that adding users to the systemd-journal group allows them to view the journal logs. If your logs are redirected to custom log files using the StandardOutput and StandardError directives in the systemd service file, you may need to adjust the file permissions accordingly.

7. Enable and Start the Service: After creating the systemd service file, reload the systemd daemon and start your service.

sudo systemctl daemon-reload
sudo systemctl start your-app-name
sudo systemctl enable your-app-name

8. Verify the Status: Check the status of your service to ensure that it’s running without any errors.

sudo systemctl status your-app-name

Conclusion

In this article, we’ve demonstrated how to install a Node.js app and automatically run it using systemd on a Linux system. By following these steps, you can ensure that your Node.js app starts automatically on system boot and is managed effectively by systemd. This approach helps in ensuring the reliability and availability of your application.

Sending Emails via AWS SES in Linux: A Step-by-Step Guide

Sending Emails via AWS SES in Linux: A Step-by-Step Guide

Read more on Medium

 

Sending emails via Amazon Simple Email Service (SES) in Linux can be efficiently achieved using the AWS Command Line Interface (CLI). This article outlines the process of setting up and using AWS SES to send emails from a Linux environment. We’ll cover configuring AWS CLI, verifying email addresses, and sending emails programmatically through a Python script.

Prerequisites:

  1. An AWS account.
  2. Linux distribution installed with AWS CLI.

1. Installing and Configuring AWS CLI:

First, ensure you have AWS CLI installed on your Linux system. You can install it via the package manager or download it directly from AWS and follow installation instructions. In Ubuntu based distros the package name is awscli for example.

After installation, configure AWS CLI by running:

aws configure

Enter your AWS access key ID, secret access key, default region name (e.g., eu-west-1), and default output format.

2. Verifying Email Addresses:

Before sending emails via AWS SES, you need to verify the email addresses you’ll be sending from. You can do this either through the AWS Management Console or via AWS CLI.

To verify an email address via AWS CLI:

aws ses verify-email-identity --email-address info@example.com

3. Sending Emails:

Once the email addresses are verified, you can send emails programmatically using AWS SES. Below is a Python script example to send an email:

import boto3

# Create an SES client
ses_client = boto3.client('ses', region_name='eu-west-1') # Replace with your AWS region

# Send the email
response = ses_client.send_email(
Source=This email address is being protected from spambots. You need JavaScript enabled to view it.', # Verified sender email address
Destination={'ToAddresses': [This email address is being protected from spambots. You need JavaScript enabled to view it.']}, # Recipient email address
Message={
'Subject': {'Data': 'Test Email'},
'Body': {'Text': {'Data': 'This is a test email sent from Amazon SES.'}}
}
)
print("Email sent. Message ID:", response['MessageId'])

Ensure you replace This email address is being protected from spambots. You need JavaScript enabled to view it.' with your verified sender email address and This email address is being protected from spambots. You need JavaScript enabled to view it.' with the recipient's email address.

4. Troubleshooting:

  • If you encounter errors during email sending, ensure your AWS IAM user has the necessary permissions to send emails via SES.
  • Check AWS SES sending limits and ensure you’re within them.
  • Verify AWS CLI configurations and ensure they are correct.

Conclusion:

With AWS SES and AWS CLI, sending emails from a Linux environment becomes straightforward and efficient. By following the steps outlined in this guide, you can quickly set up and start sending emails via AWS SES, enabling reliable email delivery for your applications and services.

 

Read more on Medium

recent posts
  • Talinn, Estonia
  • support@lnsolutions.ee
About
We support your software projects and solutions. We specialize in Java Backend, Web development, BPM Workflow design and Bitcoin payment solutions.
Subscribe