Categories
blandet

Hent valutadata fra Nordnet med Python

Nordnet har f√•et en ny hjemmeside med en markedsoversigt, hvor man blandt andet kan finde valutakurser. Dem ville jeg gerne have fat i til et Excelark ūüôā

Jeg trykkede F12 i min browser for at unders√łge, hvad der sker, n√•r jeg klikker “Valutaer” p√• siden, og hvad der kr√¶vedes af cookies og klient-identifikation for at f√• data tilbage. (Du kan l√¶se mere om metoden i mange af mine andre programmeringsindl√¶g.)

Det endte med dette program, der genererer en CSV-fil med den seneste valutakurs for en række almindelige valuter fra Nordnet:

# -*- coding: utf-8 -*-
# Author: Morten Helmstedt. E-mail: helmstedt@gmail.com
""" This program gets currency data from Nordnet.
Handy for exporting to Excel with as few manual steps as possible """
import requests 

# Creates a dictionary to use for cookies	
cookies = {}

# Sets NEXT cookie
url = 'https://www.nordnet.dk/markedet'
r = requests.get(url)
cookies['NEXT'] = r.cookies['NEXT']

# Requests currency data
headers = {'client-id': 'NEXT'}

# Gets currency data
url = 'https://www.nordnet.dk/api/2/instrument_search/query/indicator?entity_type=CURRENCY&apply_filters=market_overview_group%3DDK_GLOBAL_MO'
r = requests.get(url, cookies=cookies, headers=headers)
currencies = r.json()

# Generate CSV output of last value by looping through currencies
output = "navn;senest\n"
for currency in currencies['results']:
	name = currency['instrument_info']['name']
	price = str(currency['price_info']['last']['price'])	
	price = price.replace(".",",")
	output += name + ";" + price + "\n"

# Write CSV output to file #
with open("currency.csv", "w", encoding='utf8') as fout:
	fout.write(output)
Categories
blandet

Sådan trækker du dine transaktioner ud fra Nordnet med Python

OPDATERING: Nordnet er ude i en ny version, som g√łr at man forel√łbig er n√łdt til at √¶ndre URLs i programmet til “classic.nordnet.dk” for at bruge den gamle version. P√• et tidspunkt virker det nok heller ikke l√¶ngere. Jeg har opdateret koden neden for med den korrekte url.

OPDATERING: Find en opdateret udgave af programmet, der virker med det nye Nordnet, her.

Jeg kan godt lide at bruge Excel til at holde √łje med min √łkonomi, s√• jeg har et ark med en pivottabel, som jeg bruger til at f√• overblik over min portef√łlje hos Nordnet. Nordnet har en funktion til at tr√¶kke en CSV-fil med en oversigt over mine transaktioner, men det er lidt besv√¶rligt at skulle a) logge ind p√• Nordnet for derefter b) at g√• ind p√• hver enkelt depot og c) tr√¶kke en ny oversigt og copy/paste hver gang, der fx udbetales udbytte.

Her klikker du hos Nordnet for at generere en CSV-fil, du kan bruge til Excel

Derfor tænkte jeg: Kan jeg automatisere dette udtræk, sådan jeg altid har opdateret data i mit Excelark? Ja, det kan jeg. Med Python. Her fortæller jeg om hvordan og deler min kode.

Hvordan snakker man http med Nordnet?

Det f√łrste jeg gjorde, var at unders√łge hvad der egentlig sker, n√•r jeg beder Nordnet om en transaktionsfil. Det g√łr jeg i Chrome ved at trykke F12, v√¶lge Network og unders√łge hvad min browser sender af sted for at f√• en CSV-fil tilbage. Jeg kan se, at der ryger en cookie af sted og nogle parametre, der handler om bl.a. sortering og periode for de transaktioner, jeg vil have ud:

I Python bruger jeg modulet Requests til at snakke med Nordnet og fors√łger at konstruere noget der ligner det, min browser smider af sted. Efter at have pr√łvet mig frem, finder jeg ud af, at det er den cookie, der hedder NOW, der er afg√łrende for, at modtage noget fra Nordnet. Jeg laver en cookie-ordbog, der forel√łbig indeholder min NOW-v√¶rdi fra Chrome:

cookies = {'NOW': '63261fc324153bd1632006105c5b4444d97fc72a'}

Min foresp√łrgsel, der giver mig data tilbage, ser s√•dan ud:

payload = {
'year': 'all',
'month': 'all',
'trtyp': 'all',
'vp': 'all',
'curr': 'all',
'sorteringsordning': 'fallande',
'sortera': 'datum',
'startperiod': startdate,
'endperiod': enddate
}
url = "https://www.nordnet.dk/mux/laddaner/transaktionsfil.html"
data = requests.get(url, params=payload, cookies=cookies)
result = data.text

Så langt, så godt.

