LAgent: an agent framework in F# – part VII – An auction application

-

Download frame­work here.

All posts are here:

Here is an ap­pli­ca­tion that uses the frame­work we have been cre­at­ing. It is an auc­tion ap­pli­ca­tion and it is de­scribed in more de­tail here.

Let’s go through it.

type AuctionMessage =
  | Offer of int * AsyncAgent // Make a bid
  | Inquire of AsyncAgent     // Check the status
and AuctionReply =
  | StartBidding
  | Status of int * DateTime // Asked sum and expiration
  | BestOffer                // Ours is the best offer
  | BeatenOffer of int       // Yours is beaten by another offer
  | AuctionConcluded of      // Auction concluded
      AsyncAgent * AsyncAgent
  | AuctionFailed            // Failed without any bids
  | AuctionOver              // Bidding is closed
let timeToShutdown = 3000
let bidIncrement = 10 

This is the for­mat of the mes­sages that the clients can send and the ac­tion agent can re­ply to. F# is re­ally good at this sort of thing. First, we need an auc­tion agent:

let auctionAgent seller minBid closing =
    let agent = spawnAgent (fun msg (isConcluded, maxBid, maxBidder) ->
                            match msg with
                            | Offer (_, client) when isConcluded ->
                                client <-- AuctionOver
                                (isConcluded, maxBid, maxBidder)
                            | Offer(bid, client) when not(isConcluded) ->
                                if bid >= maxBid + bidIncrement then
                                    if maxBid >= minBid then maxBidder <-- BeatenOffer bid
                                    client <-- BestOffer
                                    (isConcluded, bid, client)
                                else
                                    client <-- BeatenOffer maxBid
                                    (isConcluded, maxBid, maxBidder)
                            | Inquire client    ->
                                client <-- Status(maxBid, closing)
                                (isConcluded, maxBid, maxBidder))
                            (false, (minBid - bidIncrement), spawnWorker (fun _ -> ()))                             

Notice that, if the ac­tion is con­cluded, the agent replies to of­fers by send­ing an AuctionOver mes­sage. If the auc­tion is still open, then, in case the bid is higher than the max, it sets a new max and no­tify the two par­ties in­volved; oth­er­wise it no­ti­fies the bid­der that the of­fer was­n’t suc­cess­ful. Also you can ask for the sta­tus of the auc­tion.

This is what the code above says. Maybe the code is sim­pler than words. Anyhow, we need to treat the case where no mes­sage is re­ceived for some amount of time.

agent <-- SetTimeoutHandler
            (closing - DateTime.Now).Milliseconds
            (fun (isConcluded: bool, maxBid, maxBidder) ->
                if maxBid >= minBid then
                  let reply = AuctionConcluded(seller, maxBidder)
                  maxBidder <-- reply
                  seller <-- reply
                else seller <-- AuctionFailed
                agent <-- SetTimeoutHandler
                    timeToShutdown
                    (fun (_:bool, _:int,_:AsyncAgent) -> StopProcessing)
                ContinueProcessing (true, maxBid, maxBidder))
agent            

We start by wait­ing for the amount of time to the clos­ing of the auc­tion. If we get no mes­sages, then two things might hap­pen: we have an of­fer that is more than the min­i­mum or we don’t. If we do, we tell every­one that it’s fin­ished. Otherwise, we tell the seller that its item was­n’t suc­cess­ful.  In any case, we pre­pare the agent to shut­down by set­ting its next time­out to be time­out­ToShut­down.

It is in­ter­est­ing that we set the time­out han­dler in­side the time­out han­dler. This is not a prob­lem be­cause of the na­ture of mes­sage pro­cess­ing (aka it processes one mes­sage at the time).

We then need a bunch of of sym­bols …

module Auction =
  let random = new Random()
  let minBid = 100
  let closing = DateTime.Now.AddMilliseconds 10000.
  let seller = spawnWorker (fun (msg:AuctionReply) -> ())
  let auction = auctionAgent seller minBid closing

Not a very smart seller we have here … Next up is our de­f­i­n­i­tion of a client.

let rec c = spawnAgent (
                fun msg (max, current) ->
                    let processBid (aMax, aCurrent) =
                        if aMax >= top then
                            log "too high for me"
                            (aMax, aCurrent)
                        elif aCurrent < aMax then
                              let aCurrent = aMax + increment
                              Thread.Sleep (1 + random.Next 1000)
                              auction <-- Offer(aCurrent, c)
                              (aMax, aCurrent)
                        else (aMax, aCurrent)
                    match msg with
                    | StartBidding      ->
                        auction <-- Inquire c
                        (max, current)
                    | Status(maxBid,_)  ->
                        log <| sprintf "status(%d)" maxBid
                        let s = processBid (maxBid, current)
                        c <-- SetTimeoutHandler timeToShutdown (fun _ -> StopProcessing)
                        s
                    | BestOffer ->
                        log <| sprintf "bestOffer(%d)" current
                        processBid(max, current)
                    | BeatenOffer maxBid ->
                        log <| sprintf "beatenOffer(%d)" maxBid
                        processBid(maxBid, current)
                    | AuctionConcluded(seller, maxBidder) ->
                        log "auctionConcluded"
                        c <-- Stop
                        (max, current)
                    | AuctionOver ->
                        log "auctionOver"
                        c <-- Stop
                        (max, current))
                 (0,0)
c

Something that I like about agents is the fact that you need to un­der­stand just small snip­pets of code at the time. For ex­am­ple, you can read the pro­cess­ing for BestOffer and fig­ure out if it makes sense.  I have an easy time per­son­al­iz­ing them as in : Ok, the guy just got a no­ti­fi­ca­tion that there has been a bet­ter of­fer, what is he go­ing to do next?”.

The code should be self ex­plana­tory for the most part. In essence, if you can of­fer more, do it oth­er­wise wait for the auc­tion to end. I’m not even sure the pro­cess­ing is com­pletely right. I con­fess I’m just try­ing to do the same as Matthews code from the link above.

We can then start up the whole thing and en­joy the cool out­put.

open Auction
(client 1 20 200) <-- StartBidding
(client 2 10 300) <-- StartBidding
(client 3 30 150) <-- StartBidding
Console.ReadLine() |> ignore  

Now for the nasty part. Implementing the frame­work.

Tags

3 Comments

Comments

chris Donnan

2009-07-11T08:49:49Z

Great work. I have been work­ing on an F# limit or­der book en­gine, this is right up my al­ley. I am in­ter­ested to see your posts on the frame­work it­self.
Do you have any plans on get­ting an in­ter­process/ ma­chine ver­sion of it go­ing?
-Chris

Hi Chris, I’m think­ing about it. Most likely I won’t have the time to do it.
I’m hav­ing an hard time find­ing the time to pol­ish the frame­work to pub­lish it on code gallery …