Improving blogger experience
Almost forgot to mention how I improved blogger experience.
I needed a TOC (Table of contents), such that a reader can quickly jump to the section he's interested in.
Blogger does NOT offer this out of the gate.
So I dug, dug deeper and I found 2 solutions which actually create a TOC. But neither was good enough.
One only fetched only level of <H> tags, the other one fetched all but got lost as soon as there's more complicated HTML code between the <H> tags.
So I used some parts of both and improved upon it: You paste this in the HTML BEFORE everything:
<div id="myToc">
</div>
<hr />
<div id="myContents">
And you paste this AFTER everything
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script type="text/javascript">
countChapters = $("#myContents > h1, #myContents > h2").length
chapters = $("#myContents > h1, #myContents > h2")
flagLevel = false
var toc = ''
for (i = 0; i < countChapters; i++) {
chapter = chapters[i]
chapterTitle = chapters[i].textContent;
chapters[i].setAttribute("id", "chapter" + i);
if ('H2' == chapter.tagName) {
if (flagLevel) {
toc += "<li><a href='#chapter" + i + "'>" + chapterTitle + "</a></li>";
} else {
toc += "<ul><li><a href='#chapter" + i + "'>" + chapterTitle + "</a></li>";
}
flagLevel = true
} else {
if (flagLevel) {
toc += "</ul>"
flagLevel = false
}
toc += "<li><a href='#chapter" + i + "'>" + chapterTitle + "</a></li>";
}
}
document.getElementById("myToc").innerHTML = toc;
</script>
Then it will parse all H1/H2 tags and create a TOC at the top.
By fine-tuning the flagLevel thing you could even have it parse H3 tags.
Who's interested: let me know and I enhance the code for you!
This technique can actually be used on every HTML page
Install web server
This is an easy one on macOS, thanks to this
I had home-brew (brew) already installed. So I just needed to follow a few steps.
Apple delivers a built-in apache version, yet the default configuration is a bit not so straight forward. With the approach from above I had the web server running in a few minutes, pointing to my directories.
Make (bot) results accessible from afar
Here I needed to think/play a little bit. Years ago there were DynDNS services, for free. Nowadays they charge for their service. What they basically do is provide a hostname which is mapped to your dynamic public IP from your home router.
So I tried to build my own "rough" DynDNS solution.
I started with https://www.whatismyip.com and https://github.com/cheeriojs/cheerio a tool which lets you parse HTML on the server (and not on the client side). Here's a nice tutorial https://dev.to/diass_le/tutorial-web-scraping-with-nodejs-and-cheerio-2jbh.
On the way I learned that jQuery is a thing of the past and one should use React or Vue now, so I did a 30 minutes primer into Vue. Nice, but couldn't understand why/where it's better than jQuery. With Vue you have to "wrap" your tags into "v-" tags in order for it to work. Works well for applications, but it's a pain if you want to do very small things, like things I did with my visual tool to analyse charts.
Anyway: I got cheerio to read the contents of whatismyip.com and it took me almost 2 hours to figure, that they would not return my IP, as they realise the request is not from a user, but from a machine.
But there's other tools, like this one: http://ip-api.com/json Very straight forward. Returns my IP and that's all I need.
Now what to do with my IP? Store it on google drive? Do they have an API? After a few minutes tinkering I figured: I will use iCloud. Create a document there with a link to my "own" web server. And that's what I did.
Please don't judge me on this code: it's a prototype and does what it needs to do:
module.paths.unshift('/Users/michael/NodeJS');
const axios = require("axios").default;
url = 'http://ip-api.com/json'
const fetchHtml = async url => {
try {
const { data } = await axios.get(url);
return data;
} catch {
console.error(
`ERROR: An error occurred while trying to fetch the URL: ${url}`
);
}
};
async function doIt() {
var result = await fetchHtml('http://ip-api.com/json')
var path = '/Users/michael/Library/Mobile Documents/com~apple~CloudDocs/HTML/'
var ip = result.query
var fs = require('fs');
fs.writeFileSync(path + 'myServer.html', `</br></br></br></br></br><H1><a href="http://${ip}/heartBeats">myServer</a></H1>`)
fs.writeFileSync(path + 'myTool.html', `</br></br></br></br></br><H1><a href="http://${ip}:3000">myTool</a></H1>`)
}
doIt()
Important here is the first line. This is required as cron, which will run the script, does not know where my node modules are installed
If fetches the IP address from my router (via this URL), stores it in a link in a HTML file in a folder in iCloud where I can access it from anywhere in the world.
I only needed to to some port forwarding in my router. Port 80/3000 need to forward to my machine.
Interestingly, once I set it up, he, the router software, told me: my Mac will from now on have a static address. How nice!
Final piece: I created a a soft link in my web documents folder to the folder where I store my bot heart beats. So now I can call my server like:
http://my.ip.address/heartBeats
and it will show me the directory listing of my heartBeats so I can check from everywhere if my bots are running.
Archives
bookmark_borderCreate a tool to visually analyse data
Problem
There are so many prices, the oscillation is gargantic.
I want to be able to detect areas where my bot could have succeeded, such that I can analyse patterns which would let my bot make the right decision for a trade.
So I first need to “detect” such areas, and then I need to find out “where” in my data points those areas are.
Approach
First I tried Numbers, an Excel-like spreadsheet application which comes with every Mac. While for a small amount of numbers I created the perfect solution, for large amount of numbers to analyse the process slows down, drastically.
So I had to rethink. Why not use the data from the database directly?
That basically meant I need a tool to display the chart from data from the database, and I needed a simple responsive web server.
There’s lots of such tools. I decided to go with Google tools, check out the Guides and the References part.
I did find the perfect tool, yet it was very poorly documented and thus I had to start low. Remember “De- complexify problems”
- do everything manually, use manual (hard coded data)
notice the “true” parameter, it says: no column headers
now I had data, but no column titles
so I changed my result from db to include column headers
but then I had to change that parameter to false
- now the table was too long and didn’t scroll
I figured after long looking and testing, there’s options you can pass to the view
the “page” parameter does it, the result looks like this
2 things were missing:
I need to be able to page data, like fetch a 1000, then fetch another thousand
I wanted to “interact” with the chart
So for the “paging” mechanism I built to buttons which would fetch the “next” or the “prev” page of data, page being a defined number of rows
the two buttons
the code being called
the code on the server being executed, see the “next” / “prev” actions, how this influences the paging (page++ or page–) from the db, see the limit clause
But how about “interaction” with the chart? Turns out, you can select points by default. But I wanted more, guess what, there’s options = {} too you can supply to the chart view. You can supply options to zoom in, to reset, for tick marks on the scale and you can turn on multiple selections.
So far so good, but I wanted to do something with that info. As a first step, I intercepted the clicked points, made sure only 2 can be selected, and I changed the tooltip, which per default was info from the table. Now I wanted the tooltip to show percentage change from the first point clicked.
After a lot of testing I figured it out.
positive change in price
Turns out, that for HTML tooltips I needed to enable in 2 areas the HTML option. I figured this out by accident. You can see this in the code above which shows the options for the chart and here:
And this is the full prototype
and here it is in action https://youtu.be/DFV3qq-4oIY
Intercept key presses
Problem
Approach
Adjusting my line chart
How to zoom-out of a chart
Adding a slider to move between pages of data
bookmark_borderSchedule programs on macOS