Men da jeg logger ud af Nordnet, virker min foresp√łrgsel ikke l√¶ngere. For at f√• en gyldig cookie, er jeg alts√• n√łdt til at logge ind p√• Nordnet.

Tilbage i Chrome kigger jeg på, hvad der sendes af sted, og modtages, ved indlogningsproceduren. Indlogningsproceduren foregår i 3 trin:

  1. På https://www.nordnet.dk/mux/login/start.html?cmpi=start-loggain&state=signin sættes to cookies: LOL og TUX-COOKIE.
  2. N√•r brugeren har indtastet brugernavn og password, foresp√łrges https://www.nordnet.dk/api/2/login/anonymous og returnerer en cookie: NOW.
  3. Til sidst sendes de 3 cookie-værdier sammen med brugernavn og password til https://www.nordnet.dk/api/2/authentication/basic/login. Der returneres en ny NOW-værdi og en anden cookie, der hedder xrsf.

Login på siden ser sådan her ud:

# LOGIN TO NORDNET #
	
# First part of cookie setting prior to login
url = 'https://www.nordnet.dk/mux/login/start.html?cmpi=start-loggain&state=signin'
r = requests.get(url)

cookies['LOL'] = r.cookies['LOL']
cookies['TUX-COOKIE'] = r.cookies['TUX-COOKIE']

# Second part of cookie setting prior to login
url = 'https://www.nordnet.dk/api/2/login/anonymous'
r = requests.post(url, cookies=cookies)
cookies['NOW'] = r.cookies['NOW']

# Actual login that gets us cookies required for primary account extraction
url = "https://www.nordnet.dk/api/2/authentication/basic/login"

r = requests.post(url,cookies=cookies, data = {'username': user, 'password': password})

cookies['NOW'] = r.cookies['NOW']
cookies['xsrf'] = r.cookies['xsrf']

N√•r denne procedure er gennemf√łrt, kan jeg med den genererede NOW-v√¶rdi tr√¶kke transaktioner ud af mit prim√¶re depot (det jeg oprettede f√łrst, da jeg fik en Nordnet-konto).

For at tr√¶kke transaktioner ud fra andre depoter, unders√łger jeg hvad der sker, n√•r jeg v√¶lger et andet depot i Nordnet. Det sker ved at foresp√łrge https://www.nordnet.dk/mux/ajax/session/bytdepa.html med mine gemte cookies og v√¶rdien fra den cookie, der hedder xrsf i headeren. Tilbage f√•r jeg en ny NOW-v√¶rdi, som jeg kan bruge til at hente transaktioner p√• det andet depot, og en ny xrsf-v√¶rdi, som jeg kan bruge, hvis jeg har endnu flere depoter, jeg f√•r brug for at skifte til:

		# Switch to secondary account and set new cookies
		url = 'https://www.nordnet.dk/mux/ajax/session/bytdepa.html'
		headers = {'X-XSRF-TOKEN': cookies['xsrf']}	
		
		r = requests.post(url,cookies=cookies, headers=headers, data = {'portfolio': item['id']})

		cookies['NOW'] = r.cookies['NOW']
		cookies['xsrf'] = r.cookies['xsrf']

Til sidst skal jeg finde en fornuftig struktur for mit program, finde ud af hvordan jeg f√•r lavet en god struktur i min CSV-fil (nogle gange returnerer Nordnet en CSV-fil med en kolonne for meget). Og s√• har jeg brug for en mulighed for at tilf√łje manuelle linjer til min CSV-fil (fordi jeg gerne vil have historisk data med fra et gammelt depot hos en anden bank).

Det færdige program i Python

Her er det færdige program. Du er velkommen til at bruge det, videreudvikle, osv.

<pre class="wp-block-syntaxhighlighter-code"><p># -*- coding: utf-8 -*-
# Author: Morten Helmstedt. E-mail: helmstedt@gmail.com
&quot;&quot;&quot; This program logs into a Nordnet account and extracts transactions as a csv file.
Handy for exporting to Excel with as few manual steps as possible &quot;&quot;&quot;

import requests 
from datetime import datetime
from datetime import date
import os

# USER ACCOUNT, PORTFOLIO AND PERIOD DATA. SHOULD BE EDITED FOR YOUR NEEDS #

# Nordnet user account credentials and name of primary portfolio (first one listed in Nordnet)
user = ''
password = ''
primaryportfolioname = &quot;Frie midler&quot;

# Names and portfolio ids for all any all secondary portfolios. The id is listed in 
# Nordnet when selecting a portfolio. If no secondary portfolios the variable
# secondaryportfolioexists should be set to False.
secondaryportfolioexists = True
secondaryportfolios = [
{'name': 'Ratepension', 'id': ''},
]

# Start date (start of period for transactions) and date today used for extraction of transactions
startdate = '01.01.2013'
today = date.today()
enddate = datetime.strftime(today, '%d.%m.%Y')


