OK feature complete and running on my Linode at this point. The repo name has been updated, the new link is:
Just to summarize again, there are two modes:
- broadcast - every trade disclosure is broadcast to a public Telegram channel
- personalized - talk to the bot and tell it what specific trades you want to know about
It may be hard to judge because of the temporal aspect of this kind of service but nevertheless was fun. I have updated the channel to the āproductionā one, but the old one can still be viewed:
And then talking to the bot itself: Telegram: Contact @stonkcritter_bot
Features
I got it so that you have personal subscriptions by talking to the bot directly. It uses the golang badger DB in the backend to store rep/senator names and subscriptions.
I needed to store the rep senator names, to help make it easier to subscribe. For instance, you can actually type /follow pelosi
since sheās the only one in there.
It wonāt let you subscribe to trade disclosures to unknown reps, since that would be useless. So you can use /critter <name>
to find the proper name.
When you do a list it shows you them, and has a handy but ugly unfollow link:
The subscriptions are quite simple. Just a chat ID and topic. The topic is prefixed with a $
to indicate a ticker. When we find a matching topic, we simply ping the given chatID.
I donāt store the disclosures because thereās so many of them, and theyāre not really needed beyond the specific timeframe in which the information is useful. Plus I could see scope creep, and that would creep on the stock watcher websites who do a far better job than I could.
I didnāt hit my stretch goal of adding types of trades (e.g. stocks or bonds). Most of the code is there but it is not tested (I head on week long 4WD trip tomorrow)
Problems
The data source was tricky to work with, it uses different date formats for some things, and the payload is a bit different between the senate and house data sources.
I also had to find, say, every possibility for the asset type field (and how often it occurs). Thanks to gron
and the unix philosophy for enabling that.
$ gron all_transations.json|grep asset_type | cut -d'=' -f2 |sort | uniq -c | sort -hk 1
3 "Cryptocurrency";
20 "Commodities/Futures Contract";
97 "Non-Public Stock";
203 "Stock Option";
237 "Corporate Bond";
361 "Other Securities";
386 "Municipal Security";
465 "PDF Disclosed Filing";
6522 "Stock";
Another problem I had was that if you were subbed to Pelosi and AAPL and she traded AAPL you would get messaged twice for the same one. So I made these little deduplicator closures to check you donāt get the same message twice:
// deduper is a quick and dirty deduplicator, returns two closures, one to test
// if the given pair is locked, and one to mark the given pair as locked
func deduper() (func(int64, string) bool, func(int64, string)) {
locks := []string{}
shouldsend := func(chatID int64, msg string) bool {
msghash := base64.RawStdEncoding.EncodeToString([]byte(msg))
lock := strconv.Itoa(int(chatID)) + "_" + msghash
for _, l := range locks {
if l == lock {
return false
}
}
return true
}
marksent := func(chatID int64, msg string) {
msghash := base64.RawStdEncoding.EncodeToString([]byte(msg))
locks = append(locks, strconv.Itoa(int(chatID))+"_"+msghash)
}
return shouldsend, marksent
}
Another one was rate limiting by the Telegram API and I might still be a little conservative with the 1 second delay between disclosure iteration, but I also used a rate limiter from the golang package. I could problem get rid of that 1 second delay.
import "golang.org/x/time/rate"
bot.dmLimit = *rate.NewLimiter(rate.Every(time.Minute/59), 1
// use like this
bot.dmLimit.Wait(context.Background())
Third party software
This is the most relevant libs that I pulled in: