Dynamic pricing for a Malaysian homestay works best as four separate layers, not one magic number: a per-weekday default price, seasonal rules for school holidays and long weekends, time-triggered same-day or next-day adjustments with a floor, and a competitor check that suggests but never pushes. This is how we price 100+ units in Johor Bahru and Desaru, and where each layer breaks.
Search "dynamic pricing Airbnb" and the first page is PriceLabs, Beyond, Wheelhouse and Hospitable explaining demand multipliers and machine learning. They are good tools. But they were built for markets where guests book weeks ahead, and none of them is written for an operator whose Friday-night guests decide on Friday afternoon at the Causeway, whose Agoda share is as big as Airbnb's, and whose channel manager sometimes quietly refuses a price for three weeks. This article is the pricing stack we actually run inside AntlerHub, layer by layer, including the parts that are still manual and the one failure we only caught because the code started complaining.
What does "dynamic pricing" actually mean for a homestay in Johor Bahru?
It means the price a guest sees for a given night is not a single number you typed once. It is the result of a stack: a base price per weekday, adjusted by any season that covers the date, adjusted again by a rule that fires at a fixed time before the stay, and compared against what nearby units are charging. Each layer has a different job and a different speed.
| Layer | What it answers | How often it changes | Who decides |
|---|---|---|---|
| 1. Default price by weekday | What is a normal Tuesday worth? A normal Saturday? | Rarely, set per unit | Operator |
| 2. Seasonal rule | What do school holidays, Chinese New Year, Raya and long weekends add? | A few times a year, drafted then pushed | Operator |
| 3. Dynamic plan | What happens to tonight's or tomorrow's empty night at 3pm? | Every day, automatically | Rule the operator set once |
| 4. AI competitor check | Are we 8% above or below the units next door on Booking.com and Agoda? | On demand, per unit per date | Suggestion only; a person pushes |
The order matters. Layer 1 is the floor everything else stands on. If a unit has no default price for a weekday, the system does not push anything for that day at all, on purpose, because pushing zero or guessing would be worse than leaving the channel manager alone.
Layer 1: why does the default price have to be per weekday, not one number?
Because a Johor Bahru homestay does not have one demand curve. It has two. Friday and Saturday nights fill with Singaporean families and KL weekenders; Sunday to Thursday is a different market of work trips, medical visits and overflow from events. Pricing both at the same number means you either leave money on the table at the weekend or sit empty midweek. Our Desaru units show this even more sharply, which we covered in the Desaru weekends-full, midweek-empty article.
So every unit in our PMS carries seven default prices, one per weekday, plus seven matching minimum prices. The minimum is the number a later layer is never allowed to go below unless a human explicitly ticks "allow below minimum" on that one rule.
Once a night, in a window between 00:30 and 01:59 Malaysia time, a scheduler pushes the effective price (default, with any seasonal rule applied) to the channel manager for every day that newly entered the rolling calendar window, which is 730 days by default. Only the new days. It never re-blankets dates that are already out there, because that would overwrite seasonal, dynamic and manual edits every single morning. That rule sounds obvious until you read vendor forums full of hosts whose manual price changes vanished overnight.
One detail that took us an audit to get right: the push used to run at exactly one minute, 00:30. If the process restarted across that minute, or the server was busy, the day was skipped and the gap at the far end of the calendar was never filled, because the next run only looked at the newest day. Now the window is 90 minutes wide and the push counts days since the last successful run, so a missed night is caught up the next night. A pricing system that "runs daily" should be asked: what happens on the day it does not?
Layer 2: how do seasonal rules work without creating a mess?
A seasonal rule in our system is a date range, a set of weekdays inside that range, a scope (all units or selected units), and an adjustment: increase or decrease by a percentage or a fixed ringgit amount. For example: 2026-12-18 to 2027-01-03, Friday and Saturday only, all units, +40%.
Three design choices keep this from becoming chaos at 100+ units.
Highest price wins when rules overlap. A school-holiday rule and a long-weekend rule can both cover the same Saturday. We do not stack them (40% plus 25% equals a price nobody books) and we do not take the first one created. We compute each and keep the higher result. That is the peak-season-safe choice: an overlap can only raise the price, never accidentally discount it.
Tiered adjustments by base price. A flat +RM50 is a 30% jump on a RM170 studio and a 6% nudge on an RM800 villa. So a rule can carry tiers: if base is RM0 to RM200, add 25%; RM201 to RM400, add 20%; above RM400, add 15%. One rule covers the whole portfolio sensibly.
Everything is a draft until pushed. Creating, editing or binding a new unit to a rule changes nothing on any channel. The rule goes grey, meaning "edited since last push," and the operator pushes when ready. Every push is logged per unit, so if Agoda accepted 97 units and refused 3, the screen shows the 3 and lets you retry them. The silent-partial-success problem we described in 7 things channel sync gets wrong applies to prices exactly as much as to availability. A bulk upload from a spreadsheet lands the same way: every row becomes a draft, bad rows are reported by row number, nothing is pushed.
Layer 3: what is a dynamic plan and why are the trigger times fixed?
A dynamic plan is a rule that says: at this time, for this target stay date, adjust the price by this much. It is the "last-minute discount" of the global tools, built for how guests here actually book.
| Trigger | Fires at (Malaysia time) | Target night | Typical use |
|---|---|---|---|
| Same day, 3pm | 15:00 today | Tonight | First cut on an empty weeknight |
| Same day, 6pm | 18:00 today | Tonight | Second, deeper cut before the evening wave |
| One day before | 00:15 | Tomorrow | Open the day with next-day pricing already set |
| Daily 3pm / 6pm | 15:00 / 18:00 | Tomorrow | Next-day cut for units with slow midweek pickup |
| Target +7 days | 00:17 | One week out | Early nudge for a week that looks empty |
| Custom | Any HH:MM | 0 to 30 days out | Anything else |
The adjustment can be a percentage or a fixed amount, up or down. Two guardrails matter more than the trigger menu.
It adjusts from the channel's current price, not from the default. At 15:00 the scheduler asks the channel manager what tonight's rate actually is, then applies the rule to that. If a seasonal rule already lifted the night to RM280, a 10% same-day cut gives RM252, not 10% off a stale default. Each plan touches each target date exactly once (the run is keyed by date and trigger), so the 3pm cut and the 6pm cut do not compound into something you never intended.
It respects the weekday minimum. Unless the plan explicitly says "allow below minimum," the result is raised back up to that weekday's floor. A 20% cut on a RM150 Saturday with a RM140 floor gives RM140, not RM120. This is the single most important guardrail, because the plan does not know that tonight is the night a nearby concert sells out. The floor is your judgement, written down once.
Here is the honest limit: the dynamic plan does not check whether the night is still empty. It pushes the adjusted rate to the channel manager for every active unit in scope. On a night that is already booked, a new rate changes nothing for that booking, so no money is lost, but it also means the plan is not "occupancy-aware" the way the global tools describe. We decided that was acceptable at our scale because the cut is small and the floor holds. If you run three units and want the system to look at pickup pace before discounting, this layer is not that, and we would rather say so than let you find out.
Layer 4: what does the AI competitor check actually do?
On demand, for one unit and one stay date, it crawls Booking.com and Agoda for comparable listings near the unit's coordinates with the same bedroom count, up to 20 of them, and compares their price for that night against ours on each channel. It reports the market median, how far we are from it in percent, a suggested price (the median, or the lowest competitor plus RM10 if there is no median), and one of three words: raise, lower or hold. "Hold" is the answer whenever we are within 8% either way, because a 5% gap is noise, not a signal.
Then it stops. It does not push anything. A person reads the suggestion and decides. We built it this way deliberately: a crawler can mistake a hostel dorm for a studio, miss a cleaning fee, or compare a Friday against a Thursday if a listing's calendar is odd. A tool that auto-applies that mistake across 100 units at 2am is not a feature. There is also a one-minute cooldown per unit and date and an eight-minute lock while a run is in progress, so two staff cannot fire the same crawl at once. And a unit with no coordinates, no bedroom count, or inactive status is refused up front; the crawl is only as good as the listing data behind it.
Is this "AI pricing" in the sense the big tools mean? No. It is a competitor snapshot with a rule on top. We call it AI pricing on the product page because the crawl and matching use models; the pricing decision is still human. If a vendor tells you their algorithm "learns your market," ask to see the comp set it learned from for one specific night.
What happens when a price push fails and nobody notices?
This is the part we would rather not write, but it is the most useful thing in the article. For several weeks, three of our units were returning a 403 error from the channel manager every time a dynamic plan tried to push a rate. Every failure was logged. The scheduler printed it. The database kept it. Nobody was told, because a log line nobody reads is a receipt, not a signal. Those units sold at whatever price was last accepted.
The fix was not a new alerts screen; that would be a second place to check, and a second place to check is a place that stops being checked. The fix was a rule: a streak of failures is counted per plan and per unit, and at exactly three consecutive failures the system raises a task in the same operations task list staff already open every morning. Not at one failure, because a single timeout usually fixes itself on the next tick. Not on every failure after three, because that buries the operator. One success clears the streak, and if the same unit breaks again later, that is a new outbreak with a new number, so it raises a new task instead of silently rewriting one somebody already closed. The task says "the channel manager is refusing us for this unit," not "HOSTPLATFORM_403," because a task you cannot act on is the same as no task.
Whatever pricing tool you choose, ask one question: when the push for one unit fails for the fifth day in a row, who finds out, and where?
Does dynamic pricing make more money? What we will and will not claim
The global tools quote 10% to 40% more annual revenue than static pricing. We are not going to quote a number, because our portfolio has grown, changed mix and changed channels over the same period, so we cannot cleanly separate the effect of pricing rules from everything else. What we can say from running it: the weekday default plus floor stopped the worst midweek underpricing, seasonal tiers stopped us from hand-editing 100 calendars before every school holiday, and the same-day cuts fill some nights that would otherwise have gone empty, at a price we chose in advance rather than in a panic at 7pm.
Two things we will not do: promise that the system knows your market better than you do, or hide that layers 3 and 4 are simpler than the brochures of the global tools. The four-layer stack is a way of writing your judgement down once so that it runs every day without you, with a floor under it and an alarm on top.
What does this cost, and what do I need to start?
AntlerHub's published price is one flat rate: RM 1.50 per credit, and every active unit uses 10 credits a month, so RM 15 per unit per month before SST, with 50 free credits for a new account, no setup fee and no contract. All four pricing layers are included; they are not a paid add-on. Current figures are always on the AntlerHub pricing page.
To start you need, per unit: a default price for each weekday, a minimum for each weekday (set it to the price at which you would rather stay empty), the unit's coordinates and bedroom count if you want the competitor check, and a channel manager connection. Then one seasonal rule for the next school holiday, drafted and pushed, and one same-day plan for your slowest weeknight with a modest cut. Run it for a month, read the push log, and only then add more rules. Every rule you add is one more thing that can overlap, so fewer, well-floored rules beat a clever stack you no longer understand.
If you want to see the four layers on a real calendar before deciding, the AntlerHub page walks through the same system we use on our own units.