Problem
Approach
* * * * * /Users/michael/Python/script.py>>/Users/Michael/Python/output.log
So I expected to see a an output.log file but I didn’t.
Fortunately, this was new to me, cron sent me a mail on the machine.
There it said: Operation not permitted.
Here the article (see link above) helped. I needed to add cron to the Applications which have Full Disk Access.
Now it worked like a charm.
Next step was to try and have cron run my python script which would fetch every minute data from Bitstamp.
This failed miserably. And it took me hours to figure out what the reason was.
The error message, as before I received emails with the error, said:
ImportError: No module named requests
When I ran the same script manually it worked like a charm. So it looked like there was a problem with the installation of the module requests, at least cron didn’t seem to be able to find that module.
I tried installing in another directory, failed.
I tried uninstalling, reinstalling, it installed in the same directory, failed.
Then I found help via Google search. You can actually prepend the PYTHONPATH to the cron tab command, like this:
* * * * * PYTHONPATH=/usr/local/lib/python3.9/site-package /Users/michael/Python/script.py
Ever since the script runs ever minute and does it’s job: retrieve the trades from the last minute from Bitstamp.
Make sure output is written immediately to log files
Simplify scheduled jobs
HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM URG STOP TSTP CONT CHLD TTIN TTOU IO XCPU XFSZ VTALRM PROF WINCH INFO USR1 USR2
#!/bin/zsh
process=$(ps -ax|grep -v grep|grep “Cellar.*temp_socket.py”)
pid=${process:0:5}
kill -s int $pid
echo “temp_socket.py terminated”
Delete lock files upon machine boot/reboot
Giving launchd / launchctl another try
-rwxr–r–@ 1 root wheel 599 Feb 2 19:25 deleteLockFiles.plist*
So you need to put a PLIST (not plst, my first attempt was always with plst which did never work) file into the ~/Library/LaunchAgents directory.
Content of PLIST file:
<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE plist PUBLIC “-//Apple//DTD PLIST 1.0//EN” “http://www.apple.com/DTDs/PropertyList-1.0.dtd”>
<plist version=”1.0″>
<dict>
<key>Label</key>
<string>com.yourName.deleteLckFiles</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/your/script/deleteLckFiles.sh</string>
</array>
<key>KeepAlive</key>
<false/>
<key>StandardErrorPath</key>
<string>/path/to/your/script/launchd_error.txt</string>
<key>StandardOutPath</key>
<string>/path/to/your/script/launchd_log.txt</string>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
sudo launchctl load deleteLckFiles.plist
unload the plist file:
sudo launchctl load deleteLckFiles.plist
- create a simply script just echoing “Hello world”, have the script and the output file in your home directory, just to make sure.
- then you may move the script to the directory where it finally will be called, if this does not work, you may need to give in the Security System Preference Panel Full Disk Access to the shell
- and increase the complexity
- I gave up trying to run my bots via launchd, couldn’t figure out how to define the paths so the python scripts work properly, but my shell scripts run just about fine via launchd / launchctl
bookmark_borderCoding
Creating a responsive web server
Going back to Python
Bot simulation
Profiling code
613716 function calls in 0.530 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.529 0.529 <string>:1(<module>)
1 0.000 0.000 0.000 0.000 classBitstampClient.py:18(__init__)
9999 0.260 0.000 0.523 0.000 classLogic.py:114(logic)
127159 0.037 0.000 0.037 0.000 classLogic.py:163(readyToSell)
391749 0.185 0.000 0.185 0.000 classLogic.py:176(readyToBuy)
7318 0.011 0.000 0.012 0.000 classLogic.py:273(simulateOrder)
9999 0.003 0.000 0.003 0.000 classLogic.py:303(getBalances)
9999 0.018 0.000 0.024 0.000 classLogic.py:347(updateStats)
1 0.000 0.000 0.000 0.000 classLogic.py:88(initLogic)
10027 0.002 0.000 0.002 0.000 classLogicPlayGround.py:102(logInfo)
1 0.000 0.000 0.000 0.000 classLogicPlayGround.py:105(__del__)
9999 0.001 0.000 0.001 0.000 classLogicPlayGround.py:108(updateHeartBeat)
1 0.000 0.000 0.000 0.000 classLogicPlayGround.py:16(__init__)
7370 0.001 0.000 0.001 0.000 classLogicPlayGround.py:99(logSimulation)
1 0.006 0.006 0.529 0.529 fetchAllAtOnce.py:40(main)
1 0.000 0.000 0.530 0.530 {built-in method builtins.exec}
9999 0.001 0.000 0.001 0.000 {built-in method builtins.len}
3 0.000 0.000 0.000 0.000 {built-in method builtins.print}
2 0.000 0.000 0.000 0.000 {built-in method now}
10051 0.002 0.000 0.002 0.000 {method ‘append’ of ‘list’ objects}
1 0.000 0.000 0.000 0.000 {method ‘commit’ of ‘sqlite3.Connection’ objects}
1 0.000 0.000 0.000 0.000 {method ‘disable’ of ‘_lsprof.Profiler’ objects}
53 0.000 0.000 0.000 0.000 {method ‘format’ of ‘str’ objects}
9979 0.003 0.000 0.003 0.000 {method ‘pop’ of ‘list’ objects}
1 0.000 0.000 0.000 0.000 {method ‘strftime’ of ‘datetime.date’ objects}
Sending messages to running code
#!/bin/zsh
# find the pid from the particular bot I want to stop trading
pid=`pgrep -f “[C]ellar.*websocket_temp.py”`
# send signal usr1
kill -s usr1 $pid
echo “websocket_temp.py trading stopped”
In the python code for the websocket you simply add these lines before going into .run_forever
bookmark_borderStop Loss Trading explained

