我打造了一套不會替我出價的拍賣引擎,平台條款解釋原因
I built an auction engine that refuses to bid for me, and the platform terms say why
作者以 Python 標準函式庫和 SQLite 打造拍賣引擎,並在身份層拒絕賣家為自己的拍品出價,以遵守平台禁止自我競投的條款。測試涵蓋低於底價、低於最低加價、同一競投者再次出價及截標後出價等規則;合成競投的測試拍賣售出 3 件中的 2 件,總成交額為 215.50,拍賣行佣金為 2.72。
I started an auction house for a selfish reason, the goods I make have no channel. One-off prints, ESP32 devices, speaker prototypes, the surplus in a homelab that gets upgraded every year. None of it has a storefront that fits, so I built the machinery before I built the inventory.
The engine is stdlib Python plus SQLite, and the interesting part isn't the scheduling, it's the one rule I refused to encode as prose.
The trap is older than the code
Every platform that runs auctions has the same rule, and it's the one an autonomous agent will happily break. HiBid's Seller Terms, item 7, "Buyback and Price Manipulation":
You shall not bid or make an offer, directly or indirectly, nor allow any other person to bid or make an offer on your behalf, by agency or otherwise, on your Listed Item(s) or any part of your auctions... we may ban you from future use.
eBay's shill-bidding rule says the same thing in different words, and both threaten the ban rather than the refund. So the guard is structural. The engine refuses a bid whose identity matches the seller, at the identity layer, not in a prompt telling a model to behave. An agent that "helps the price along" is the fastest route to a banned account, and a banned account on your real name is worse than a bad sale.
def place_bid(self, lot_no, bidder_id, amount, at):
"""Returns (accepted, reason). Every rejection is a real auction rule."""
if bidder_id == self.seller_id:
return False, "SELF_BID: a seller may not bid on their own lot, directly or by agency"
lot = self.lot(lot_no)
if lot is None:
return False, "UNKNOWN_LOT"
if at > self._dt(lot["closes_at"]):
return False, "AFTER_CLOSE"
cur = lot["current_bid"]
# a reserve means the first legal bid is the reserve itself, not
# reserve-minus-an-increment; after that, increments off the live price
if cur == 0 and lot["reserve"] > 0:
min_next = lot["reserve"]
else:
min_next = max(cur, lot["reserve"]) + increment_for(max(cur, lot["reserve"]))
if amount < min_next:
return False, f"UNDER_MINIMUM: next valid bid is {min_next}"
if lot["high_bidder"] == bidder_id:
return False, "SAME_BIDDER: raising against yourself is not a contest"
Every rejection returns a reason string, so the suite can assert against the rules as written.
What the rules buy you
Reserve is the first legal bid, not reserve-minus-an-increment. An off-by-one here lets a lot open below reserve and reads as a rules violation to the platform, which is the difference between a flagged listing and a suspended account.
Increments follow price bands, the standard practice:
BANDS = [
(0, 25, 1), (25, 50, 2), (50, 100, 5), (100, 250, 10),
(250, 500, 25), (500, 1000, 50), (1000, 5000, 100),
(5000, 10 ** 12, 500),
]
Closes are staggered, never simultaneous. A single close invites sniping on every lot at once and makes the sale unwatchable, and lot order is the reverse of desirability so the best pieces close last.
A lot cannot exist without a condition report and a disclosure line. Those fields are non-optional in the engine, because a sloppy catalog should fail loudly instead of quietly lying about the goods. Seller terms also require that items are not stolen or counterfeit and that faults are disclosed plainly, the "sold AS IS, WHERE IS, with all its faults" convention.
Buyer's premium is the house's own fee and it's non-refundable by convention, so I set it explicitly with a minimum floor rather than trusting a provider default. Here's the reason that matters: HiBid charges bidders nothing and does not handle payments at all, the house sets its own premium and collects on its own card. If you don't set the premium, your margin doesn't exist.
The fee forensics, from the primary pages
| venue | house fee | other |
|---|---|---|
| HiBid | 2% of online hammer | $0.25 per unique bid, capped $150 per auction, $75 webcast setup |
| AuctionFlex 360 | subscription | $95/mo Solo, $145 Team, $195 Business, $295 Enterprise |
| LiveAuctioneers | 5% of hammer | live event fees $525-895/day, payments 3.99% + 25c, plus a separate buyer-side fee |
| Proxibid | 5% arts/antiques/firearms, 3% vehicles, 2% farm equipment | |
| Invaluable | 5% down to 1% by hammer size | acquired by Liquidity Services 2026-10-01 |
| eBay auction-style | ~13% all-in | automatable through his own API |
| Own platform | zero commission | Circuit, BidJS (two script tags), Bidsquare, BidWrangler, AuctionWorx |
The break-even rule from Circuit's worked example: below about $250k a year of online hammer, the marketplace-versus-own-platform choice is about brand and ownership, not cost. Above it, run both. The mistake most first-time sellers make is sizing the channel by its fee, a 2% house with no bidders earns less than a 13% marketplace with traffic. Model the bidder count before the fee.
One Florida note, and it's the kind of fact that belongs in any build like this, an online-only sale is not license-regulated there, in-person floor sales often are, and the online exemption doesn't carry, so re-verify before any floor sale.
The test sale
Synthetic bidders, labeled as such. The suite asserts: undocumented lot blocked, seller bid refused, under-reserve refused, under-increment refused, same-bidder raise refused, post-close refused, close order staggered, premium math correct including the minimum floor, and an unsold lot producing exactly zero revenue.
Gross on the block came out to 215.50 across 2 sold of 3 lots, house commission 2.72, and the unsold lot generated nothing. Every check passes.
The honest verdict
Machinery is a solved problem, money is not. The real cost line in a HiBid sale is the cataloging labor, not the fees, and that is exactly the job a 24/7 local model is good at, intake in, description plus condition report plus disclosure out, lot scheduling, invoicing, consignor settlement. What the model can't manufacture is bidders. The bottleneck is inventory plus audience, and it'll be reported as bidders, not as code.
來源:DEV Community · dev.to