Momentum Scanner & Scanning Related Functions

Alright so as I’ve mentioned before, one of the most important base functions of Mimir is going to be a momentum scanner. While really beneficial for day trading, it’s programmatic purpose is to allow the system to “know” what is moving so that subprocesses can search for trading opportunities or generating related signals for tickers with active positions.

The Momentum Scanner is up and running in the Dashboard

It’s somewhat simple:

const currentPrice = newQuote.mark;
const previousPrice = oldQuote.mark;
const priceChange = ((currentPrice - previousPrice) / ((currentPrice + previousPrice) / 2)) * 100;
const currentVolume = newQuote.dailyVolume - oldQuote.dailyVolume;
const averageVolumePerMinute = (oldQuote.vol1DayAvg / 390) * 2; // Assuming 390 minutes in a trading day
const percentageVolume = currentVolume / averageVolumePerMinute
const momentumScore = priceChange * percentageVolume

The current iteration compares quotes from second to second and calculates a normalized price change to attempt to reduce the overrepresentation of smaller priced stocks due to their higher volatility in priceChange. The quotes have a cumulative volume so we find the difference between quotes in currentVolume and then grab the average minute volume in averageVolumePerMinute (current iteration uses 2 minute volume for no real reason hence the "*2"). We grab the ratio of current volume to average minute volume and for the momentum score we multiply priceChange by that ratio.

After this, there is a more complex system that handles “watching” tickers. The flow is:

  1. If a ticker has a volume ratio of > than 0.1 (of 2 minute vol) it’s sent to a process that watches it for five seconds. If it triggers the criteria again within that timeframe, it’s added to the momentum scanner and is “full watched”. This part is designed to filter out barcoding stocks as they look like they get crazy amounts of volume in the rare candles that they do have. So we’re looking for sustained volume.
  2. Full watched tickers are followed for 30 seconds. Every time criteria is tripped again (initial ratio check and the sustained volume check) the timer is reset. If a ticker doesn’t have movement for 30 seconds, it’s removed from the list.

Right now, as ticker are in the list, their quotes are updated live. The initial priceChange, percentageVolume and momentumScore figures are frozen currently for the sake of development.

Unlike a lot of scanners, this functions in AH and PM as well. It does so by shifting gears and looking for a lot lower volume ratio (0.01 instead of 0.1) and the “full watch” process follows for 30 minutes instead of 30 seconds.

What’s working

The scanner itself is working pretty well. It’s routinely finding stocks that are moving on volume and it’s following them well.

What’s not

The development needed is mainly filtering criteria. momentumScore itself isn’t very reliable as stocks that have surges of volume but don’t go anywhere price wise are pretty popular. So I’m not sure if it needs a better calculation or if we need to find and set a limit on the priceChange figure.

The sustained volume function likely needs slight adjustment in its watch timeframe (5s) and number of triggers (1).

Lastly the full watch function likely needs more thinking out. As it exists currently it’s a very “momentary” type of deal with no real features that are capable of tracking the total amount of times a ticker triggers the function in a day. Would be useful for a list of things that are unusually active on a broader scale.

Planned additions

While it’s scanning quotes there are other checks that we could be running. The system uses a set of fundamental data that includes things like 52 Week High, Average Volume across multiple timeframes, etc. Additional checks for these things can easily be added to the main function. The data it has is:

"TSLA": {
  "fundamental": {
    "symbol": "TSLA",
    "high52": 1243.49,
    "low52": 539.49,
    "dividendAmount": 0,
    "dividendYield": 0,
    "dividendDate": " ",
    "peRatio": 329.3561,
    "pegRatio": 0.657245,
    "pbRatio": 37.66791,
    "prRatio": 21.75757,
    "pcfRatio": 162.3604,
    "grossMarginTTM": 23.10664,
    "grossMarginMRQ": 26.60464,
    "netProfitMarginTTM": 7.67802,
    "netProfitMarginMRQ": 12.05932,
    "operatingMarginTTM": 9.57351,
    "operatingMarginMRQ": 14.56713,
    "returnOnEquity": 16.09878,
    "returnOnAssets": 6.94905,
    "returnOnInvestment": 10.38665,
    "quickRatio": 1.09706,
    "currentRatio": 1.38508,
    "interestCoverage": 16.57258,
    "totalDebtToCapital": 22.24951,
    "ltDebtToEquity": 23.79773,
    "totalDebtToEquity": 30.14083,
    "epsTTM": 3.08168,
    "epsChangePercentTTM": 501.1158,
    "epsChangeYear": 430.6936,
    "epsChange": 0,
    "revChangeYear": 0,
    "revChangeTTM": 66.26917,
    "revChangeIn": 15.04432,
    "sharesOutstanding": 1004264852,
    "marketCapFloat": 811.8643,
    "marketCap": 1019299,
    "bookValuePerShare": 69.61395,
    "shortIntToFloat": 0,
    "shortIntDayToCover": 0,
    "divGrowthRate3Year": 0,
    "dividendPayAmount": 0,
    "dividendPayDate": " ",
    "beta": 2.05101,
    "vol1DayAvg": 24914380,
    "vol10DayAvg": 24976442,
    "vol3MonthAvg": 534816470

With the new dashboard, showing results of these checks is rather easy so feel free to make suggestions on things that would be pertinent.

Also feel free to make suggestions or contributions to the main formula. Watching the scanner and providing feedback is also a huge help even if it’s just logging entries that were “what we’re looking for” so we can see what the numbers they returned are.


Adding an example of the quotes. All of this data is available at runtime (the fundamental data is grabbed and stored nightly and the oldQuote is simply added to that object in storage, then obviously the newQuote is present as well). The quote data is:

  "NAV": undefined,
  "ask": 334.6,
  "askSize": 1,
  "assetMainType": "EQUITY",
  "bid": 334.12,
  "bidSize": 2,
  "bidTickDirection": " ",
  "cusip": "594918104",
  "dailyHigh": 338,
  "dailyLow": 329.78,
  "dailyOpen": 335.35,
  "dailyVolume": 28918013,
  "delayed": false,
  "description": "Microsoft Corporation - Common Stock",
  "dividendAmount": 2.48,
  "dividendDate": "2022-02-16 00:00:00.000",
  "dividendYield": 0.74,
  "exchangeBestAsk": "P",
  "exchangeBestBid": "P",
  "exchangeLastTrade": "Q",
  "exchangeName": "NASD",
  "exchangeOfPrimaryListing": "q",
  "fiftyTwoWeekHigh": 349.67,
  "fiftyTwoWeekLow": 211.94,
  "fundPrice": undefined,
  "isLastQuoteFromRegularMarket": true,
  "isLastTradeFromRegularMarket": undefined,
  "isMarginable": true,
  "isShortable": true,
  "islandAsk": undefined,
  "islandAskSize": undefined,
  "islandBid": undefined,
  "islandBidSize": undefined,
  "islandVolume": undefined,
  "key": "MSFT",
  "last": 334.41,
  "lastQuoteTimeSecsFromMidnight": 71988,
  "lastRegularMarketTradeDay": 18995,
  "lastRegularMarketTradeTimeMSEpoch": 1641243600763,
  "lastRegularMarketTradeTimeSecsFromMidnight": 57600,
  "lastSize": undefined,
  "lastTradeTimeSecsFromMidnight": 71984,
  "mark": 334.6,
  "netChange": -1.91,
  "peRatio": 37.5958,
  "previousDayClose": 336.32,
  "quoteDay": 18995,
  "regularMarketLastPrice": 334.75,
  "regularMarketLastSize": 28339,
  "regularMarketNetchange": -1.57,
  "symbol": "MSFT",
  "timeLastQuote": 1641257988820,
  "timeLastTrade": 1641257984777,
  "timestamp": 1641280945471,
  "tradeDay": 18995,
  "tradingStatus": "Normal",
  "validDigits": 4,
  "volatility": 0.0117,

Again this can do more than just for momentum so I’m open to any ideas of things that Mimir should be scanning for.