Stop Loss Trading explained
Even if the price only goes back to 90 cents you’ll still make a small profit.
bookmark_borderTrading Bot on macOS Big Sur: my experiences during the project
Most important Tip
Summary
bookmark_borderQUINDIP: document your code properly in LotusScript

bookmark_borderQUINDIP: intercept wrong/inexistent documentId

Hi
long time no post… but alas.. finally something I really like:
Have you ever had the situation, that you have a xPage which is workflow enabled, and you have (maybe) multiple servers? Some users might even use the LN client front-end on another server. The workflow sends out a mail telling the approver to approve.
Boom… the document did not yet replicate to the target server…
you do not want either of this message displayed:
what to do?
hmm.. I first tried with this, sorry, was not happy with it…
thing is: we use the same xpView for production and for archive, only the data source changes in the background. This will open the documents from the archive with such an URL:
http://server/db.nsf/%24%24OpenDominoDocument.xsp?databaseName=server!!otherDb.nsf&documentId=6496BA354B37544CC1257C680059C04B&action=openDocument
I could not get this to work..
So I had to figure out another way to intercept a “wrong” documentId.
Solution: use a phaselistener!!! (how to implement see here, kudos to Sven)
Now I only need to intercept the first phase in the JSF lifecycle, test if the supplied documentId exists and redirect:
event.getFacesContext().getExternalContext().redirect(“xpDocumentNotFound.xsp”);
event.getFacesContext().responseComplete();
I do some checks and then call redirect and responseComplete which executes immediately and does not continue with the other JSF phases
bookmark_borderQUINDIP: set JAVA Compiler target in notes.ini
Literally EVERY time I created a Java agent or library the compiler target was set to 1.2 even though I have been using LN 7 or later…
I always had to find the noteId and use a tool to manually change the source and target to 1.6.
here’s how you can change that behaviour:
http://www-10.lotus.com/ldd/ddwiki.nsf/dx/07152009034956PMMSTR75.htm
enjoy
AND make sure you add an EMPTY at the end in case JavaCompilerTarget=1.6 is your last line! otherwise this setting will not be used!
bookmark_borderhmmmmmm.. that does not really improve my trust in “stable xPages” ;-)