# Manual date lines. These can be used if you have portfolios elsewhere that you would
# like to add manually to the data set. If no manual data the variable manualdataexists
# should be set to False
manualdataexists = True
manualdata = &quot;&quot;&quot;
Id;Bogf√łringsdag;Handelsdag;Val√łrdag;Transaktionstype;V√¶rdipapirer;Instrumenttyp;ISIN;Antal;Kurs;Rente;Afgifter;Bel√łb;Valuta;Indk√łbsv√¶rdi;Resultat;Totalt antal;Saldo;Vekslingskurs;Transaktionstekst;Makuleringsdato;Verifikations-/Notanummer;Depot
;30-09-2013;30-09-2013;30-09-2013;K√ėBT;Obligationer 3,5%;Obligationer;;72000;;;;-69.891,54;DKK;;;;;;;;;Frie midler
&quot;&quot;&quot;


# CREATE OUTPUT FOLDER AND VARIABLES FOR LATER USE. #

# Checking that we have an output folder to save our csv file
if not os.path.exists(&quot;./output&quot;):
	os.makedirs(&quot;./output&quot;)

# Creates a dictionary to use with cookies	
cookies = {}

# A variable to store transactions before saving to csv
transactions = &quot;&quot;

# Payload for transaction requests
payload = {
'year': 'all',
'month': 'all',
'trtyp': 'all',
'vp': 'all',
'curr': 'all',
'sorteringsordning': 'fallande',
'sortera': 'datum',
'startperiod': startdate,
'endperiod': enddate
}

# LOGIN TO NORDNET #
	
# First part of cookie setting prior to login
url = 'https://classic.nordnet.dk/mux/login/start.html?cmpi=start-loggain&amp;amp;state=signin'
r = requests.get(url)

cookies['LOL'] = r.cookies['LOL']
cookies['TUX-COOKIE'] = r.cookies['TUX-COOKIE']

# Second part of cookie setting prior to login
url = 'https://classic.nordnet.dk/api/2/login/anonymous'
r = requests.post(url, cookies=cookies)
cookies['NOW'] = r.cookies['NOW']

# Actual login that gets us cookies required for primary account extraction
url = &quot;https://classic.nordnet.dk/api/2/authentication/basic/login&quot;

r = requests.post(url,cookies=cookies, data = {'username': user, 'password': password})

cookies['NOW'] = r.cookies['NOW']
cookies['xsrf'] = r.cookies['xsrf']


# GET PRIMARY ACCOUNT TRANSACTION DATA #

# Get CSV for primary account
url = &quot;https://classic.nordnet.dk/mux/laddaner/transaktionsfil.html&quot;
data = requests.get(url, params=payload, cookies=cookies)
result = data.text
result = result.splitlines()
firstline = 0

for line in result:
	if line and firstline == 0:
		transactions += line + ';' + &quot;Depot&quot; + &quot;\n&quot;
		firstline = 1
	elif line:
		# Sometimes Nordnet inserts one semicolon too many in the file. This removes the additional semicolon
		if line.count(';') == 22:
			position = line.rfind(';')
			line = line [:position] + line[position+1:]
		transactions += line + ';' + primaryportfolioname + &quot;\n&quot;

		
# GET TRANSACTION DATA FOR ALL/ANY SECONDARY ACCOUNTS #

if secondaryportfolioexists == True:
	for item in secondaryportfolios:
		# Switch to secondary account and set new cookies
		url = 'https://classic.nordnet.dk/mux/ajax/session/bytdepa.html'
		headers = {'X-XSRF-TOKEN': cookies['xsrf']}	
		
		r = requests.post(url,cookies=cookies, headers=headers, data = {'portfolio': item['id']})

		cookies['NOW'] = r.cookies['NOW']
		cookies['xsrf'] = r.cookies['xsrf']

		# Get CSV for secondary account
		url = &quot;https://classic.nordnet.dk/mux/laddaner/transaktionsfil.html&quot;
		data = requests.get(url, params=payload, cookies=cookies)
		result = data.text
		result = result.split(&quot;\n&quot;,1)[1]
		result = result.splitlines()

		for line in result:
			if line:
				# Sometimes Nordnet inserts one semicolon too many in the file. This removes the additional semicolon
				if line.count(';') == 22:
					position = line.rfind(';')
					line = line [:position] + line[position+1:]
				transactions += line + ';' + item['name'] + &quot;\n&quot;


if manualdataexists == True:
	manualdata = manualdata.split(&quot;\n&quot;,2)[2]
	transactions += manualdata				


# WRITE CSV OUTPUT TO FILE #
		
with open(&quot;./output/trans.csv&quot;, &quot;w&quot;, encoding='utf8') as fout:
	fout.write(transactions)</p></pre>