I have a bunch of ticker prices and volumes that keep getting stored from websockets via Node.js
Currently storing each ticker in a hash like
"XYZ:Source1": {"price": 100, "volume": 10000}
"XYZ:Source2": {"price": 120, "volume": 15000}
"ABC:Source1": {"price": 544, "volume": 18000}
As soon as price or volume of XYZ changes from any source, I want the VWAP aggregate XYZ price which in the above example would be (100 * 10000 + 120 * 15000) / (10000 + 15000) and ABC computed on redis
How can I achieve this on redis level without loading everything into my application
I think it depends on how frequently your tickers update and how many sources each ticker has.
If the numbers are small, then you can use a Lua script to read every source every time a ticker updates.
Note: I've realized the use of SCAN in the next example is invalid because it can return duplicate values. I'm leaving this example solution here because it might be able to be fixed.
First, I would name your sources "ABC:source:1" so you can store the VWAP under the key "ABC:VWAP" and not mix that key up with sources. Then the Lua script would look something like this (I have not tested this):
-- Execute as:
--
-- EVALSHA <hash> 2 ABC:source:* ABC:VWAP
--
-- "ABC:source:*" is the prefix of all sources
-- "ABC:VWAP" is the key to store the VWAP
local cursor = 0
-- numerator and denominator of VWAP price
local num = 0
local denom = 0
repeat
-- scan through all keys matching: ABC:source:*
local result = redis.call("HSCAN", 0, "MATCH", KEYS[1])
cursor = result[1]
local keys = result[2]
-- read the price and quantity of each ticket
-- update the numerator and denominator
for i = 1, #keys do
local ticker = redis.call("HMGET", keys[i], "price", "quantity")
num += ticker[1] * ticker[2]
denom += ticker[2]
end
until cursor == 0
local vwap = 0
if denom > 0 do
vwap = num / denom
end
-- store the VWAP in the key: ABC:VWAP
redis.call("SET", KEYS[2], vwap)
Although this executes atomically on the Redis server and doesn't bring the data back to your application, it makes at least N+2 look-ups, where N is the number of sources of the ticker. If the number of sources is high, then you may want to avoid scanning them all every time a ticket updates. In that case, you could store num and denom in Redis and update them each time a ticket updates. The keys would be something like:
ABC:source:<name> - Sources
ABC:VWAP:num - Current VWAP numerator
ABC:VWAP:denom - Current VWAP denominator
ABC:VWAP - Current VWAP
The algorithm to do a ticker update would be something like this:
Read the key ABC:source:1 to get the current price and quantity
Update the key ABC:source:1 with the new price and quantity
Calculate the numerator change: num_change = price * quantity - prevPrice * prevQuantity
Calculate the denominator change: denom_change = quantity - prevQuantity
INCRBY ABC:VWAP:num <num_change> (returns the new numerator)
INCRBY ABC:VWAP:denom <denom_change> (returns the new denominator)
Calculate vwap = num / denom and update the VWAP: SET ABC:VWAP <vwap>
This would have only 5 Redis look-ups each time a ticker updates. But it introduces the possibility of errors if the cached numerator and denominator values don't get updated properly.
Related
I was needed to implement set of items with individual expiration, so I used zsetwith score of expiration timestamp.
Now I want to get random item from range of not expired items, or at least from all items in set.
How can I do it?
Can I get min and max rank of range and random rank in between of it via LUA scripting?
Redis version: 5.0.2
I solve this via following script:
-- KEYS[1] - set key
-- ARGV[1] - seed timestamp
local count = redis.call('ZCARD', KEYS[1])
if count ~= 0 then
math.randomseed(ARGV[1])
local rank = math.random(0, count - 1)
local range = redis.call('ZRANGE', KEYS[1], rank, rank)
return range[1]
else
return ''
end
And because I search among all items I do sanitization from expired items every n seconds.
Can change:
ARGV[1] -> os.time()
When i add a score for a key using zincrby, it increases the score and puts the element in lexicographical order.
Can i get this list in the order, in which the elements are updated or added ?
e.g>
If I execute
zincrby A 100 g
zincrby A 100 a
zincrby A 100 z
and then
zrange A 0 -1
then the result is
a->g->z
where, i want the result in order the entries are made so,
g->a->z
As score is same for all, redis is placing the elements in lexicographical order. Is there any way to prevent it ?
I don't think it is possible, but if you want to keep the order of insertion with scores, you should manipulate something like this:
<score><timestamp>
instead of
<score>
You will have to define a good time record (millis should be ok). Then you can use
zincrby A 100 * (10^nbdigitsformillis)
For instance:
Score = 100 and timestamps is 1381377600 seconds
That gives: 1001381377600
You incr by 200 the score: 1001381377600 + 200 * 10 = 3001381377600
Be careful with zset as it stores scores with double values (64 bits, but only 52 available for int value) so don't store more than 15-17 digits.
If you can't do that (need for great timestamp precision, and great score precision), you will have to manage two zsets (one for actual score, one for timestamp) and managing your ranking manual with the two values.
So I need an idea of how to divide out an amount of money into actual counts of various bills and coinage. I know this is confusing, so let me give an example:
$16.32 - Sixteen dollars and thirty-two cents
One $10 bill
One $5 bill
One $1 bill
One Quarter ($0.25)
One Nickel ($0.05)
Two Pennies ($0.01)
So as you can see, we're just getting the number of bills and coinage that goes into a value, which will change according to user input.
Here's my current setup (Visual Basic):
If 100 Mod amount < 0 Then
If 50 Mod amount < 0 Then
' Continue this pattern until you get all the way down to the end ($0.01)
Else
While amount > 50
fiftiesAmount += 1
amount -= 50
End If
Else
While amount > 100
hundredsAmount += 1
amount -= 100
End If
Basically, each If statement determines whether or not your total amount needs an extra billing amount of that type, and then either adds to the amount of bills/coinage already created or moves on to the next amount.
Is this an efficient way of doing things, or am I missing out on an easier/faster algorithm/pattern that would make my life, and whoever is reading my code's life easier?
If you need extra details, I'll be happy to edit the question as needed.
Convert your amount to cents (it's easier). Divide by the currency value being tested, and then deduct that amount from the balance (pseudo-code)
Value = 16.32 * 100 ' Convert to cents
If Value > 10000 ' Hundreds
Hundreds = Value / 10000 ' How many?
Value = Value - (Hundreds * 10000) ' Reduce amount accordingly
End If
If Value > 5000 ' Fifties
Fifties = Value / 5000
Value = Value - (Fifties * 5000)
End If
If Value > 2000 ' Twenties
Twenties = Value / 2000
Value = Value - (Twenties * 2000)
End If
Repeat until you have less than 100, at which point you start with coins (50, 25, 10, 5)
Once you've got > 10, you've reached pennies; save them, reduce Value by that amount, and
Value is zero, so you're finished.
I'm building an application and I'm finding it necessary to perform some simple math calculations in my query. Essentially, I've got a database with daily values from the S&P 500, and I need to get a listing of days depending on the criteria entered.
The user inputs both a day range, and a % range. For instance, if the date range is Jan 1/2013 - Apr 1/2013 and the % range is -1% - 1%, it should return a list of all S%P 500 daily values between the dates where difference between the opening and closing values are in the % range.
The problem is that I don't actually have a column for %; I only have a column for opening/closing values. It is simple enough to calculate the % given only the opening and closing values (close-open)/open*100. But I'm not sure how to do this within the query.
Right now the query is successfully searching within the date range. My query is:
#cases = Close.find(:all, conditions:["date between ? and ?",
#f_start, #f_end])
But how can I get it to check if the current row's (close-open)/open*100 value is between the two % range values?
Alternatively, if this is not possible or in bad practice, where should I be handling this?
You can calculate the open/close range yourself in Ruby/Rails and pass it on the same way you do with the date range. Something like:
percent = #f_percent / 100.0 # 5% => 0.05
low = #f_close * (1.0 - percent)
high = #f_close * (1.0 + percent)
Close.where 'date between ? and ? AND close between ? and ?', \
#f_start, #f_end, low, high
What's the best way to get the sum of SCORES in a Redis sorted set?
The only option I think is iterating the sorted set and computing the sum client side.
Available since Redis v2.6 is the most awesome ability to execute Lua scripts on the Redis server. This renders the challenge of summing up a Sorted Set's scores to trivial:
local sum=0
local z=redis.call('ZRANGE', KEYS[1], 0, -1, 'WITHSCORES')
for i=2, #z, 2 do
sum=sum+z[i]
end
return sum
Runtime example:
~$ redis-cli zadd z 1 a 2 b 3 c 4 d 5 e
(integer) 5
~$ redis-cli eval "local sum=0 local z=redis.call('ZRANGE', KEYS[1], 0, -1, 'WITHSCORES') for i=2, #z, 2 do sum=sum+z[i] end return sum" 1 z
(integer) 15
If the sets are small, and you don't need killer performance, I would just iterate (zrange/zrangebyscore) and sum the values client side.
If, on the other hand, you are talking about many thousands - millions of items, you can always keep a reference set with running totals for each user and increment/decrement them as the gifts are sent.
So when you do your ZINCR 123:gifts 1 "3|345", you could do a seperate ZINCR command, which could be something like this:
ZINCR received-gifts 1 <user_id>
Then, to get the # of gifts received for a given user, you just need to run a ZSCORE:
ZSCORE received-gifts <user_id>
Here is a little lua script that maintains the zset score total as you go, in a counter with key postfixed with '.ss'. You can use it instead of ZADD.
local delta = 0
for i=1,#ARGV,2 do
local oldScore = redis.call('zscore', KEYS[1], ARGV[i+1])
if oldScore == false then
oldScore = 0
end
delta = delta - oldScore + ARGV[i]
end
local val = redis.call('zadd', KEYS[1], unpack(ARGV))
redis.call('INCRBY', KEYS[1]..'.ss', delta)